[automerger skipped] Fix fvar table size validation logic - DO NOT MERGE am: 3085d8c96a  -s ours am: 7fa5424356  -s ours
am: 18b7993857  -s ours

Change-Id: I1c34fde5cff7a373e37a42a506d4428996ba03d2
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e0611e3
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,8 @@
+BasedOnStyle: Google
+DerivePointerAlignment: false
+IndentWidth: 4
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+ColumnLimit: 100
+AllowShortFunctionsOnASingleLine: Inline
+AccessModifierOffset: -4
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..f031ffc
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,5 @@
+[Builtin Hooks]
+clang_format = true
+
+[Builtin Hooks Options]
+clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions h,cpp
diff --git a/app/Android.bp b/app/Android.bp
index e8085c1..a74ca7e 100644
--- a/app/Android.bp
+++ b/app/Android.bp
@@ -27,4 +27,6 @@
     ],
 
     srcs: ["HyphTool.cpp"],
+
+    cflags: ["-Wall", "-Werror"],
 }
diff --git a/app/HyphTool.cpp b/app/HyphTool.cpp
index 403d374..33d42b4 100644
--- a/app/HyphTool.cpp
+++ b/app/HyphTool.cpp
@@ -1,17 +1,17 @@
-#include <stdio.h>
-#include <sys/stat.h>
 #include <string.h>
-
-#include "unicode/locid.h"
-#include "utils/Log.h"
-
+#include <sys/stat.h>
+#include <cstdio>
 #include <vector>
-#include <minikin/Hyphenator.h>
+
+#include <unicode/locid.h>
+#include <utils/Log.h>
+
+#include "minikin/Hyphenator.h"
 
 using minikin::HyphenationType;
 using minikin::Hyphenator;
 
-Hyphenator* loadHybFile(const char* fn, int minPrefix, int minSuffix) {
+Hyphenator* loadHybFile(const char* fn, int minPrefix, int minSuffix, const char* language) {
     struct stat statbuf;
     int status = stat(fn, &statbuf);
     if (status < 0) {
@@ -32,11 +32,11 @@
         delete[] buf;
         return nullptr;
     }
-    return Hyphenator::loadBinary(buf, minPrefix, minSuffix);
+    return Hyphenator::loadBinary(buf, minPrefix, minSuffix, language);
 }
 
 int main(int argc, char** argv) {
-    Hyphenator* hyph = loadHybFile("/tmp/en.hyb", 2, 3);  // should also be configurable
+    Hyphenator* hyph = loadHybFile("/tmp/en.hyb", 2, 3, "en");  // should also be configurable
     std::vector<HyphenationType> result;
     std::vector<uint16_t> word;
     if (argc < 2) {
@@ -53,7 +53,7 @@
         // ASCII (or possibly ISO Latin 1), but kinda painful to do utf conversion :(
         word.push_back(c);
     }
-    hyph->hyphenate(&result, word.data(), word.size(), icu::Locale::getUS());
+    hyph->hyphenate(word, &result);
     for (size_t i = 0; i < len; i++) {
         if (result[i] != HyphenationType::DONT_BREAK) {
             printf("-");
diff --git a/doc/minikin_style.md b/doc/minikin_style.md
new file mode 100644
index 0000000..3c1c521
--- /dev/null
+++ b/doc/minikin_style.md
@@ -0,0 +1,39 @@
+### Minikin Style Guide
+
+The C++ style in Minikin follows Android Framework C++ Code Style Guide except for following rules:
+
+ * Order of include
+
+ In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in
+ dir2/foo2.h, order your includes as follows:
+
+   1. dir2/foo.h
+   2. A blank line
+   3. C system files
+   4. C++ system files
+   5. A blank line
+   6. Other libraries' files
+   7. A blank line
+   8. Minikin public files
+   9. A blank line
+   10. Minikin private files
+
+ For example,
+ ```
+ #include "minikin/Layout.h"  // The corresponding header file.
+
+ #include <math.h>  // C system header files.
+ #include <string>  // C++ system header files.
+
+ #include <hb.h>  // Other library, HarfBuzz, header file.
+ #include <log/log.h>  // Other library, Android, header file.
+ #include <unicode/ubidi.h>  // Other library, ICU, header file.
+
+ #include "minikin/Emoji.h"  // The minikin public header file.
+ #include "HbFontCache.h"  // The minikin private header file.
+ ```
+
+ * "<>" vs ""
+
+   * `#include <...>` should be used for non local library files.
+   * `#include "..."` should be used for minikin header files.
diff --git a/include/minikin/AndroidLineBreakerHelper.h b/include/minikin/AndroidLineBreakerHelper.h
new file mode 100644
index 0000000..462644d
--- /dev/null
+++ b/include/minikin/AndroidLineBreakerHelper.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 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_ANDROID_LINE_BREAKER_HELPERS_H
+#define MINIKIN_ANDROID_LINE_BREAKER_HELPERS_H
+
+#include <algorithm>
+
+#include "minikin/LineBreaker.h"
+
+namespace minikin {
+namespace android {
+
+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)
+            : mFirstWidth(firstWidth),
+              mFirstLineCount(firstLineCount),
+              mRestWidth(restWidth),
+              mIndents(indents),
+              mLeftPaddings(leftPaddings),
+              mRightPaddings(rightPaddings),
+              mOffset(indentsAndPaddingsOffset) {}
+
+    float getAt(size_t lineNo) const override {
+        const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount) ? mFirstWidth : mRestWidth;
+        return std::max(0.0f, width - get(mIndents, lineNo));
+    }
+
+    float getMin() const override {
+        // A simpler algorithm would have been simply looping until the larger of
+        // mFirstLineCount and mIndents.size()-mOffset, but that does unnecessary calculations
+        // when mFirstLineCount is large. Instead, we measure the first line, all the lines that
+        // have an indent, and the first line after firstWidth ends and restWidth starts.
+        float minWidth = std::min(getAt(0), getAt(mFirstLineCount));
+        for (size_t lineNo = 1; lineNo + mOffset < mIndents.size(); lineNo++) {
+            minWidth = std::min(minWidth, getAt(lineNo));
+        }
+        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()) {
+            return 0;
+        }
+        const size_t index = lineNo + mOffset;
+        if (index < vec.size()) {
+            return vec[index];
+        } else {
+            return vec.back();
+        }
+    }
+
+    const float mFirstWidth;
+    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)
+            : mStrategy(strategy),
+              mFrequency(frequency),
+              mIsJustified(isJustified),
+              mIndents(std::move(indents)),
+              mLeftPaddings(std::move(leftPaddings)),
+              mRightPaddings(std::move(rightPaddings)) {}
+
+    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 {
+        AndroidLineWidth lineWidth(firstWidth, firstWidthLineCount, restWidth, mIndents,
+                                   mLeftPaddings, mRightPaddings, indentsOffset);
+        return breakIntoLines(textBuf, mStrategy, mFrequency, mIsJustified, measuredText, lineWidth,
+                              TabStops(tabStops, tabStopSize, defaultTabStopWidth));
+    }
+
+    inline BreakStrategy getStrategy() const { return mStrategy; }
+    inline HyphenationFrequency getFrequency() const { return mFrequency; }
+    inline bool isJustified() const { return mIsJustified; }
+
+private:
+    const BreakStrategy mStrategy;
+    const HyphenationFrequency mFrequency;
+    const bool mIsJustified;
+    const std::vector<float> mIndents;
+    const std::vector<float> mLeftPaddings;
+    const std::vector<float> mRightPaddings;
+};
+
+}  // namespace android
+}  // namespace minikin
+
+#endif  // MINIKIN_ANDROID_LINE_BREAKER_HELPERS_H
diff --git a/include/minikin/Characters.h b/include/minikin/Characters.h
new file mode 100644
index 0000000..074d134
--- /dev/null
+++ b/include/minikin/Characters.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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_CHARACTERS_H
+#define MINIKIN_CHARACTERS_H
+
+#include <cstdint>
+
+namespace minikin {
+
+// Code point order
+constexpr uint32_t CHAR_LINE_FEED = 0x000A;
+constexpr uint32_t CHAR_CARRIAGE_RETURN = 0x000D;
+constexpr uint32_t CHAR_TAB = 0x0009;
+constexpr uint32_t CHAR_HYPHEN_MINUS = 0x002D;
+constexpr uint32_t CHAR_NBSP = 0x00A0;
+constexpr uint32_t CHAR_SOFT_HYPHEN = 0x00AD;
+constexpr uint32_t CHAR_MIDDLE_DOT = 0x00B7;
+constexpr uint32_t CHAR_ARMENIAN_HYPHEN = 0x058A;
+constexpr uint32_t CHAR_MAQAF = 0x05BE;
+constexpr uint32_t CHAR_UCAS_HYPHEN = 0x1400;
+constexpr uint32_t CHAR_ZWJ = 0x200D;
+constexpr uint32_t CHAR_HYPHEN = 0x2010;
+
+}  // namespace minikin
+
+#endif  // MINIKIN_CHARACTERS_H
diff --git a/include/minikin/CmapCoverage.h b/include/minikin/CmapCoverage.h
index af5960d..583593d 100644
--- a/include/minikin/CmapCoverage.h
+++ b/include/minikin/CmapCoverage.h
@@ -17,7 +17,7 @@
 #ifndef MINIKIN_CMAP_COVERAGE_H
 #define MINIKIN_CMAP_COVERAGE_H
 
-#include <minikin/SparseBitSet.h>
+#include "minikin/SparseBitSet.h"
 
 #include <memory>
 #include <vector>
@@ -27,7 +27,7 @@
 class CmapCoverage {
 public:
     static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size,
-            std::vector<std::unique_ptr<SparseBitSet>>* out);
+                                    std::vector<std::unique_ptr<SparseBitSet>>* out);
 };
 
 }  // namespace minikin
diff --git a/include/minikin/Emoji.h b/include/minikin/Emoji.h
index 2826173..046a9d6 100644
--- a/include/minikin/Emoji.h
+++ b/include/minikin/Emoji.h
@@ -31,4 +31,3 @@
 UCharDirection emojiBidiOverride(const void* context, UChar32 c);
 
 }  // namespace minikin
-
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index 138ba45..642bb6f 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -21,11 +21,14 @@
 #include <unordered_set>
 #include <vector>
 
-#include <minikin/MinikinFont.h>
-#include <minikin/FontFamily.h>
+#include "minikin/FontFamily.h"
+#include "minikin/MinikinFont.h"
 
 namespace minikin {
 
+// The maximum number of font families.
+constexpr uint32_t MAX_FAMILY_COUNT = 254;
+
 class FontCollection {
 public:
     explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
@@ -37,8 +40,8 @@
         int end;
     };
 
-    void itemize(const uint16_t *string, size_t string_length, FontStyle style,
-            std::vector<Run>* result) const;
+    void itemize(const uint16_t* string, size_t string_length, const MinikinPaint& paint,
+                 std::vector<Run>* result) const;
 
     // 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
@@ -50,12 +53,10 @@
 
     // Creates new FontCollection based on this collection while applying font variations. Returns
     // nullptr if none of variations apply to this collection.
-    std::shared_ptr<FontCollection>
-            createCollectionWithVariation(const std::vector<FontVariation>& variations);
+    std::shared_ptr<FontCollection> createCollectionWithVariation(
+            const std::vector<FontVariation>& variations);
 
-    const std::unordered_set<AxisTag>& getSupportedTags() const {
-        return mSupportedAxes;
-    }
+    const std::unordered_set<AxisTag>& getSupportedTags() const { return mSupportedAxes; }
 
     uint32_t getId() const;
 
@@ -77,21 +78,21 @@
     void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);
 
     const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs,
-            uint32_t langListId, int variant) const;
+                                                        uint32_t localeListId,
+                                                        FontFamily::Variant variant) const;
 
-    uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, int variant, uint32_t langListId,
-            const std::shared_ptr<FontFamily>& fontFamily) const;
+    uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+                             uint32_t localeListId,
+                             const std::shared_ptr<FontFamily>& fontFamily) const;
 
-    uint32_t calcCoverageScore(uint32_t ch, uint32_t vs,
-            const std::shared_ptr<FontFamily>& fontFamily) const;
+    uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
+                               const std::shared_ptr<FontFamily>& fontFamily) const;
 
-    static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,
-                                              const FontFamily& fontFamily);
+    static uint32_t calcLocaleMatchingScore(uint32_t userLocaleListId,
+                                            const FontFamily& fontFamily);
 
-    static uint32_t calcVariantMatchingScore(int variant, const FontFamily& fontFamily);
-
-    // static for allocating unique id's
-    static uint32_t sNextId;
+    static uint32_t calcVariantMatchingScore(FontFamily::Variant 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 04c95bc..66fe3a8 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -22,73 +22,25 @@
 #include <unordered_set>
 #include <vector>
 
-#include <hb.h>
-
-#include <utils/TypeHelpers.h>
-
-#include <minikin/SparseBitSet.h>
+#include "minikin/FontStyle.h"
+#include "minikin/HbUtils.h"
+#include "minikin/Macros.h"
+#include "minikin/SparseBitSet.h"
 
 namespace minikin {
 
+class Font;
 class MinikinFont;
 
-// FontStyle represents all style information needed to select an actual font
-// from a collection. The implementation is packed into two 32-bit words
-// so it can be efficiently copied, embedded in other objects, etc.
-class FontStyle {
-public:
-    FontStyle() : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}
-    FontStyle(int weight, bool italic) : FontStyle(0 /* variant */, weight, italic) {}
-    FontStyle(uint32_t langListId)  // NOLINT(implicit)
-            : FontStyle(langListId, 0 /* variant */, 4 /* weight */, false /* italic */) {}
-
-    FontStyle(int variant, int weight, bool italic);
-    FontStyle(uint32_t langListId, int variant, int weight, bool italic);
-
-    int getWeight() const { return bits & kWeightMask; }
-    bool getItalic() const { return (bits & kItalicMask) != 0; }
-    int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }
-    uint32_t getLanguageListId() const { return mLanguageListId; }
-
-    bool operator==(const FontStyle other) const {
-          return bits == other.bits && mLanguageListId == other.mLanguageListId;
-    }
-
-    android::hash_t hash() const;
-
-    // Looks up a language list from an internal cache and returns its ID.
-    // If the passed language list is not in the cache, registers it and returns newly assigned ID.
-    static uint32_t registerLanguageList(const std::string& languages);
-private:
-    static const uint32_t kWeightMask = (1 << 4) - 1;
-    static const uint32_t kItalicMask = 1 << 4;
-    static const int kVariantShift = 5;
-    static const uint32_t kVariantMask = (1 << 2) - 1;
-
-    static uint32_t pack(int variant, int weight, bool italic);
-
-    uint32_t bits;
-    uint32_t mLanguageListId;
-};
-
-enum FontVariant {
-    VARIANT_DEFAULT = 0,
-    VARIANT_COMPACT = 1,
-    VARIANT_ELEGANT = 2,
-};
-
-inline android::hash_t hash_type(const FontStyle &style) {
-    return style.hash();
-}
-
 // 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) { }
+    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;
@@ -96,22 +48,73 @@
 
 struct FakedFont {
     // ownership is the enclosing FontCollection
-    MinikinFont* font;
+    const Font* font;
     FontFakery fakery;
 };
 
 typedef uint32_t AxisTag;
 
-struct Font {
-    Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);
-    Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);
-    Font(Font&& o);
-    Font(const Font& o);
+// Represents a single font file.
+class Font {
+public:
+    class Builder {
+    public:
+        Builder(const std::shared_ptr<MinikinFont>& typeface) : mTypeface(typeface) {}
 
-    std::shared_ptr<MinikinFont> typeface;
-    FontStyle style;
+        // 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;
+        }
 
-    std::unordered_set<AxisTag> getSupportedAxesLocked() const;
+        // 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 {
@@ -122,25 +125,27 @@
 
 class FontFamily {
 public:
-    explicit FontFamily(std::vector<Font>&& fonts);
-    FontFamily(int variant, std::vector<Font>&& fonts);
-    FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);
+    // 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
+    };
 
-    // TODO: Good to expose FontUtil.h.
-    static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
-            bool* italic);
+    explicit FontFamily(std::vector<Font>&& fonts);
+    FontFamily(Variant variant, std::vector<Font>&& fonts);
+    FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts);
+
     FakedFont getClosestMatch(FontStyle style) const;
 
-    uint32_t langId() const { return mLangId; }
-    int variant() const { return mVariant; }
+    uint32_t localeListId() const { return mLocaleListId; }
+    Variant 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(); }
-    const std::shared_ptr<MinikinFont>& getFont(size_t index) const {
-        return mFonts[index].typeface;
-    }
-    FontStyle getStyle(size_t index) const { return mFonts[index].style; }
-    bool isColorEmojiFamily() const;
+    const Font* getFont(size_t index) const { return &mFonts[index]; }
+    FontStyle getStyle(size_t index) const { return mFonts[index].style(); }
+    bool isColorEmojiFamily() const { return mIsColorEmoji; }
     const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; }
 
     // Get Unicode coverage.
@@ -161,17 +166,16 @@
 private:
     void computeCoverage();
 
-    uint32_t mLangId;
-    int mVariant;
+    uint32_t mLocaleListId;
+    Variant mVariant;
     std::vector<Font> mFonts;
     std::unordered_set<AxisTag> mSupportedAxes;
+    bool mIsColorEmoji;
 
     SparseBitSet mCoverage;
     std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage;
 
-    // Forbid copying and assignment.
-    FontFamily(const FontFamily&) = delete;
-    void operator=(const FontFamily&) = delete;
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(FontFamily);
 };
 
 }  // namespace minikin
diff --git a/include/minikin/FontStyle.h b/include/minikin/FontStyle.h
new file mode 100644
index 0000000..73cf427
--- /dev/null
+++ b/include/minikin/FontStyle.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 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_STYLE_H
+#define MINIKIN_FONT_STYLE_H
+
+namespace minikin {
+
+// FontStyle represents style information.
+class FontStyle {
+public:
+    enum class Weight : uint16_t {
+        THIN = 100,
+        EXTRA_LIGHT = 200,
+        LIGHT = 300,
+        NORMAL = 400,
+        MEDIUM = 500,
+        SEMI_BOLD = 600,
+        BOLD = 700,
+        EXTRA_BOLD = 800,
+        BLACK = 900,
+        EXTRA_BLACK = 1000,
+    };
+
+    enum class Slant : bool {
+        ITALIC = true,
+        UPRIGHT = false,
+    };
+
+    constexpr FontStyle() : FontStyle(Weight::NORMAL, Slant::UPRIGHT) {}
+    constexpr explicit FontStyle(Weight weight) : FontStyle(weight, Slant::UPRIGHT) {}
+    constexpr explicit FontStyle(Slant slant) : FontStyle(Weight::NORMAL, slant) {}
+    constexpr FontStyle(Weight weight, Slant slant)
+            : FontStyle(static_cast<uint16_t>(weight), slant) {}
+    constexpr FontStyle(uint16_t weight, Slant slant) : mWeight(weight), mSlant(slant) {}
+
+    constexpr uint16_t weight() const { return mWeight; }
+    constexpr Slant slant() const { return mSlant; }
+
+    constexpr bool operator==(const FontStyle& other) const {
+        return weight() == other.weight() && slant() == other.slant();
+    }
+
+    constexpr uint32_t identifier() const {
+        return (static_cast<uint32_t>(weight()) << 16) | static_cast<uint32_t>(slant());
+    }
+
+private:
+    uint16_t mWeight;
+    Slant mSlant;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_FONT_STYLE_H
diff --git a/include/minikin/GraphemeBreak.h b/include/minikin/GraphemeBreak.h
index f1b5102..8d8e359 100644
--- a/include/minikin/GraphemeBreak.h
+++ b/include/minikin/GraphemeBreak.h
@@ -17,29 +17,25 @@
 #ifndef MINIKIN_GRAPHEME_BREAK_H
 #define MINIKIN_GRAPHEME_BREAK_H
 
+#include <cstdint>
+
 namespace minikin {
 
 class GraphemeBreak {
 public:
     // These values must be kept in sync with CURSOR_AFTER etc in Paint.java
-    enum MoveOpt {
-        AFTER = 0,
-        AT_OR_AFTER = 1,
-        BEFORE = 2,
-        AT_OR_BEFORE = 3,
-        AT = 4
-    };
+    enum MoveOpt { AFTER = 0, AT_OR_AFTER = 1, BEFORE = 2, AT_OR_BEFORE = 3, AT = 4 };
 
     // Determine whether the given offset is a grapheme break.
     // This implementation generally follows Unicode's UTR #29 extended
     // grapheme break, with various tweaks.
     static bool isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
-            size_t count, size_t offset);
+                                size_t count, size_t offset);
 
     // Matches Android's Java API. Note, return (size_t)-1 for AT to
     // signal non-break because unsigned return type.
     static size_t getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
-            size_t count, size_t offset, MoveOpt opt);
+                                   size_t count, size_t offset, MoveOpt opt);
 };
 
 }  // namespace minikin
diff --git a/include/minikin/HbUtils.h b/include/minikin/HbUtils.h
new file mode 100644
index 0000000..68e4ab8
--- /dev/null
+++ b/include/minikin/HbUtils.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_HB_UTILS_H
+#define MINIKIN_HB_UTILS_H
+
+#include <hb.h>
+#include <memory>
+
+namespace minikin {
+
+struct HbBlobDeleter {
+    void operator()(hb_blob_t* v) { hb_blob_destroy(v); }
+};
+
+struct HbFaceDeleter {
+    void operator()(hb_face_t* v) { hb_face_destroy(v); }
+};
+
+struct HbFontDeleter {
+    void operator()(hb_font_t* v) { hb_font_destroy(v); }
+};
+
+struct HbBufferDeleter {
+    void operator()(hb_buffer_t* v) { hb_buffer_destroy(v); }
+};
+
+using HbBlobUniquePtr = std::unique_ptr<hb_blob_t, HbBlobDeleter>;
+using HbFaceUniquePtr = std::unique_ptr<hb_face_t, HbFaceDeleter>;
+using HbFontUniquePtr = std::unique_ptr<hb_font_t, HbFontDeleter>;
+using HbBufferUniquePtr = std::unique_ptr<hb_buffer_t, HbBufferDeleter>;
+
+}  // namespace minikin
+
+#endif  // MINIKIN_HB_UTILS_H
diff --git a/include/minikin/Hyphenator.h b/include/minikin/Hyphenator.h
index 2b8ccb7..26c2a19 100644
--- a/include/minikin/Hyphenator.h
+++ b/include/minikin/Hyphenator.h
@@ -18,15 +18,25 @@
  * An implementation of Liang's hyphenation algorithm.
  */
 
-#include "unicode/locid.h"
-#include <memory>
-#include <unordered_map>
-
 #ifndef MINIKIN_HYPHENATOR_H
 #define MINIKIN_HYPHENATOR_H
 
+#include <string>
+#include <vector>
+
+#include "minikin/Characters.h"
+#include "minikin/U16StringPiece.h"
+
 namespace minikin {
 
+class Hyphenator;
+
+// Registers the hyphenator.
+// This doesn't take ownership of the hyphenator but we don't need to care about the ownership.
+// In Android, the Hyphenator is allocated in Zygote and never gets released.
+void addHyphenator(const std::string& localeStr, const Hyphenator* hyphenator);
+void addHyphenatorAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr);
+
 enum class HyphenationType : uint8_t {
     // Note: There are implicit assumptions scattered in the code that DONT_BREAK is 0.
 
@@ -47,8 +57,8 @@
     // as "l-/l".
     BREAK_AND_REPLACE_WITH_HYPHEN = 6,
     // Break the line, and repeat the hyphen (which is the last character) at the beginning of the
-    // next line. Used in Polish, where "czerwono-niebieska" should hyphenate as
-    // "czerwono-/-niebieska".
+    // next line. Used in Polish (where "czerwono-niebieska" should hyphenate as
+    // "czerwono-/-niebieska") and Slovenian.
     BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE = 7,
     // Break the line, insert a ZWJ and hyphen at the first line, and a ZWJ at the second line.
     // This is used in Arabic script, mostly for writing systems of Central Asia. It's our default
@@ -56,62 +66,107 @@
     BREAK_AND_INSERT_HYPHEN_AND_ZWJ = 8
 };
 
-// The hyphen edit represents an edit to the string when a word is
-// hyphenated. The most common hyphen edit is adding a "-" at the end
-// of a syllable, but nonstandard hyphenation allows for more choices.
-// Note that a HyphenEdit can hold two types of edits at the same time,
+// The hyphen edit represents an edit to the string when a word is hyphenated.
+// The most common hyphen edit is adding a "-" at the end of a syllable, but nonstandard hyphenation
+// allows for more choices.
 // One at the beginning of the string/line and one at the end.
-class HyphenEdit {
-public:
-    static const uint32_t NO_EDIT = 0x00;
+enum class EndHyphenEdit : uint8_t {
+    // Note that everything inserting characters must have a value greater than or equal to
+    // INSERT_HYPHEN.
+    NO_EDIT = 0b000,
+    REPLACE_WITH_HYPHEN = 0b001,
 
-    static const uint32_t INSERT_HYPHEN_AT_END = 0x01;
-    static const uint32_t INSERT_ARMENIAN_HYPHEN_AT_END = 0x02;
-    static const uint32_t INSERT_MAQAF_AT_END = 0x03;
-    static const uint32_t INSERT_UCAS_HYPHEN_AT_END = 0x04;
-    static const uint32_t INSERT_ZWJ_AND_HYPHEN_AT_END = 0x05;
-    static const uint32_t REPLACE_WITH_HYPHEN_AT_END = 0x06;
-    static const uint32_t BREAK_AT_END = 0x07;
-
-    static const uint32_t INSERT_HYPHEN_AT_START = 0x01 << 3;
-    static const uint32_t INSERT_ZWJ_AT_START = 0x02 << 3;
-    static const uint32_t BREAK_AT_START = 0x03 << 3;
-
-    // Keep in sync with the definitions in the Java code at:
-    // frameworks/base/graphics/java/android/graphics/Paint.java
-    static const uint32_t MASK_END_OF_LINE = 0x07;
-    static const uint32_t MASK_START_OF_LINE = 0x03 << 3;
-
-    inline static bool isReplacement(uint32_t hyph) {
-        return hyph == REPLACE_WITH_HYPHEN_AT_END;
-    }
-
-    inline static bool isInsertion(uint32_t hyph) {
-        return (hyph == INSERT_HYPHEN_AT_END
-                || hyph == INSERT_ARMENIAN_HYPHEN_AT_END
-                || hyph == INSERT_MAQAF_AT_END
-                || hyph == INSERT_UCAS_HYPHEN_AT_END
-                || hyph == INSERT_ZWJ_AND_HYPHEN_AT_END
-                || hyph == INSERT_HYPHEN_AT_START
-                || hyph == INSERT_ZWJ_AT_START);
-    }
-
-    const static uint32_t* getHyphenString(uint32_t hyph);
-    static uint32_t editForThisLine(HyphenationType type);
-    static uint32_t editForNextLine(HyphenationType type);
-
-    HyphenEdit() : hyphen(NO_EDIT) { }
-    HyphenEdit(uint32_t hyphenInt) : hyphen(hyphenInt) { }  // NOLINT(implicit)
-    uint32_t getHyphen() const { return hyphen; }
-    bool operator==(const HyphenEdit &other) const { return hyphen == other.hyphen; }
-
-    uint32_t getEnd() const { return hyphen & MASK_END_OF_LINE; }
-    uint32_t getStart() const { return hyphen & MASK_START_OF_LINE; }
-
-private:
-    uint32_t hyphen;
+    INSERT_HYPHEN = 0b010,
+    INSERT_ARMENIAN_HYPHEN = 0b011,
+    INSERT_MAQAF = 0b100,
+    INSERT_UCAS_HYPHEN = 0b101,
+    INSERT_ZWJ_AND_HYPHEN = 0b110,
 };
 
+enum class StartHyphenEdit : uint8_t {
+    NO_EDIT = 0b00,
+
+    INSERT_HYPHEN = 0b01,
+    INSERT_ZWJ = 0b10,
+};
+
+typedef uint8_t HyphenEdit;
+constexpr uint8_t START_BITS_SHIFT = 3;
+// The following two masks must keep in sync with the definitions in the Java code at:
+// frameworks/base/graphics/java/android/graphics/Paint.java
+constexpr uint8_t MASK_END_OF_LINE = 0b00111;
+constexpr uint8_t MASK_START_OF_LINE = 0b11000;
+
+inline HyphenEdit packHyphenEdit(StartHyphenEdit start, EndHyphenEdit end) {
+    return static_cast<uint8_t>(start) << START_BITS_SHIFT | static_cast<uint8_t>(end);
+}
+
+inline EndHyphenEdit endHyphenEdit(HyphenEdit hyphenEdit) {
+    return static_cast<EndHyphenEdit>(hyphenEdit & MASK_END_OF_LINE);
+}
+
+inline StartHyphenEdit startHyphenEdit(HyphenEdit hyphenEdit) {
+    return static_cast<StartHyphenEdit>(hyphenEdit >> START_BITS_SHIFT);
+}
+
+inline bool isReplacement(EndHyphenEdit hyph) {
+    return hyph == EndHyphenEdit::REPLACE_WITH_HYPHEN;
+}
+
+inline bool isInsertion(StartHyphenEdit hyph) {
+    return hyph != StartHyphenEdit::NO_EDIT;
+}
+
+inline bool isInsertion(EndHyphenEdit hyph) {
+    return static_cast<uint8_t>(hyph) >= static_cast<uint8_t>(EndHyphenEdit::INSERT_HYPHEN);
+}
+
+template <typename T, size_t size>
+constexpr size_t ARRAYSIZE(T const (&)[size]) {
+    return size;
+}
+constexpr uint32_t HYPHEN_STR_ZWJ[] = {CHAR_ZWJ};
+constexpr uint32_t HYPHEN_STR_HYPHEN[] = {CHAR_HYPHEN};
+constexpr uint32_t HYPHEN_STR_ARMENIAN_HYPHEN[] = {CHAR_ARMENIAN_HYPHEN};
+constexpr uint32_t HYPHEN_STR_MAQAF[] = {CHAR_MAQAF};
+constexpr uint32_t HYPHEN_STR_UCAS_HYPHEN[] = {CHAR_UCAS_HYPHEN};
+constexpr uint32_t HYPHEN_STR_ZWJ_AND_HYPHEN[] = {CHAR_ZWJ, CHAR_HYPHEN};
+constexpr std::pair<const uint32_t*, size_t> EMPTY_HYPHEN_STR(nullptr, 0);
+#define MAKE_HYPHEN_STR(chars) std::make_pair((chars), ARRAYSIZE(chars))
+
+inline std::pair<const uint32_t*, size_t> getHyphenString(StartHyphenEdit hyph) {
+    if (hyph == StartHyphenEdit::INSERT_ZWJ) {
+        return MAKE_HYPHEN_STR(HYPHEN_STR_ZWJ);
+    } else if (hyph == StartHyphenEdit::INSERT_HYPHEN) {
+        return MAKE_HYPHEN_STR(HYPHEN_STR_HYPHEN);
+    } else {
+        return EMPTY_HYPHEN_STR;
+    }
+}
+
+inline std::pair<const uint32_t*, size_t> getHyphenString(EndHyphenEdit hyph) {
+    switch (hyph) {
+        case EndHyphenEdit::REPLACE_WITH_HYPHEN:  // fall through
+        case EndHyphenEdit::INSERT_HYPHEN:
+            return MAKE_HYPHEN_STR(HYPHEN_STR_HYPHEN);
+        case EndHyphenEdit::INSERT_ARMENIAN_HYPHEN:
+            return MAKE_HYPHEN_STR(HYPHEN_STR_ARMENIAN_HYPHEN);
+        case EndHyphenEdit::INSERT_MAQAF:
+            return MAKE_HYPHEN_STR(HYPHEN_STR_MAQAF);
+        case EndHyphenEdit::INSERT_UCAS_HYPHEN:
+            return MAKE_HYPHEN_STR(HYPHEN_STR_UCAS_HYPHEN);
+        case EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN:
+            return MAKE_HYPHEN_STR(HYPHEN_STR_ZWJ_AND_HYPHEN);
+        case EndHyphenEdit::NO_EDIT:
+        default:
+            return EMPTY_HYPHEN_STR;
+    }
+}
+#undef MAKE_HYPHEN_STR
+
+EndHyphenEdit editForThisLine(HyphenationType type);
+StartHyphenEdit editForNextLine(HyphenationType type);
+
 // hyb file header; implementation details are in the .cpp file
 struct Header;
 
@@ -121,10 +176,19 @@
     // the vector is a "hyphenation type" for a potential hyphenation that can be applied at the
     // corresponding code unit offset in the word.
     //
+    // out must have at least the length of the word capacity.
+    //
     // Example: word is "hyphen", result is the following, corresponding to "hy-phen":
     // [DONT_BREAK, DONT_BREAK, BREAK_AND_INSERT_HYPHEN, DONT_BREAK, DONT_BREAK, DONT_BREAK]
-    void hyphenate(std::vector<HyphenationType>* result, const uint16_t* word, size_t len,
-            const icu::Locale& locale);
+    void hyphenate(const U16StringPiece& word, HyphenationType* out) const;
+
+    // Compute the hyphenation of a word.
+    //
+    // out will be resized to word length.
+    void hyphenate(const U16StringPiece& word, std::vector<HyphenationType>* out) const {
+        out->resize(word.size());
+        return hyphenate(word, out->data());
+    }
 
     // Returns true if the codepoint is like U+2010 HYPHEN in line breaking and usage: a character
     // immediately after which line breaks are allowed, but words containing it should not be
@@ -135,39 +199,50 @@
     // the caller is responsible for ensuring that the lifetime of the pattern data is
     // at least as long as the Hyphenator object.
 
+    // This class doesn't copy or take ownership of patternData. Caller must keep the data valid
+    // until this instance is deleted.
     // Note: nullptr is valid input, in which case the hyphenator only processes soft hyphens.
-    static Hyphenator* loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix);
+    static Hyphenator* loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+                                  const std::string& locale);
 
 private:
+    enum class HyphenationLocale : uint8_t {
+        OTHER = 0,
+        CATALAN = 1,
+        POLISH = 2,
+        SLOVENIAN = 3,
+    };
+
+    // Use Hyphenator::loadBinary instead.
+    Hyphenator(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+               HyphenationLocale hyphenLocale);
+
     // apply various hyphenation rules including hard and soft hyphens, ignoring patterns
-    void hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
-            const icu::Locale& locale);
+    void hyphenateWithNoPatterns(const U16StringPiece& word, HyphenationType* out) const;
 
     // Try looking up word in alphabet table, return DONT_BREAK if any code units fail to map.
     // Otherwise, returns BREAK_AND_INSERT_HYPHEN, BREAK_AND_INSERT_ARMENIAN_HYPHEN, or
     // BREAK_AND_DONT_INSERT_HYPHEN based on the the script of the characters seen.
     // Note that this method writes len+2 entries into alpha_codes (including start and stop)
-    HyphenationType alphabetLookup(uint16_t* alpha_codes, const uint16_t* word, size_t len);
+    HyphenationType alphabetLookup(uint16_t* alpha_codes, const U16StringPiece& word) const;
 
     // calculate hyphenation from patterns, assuming alphabet lookup has already been done
-    void hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
-            HyphenationType hyphenValue);
+    void hyphenateFromCodes(const uint16_t* codes, size_t len, HyphenationType hyphenValue,
+                            HyphenationType* out) const;
 
     // See also LONGEST_HYPHENATED_WORD in LineBreaker.cpp. Here the constant is used so
     // that temporary buffers can be stack-allocated without waste, which is a slightly
     // different use case. It measures UTF-16 code units.
     static const size_t MAX_HYPHENATED_SIZE = 64;
 
-    const uint8_t* patternData;
-    size_t minPrefix, minSuffix;
+    const uint8_t* mPatternData;
+    const size_t mMinPrefix, mMinSuffix;
+    const HyphenationLocale mHyphenationLocale;
 
     // accessors for binary data
-    const Header* getHeader() const {
-        return reinterpret_cast<const Header*>(patternData);
-    }
-
+    const Header* getHeader() const { return reinterpret_cast<const Header*>(mPatternData); }
 };
 
 }  // namespace minikin
 
-#endif   // MINIKIN_HYPHENATOR_H
+#endif  // MINIKIN_HYPHENATOR_H
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index 6d1de2f..ba32d98 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -17,15 +17,22 @@
 #ifndef MINIKIN_LAYOUT_H
 #define MINIKIN_LAYOUT_H
 
-#include <hb.h>
-
 #include <memory>
+#include <unordered_map>
 #include <vector>
 
-#include <minikin/FontCollection.h>
+#include <gtest/gtest_prod.h>
+#include <utils/JenkinsHash.h>
+
+#include "minikin/FontCollection.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
 
 namespace minikin {
 
+class Layout;
+struct LayoutPieces;
+
 struct LayoutGlyph {
     // index into mFaces and mHbFonts vectors. We could imagine
     // moving this into a run length representation, because it's
@@ -39,45 +46,64 @@
     float y;
 };
 
-// Internal state used during layout operation
-struct LayoutContext;
-
-enum {
-    kBidi_LTR = 0,
-    kBidi_RTL = 1,
-    kBidi_Default_LTR = 2,
-    kBidi_Default_RTL = 3,
-    kBidi_Force_LTR = 4,
-    kBidi_Force_RTL = 5,
-
-    kBidi_Mask = 0x7
+// Must be the same value with Paint.java
+enum class Bidi : uint8_t {
+    LTR = 0b0000,          // Must be same with Paint.BIDI_LTR
+    RTL = 0b0001,          // Must be same with Paint.BIDI_RTL
+    DEFAULT_LTR = 0b0010,  // Must be same with Paint.BIDI_DEFAULT_LTR
+    DEFAULT_RTL = 0b0011,  // Must be same with Paint.BIDI_DEFAULT_RTL
+    FORCE_LTR = 0b0100,    // Must be same with Paint.BIDI_FORCE_LTR
+    FORCE_RTL = 0b0101,    // Must be same with Paint.BIDI_FORCE_RTL
 };
 
+inline bool isRtl(Bidi bidi) {
+    return static_cast<uint8_t>(bidi) & 0b0001;
+}
+inline bool isOverride(Bidi bidi) {
+    return static_cast<uint8_t>(bidi) & 0b0100;
+}
+
 // Lifecycle and threading assumptions for Layout:
 // The object is assumed to be owned by a single thread; multiple threads
 // may not mutate it at the same time.
 class Layout {
 public:
-
-    Layout() : mGlyphs(), mAdvances(), mFaces(), mAdvance(0), mBounds() {
+    Layout()
+            : mGlyphs(),
+              mAdvances(),
+              mExtents(),
+              mFaces(),
+              mAdvance(0),
+              mBounds() {
         mBounds.setEmpty();
     }
 
     Layout(Layout&& layout) = default;
 
-    // Forbid copying and assignment.
-    Layout(const Layout&) = delete;
-    void operator=(const Layout&) = delete;
+    Layout(const Layout&) = default;
+    Layout& operator=(const Layout&) = default;
 
     void dump() const;
 
-    void doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
-        const std::shared_ptr<FontCollection>& collection);
+    void doLayout(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+                  const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen);
 
-    static float measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
-        const std::shared_ptr<FontCollection>& collection, float* advances);
+    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);
+
+    static float measureText(const U16StringPiece& str, const Range& range, Bidi bidiFlags,
+                             const MinikinPaint& paint, StartHyphenEdit startHyphen,
+                             EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
+                             LayoutPieces* pieces);
+
+    inline const std::vector<float>& advances() const { return mAdvances; }
 
     // public accessors
     size_t nGlyphs() const;
@@ -91,22 +117,42 @@
 
     // 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);
+    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
     float getCharAdvance(size_t i) const { return mAdvances[i]; }
 
     void getBounds(MinikinRect* rect) const;
+    const MinikinRect& getBounds() const { return mBounds; }
 
     // Purge all caches, useful in low memory conditions
     static void purgeCaches();
 
+    // 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);
+
 private:
     friend class LayoutCacheKey;
+    friend class LayoutCache;
 
-    // Find a face in the mFaces vector, or create a new entry
-    int findFace(const FakedFont& face, LayoutContext* ctx);
+    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();
@@ -114,24 +160,31 @@
     // Lay out a single bidi run
     // When layout is not null, layout info will be stored in the object.
     // When advances is not null, measurement results will be stored in the array.
-    static float doLayoutRunCached(const uint16_t* buf, size_t runStart, size_t runLength,
-        size_t bufSize, bool isRtl, LayoutContext* ctx, size_t dstStart,
-        const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
+    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);
 
     // Lay out a single word
     static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        bool isRtl, LayoutContext* ctx, size_t bufStart,
-        const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances);
+                              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);
 
     // Lay out a single bidi run
-    void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection);
-
-    // Append another layout (for example, cached value) into this one
-    void appendLayout(Layout* src, size_t start, float extraAdvance);
+    void doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize, bool isRtl,
+                     const MinikinPaint& paint, StartHyphenEdit startHyphen,
+                     EndHyphenEdit endHyphen);
 
     std::vector<LayoutGlyph> mGlyphs;
+
+    // The following three vectors are 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;
diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
new file mode 100644
index 0000000..e99fbe4
--- /dev/null
+++ b/include/minikin/LayoutCache.h
@@ -0,0 +1,214 @@
+/*
+ * 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_CACHE_H
+#define MINIKIN_LAYOUT_CACHE_H
+
+#include "minikin/Layout.h"
+
+#include <mutex>
+
+#include <utils/JenkinsHash.h>
+#include <utils/LruCache.h>
+
+namespace minikin {
+
+// Layout cache datatypes
+class LayoutCacheKey {
+public:
+    LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
+                   bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
+            : mChars(text.data()),
+              mNchars(text.size()),
+              mStart(range.getStart()),
+              mCount(range.getLength()),
+              mId(paint.font->getId()),
+              mStyle(paint.fontStyle),
+              mSize(paint.size),
+              mScaleX(paint.scaleX),
+              mSkewX(paint.skewX),
+              mLetterSpacing(paint.letterSpacing),
+              mWordSpacing(paint.wordSpacing),
+              mPaintFlags(paint.paintFlags),
+              mLocaleListId(paint.localeListId),
+              mFamilyVariant(paint.familyVariant),
+              mStartHyphen(startHyphen),
+              mEndHyphen(endHyphen),
+              mIsRtl(dir),
+              mHash(computeHash()) {}
+
+    bool operator==(const LayoutCacheKey& o) const {
+        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 &&
+               mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
+               mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
+               !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
+    }
+
+    android::hash_t hash() const { return mHash; }
+
+    void copyText() {
+        uint16_t* charsCopy = new uint16_t[mNchars];
+        memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
+        mChars = charsCopy;
+    }
+    void freeText() {
+        delete[] mChars;
+        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:
+    const uint16_t* mChars;
+    size_t mNchars;
+    size_t mStart;
+    size_t mCount;
+    uint32_t mId;  // for the font collection
+    FontStyle mStyle;
+    float mSize;
+    float mScaleX;
+    float mSkewX;
+    float mLetterSpacing;
+    float mWordSpacing;
+    int32_t mPaintFlags;
+    uint32_t mLocaleListId;
+    FontFamily::Variant mFamilyVariant;
+    StartHyphenEdit mStartHyphen;
+    EndHyphenEdit mEndHyphen;
+    bool mIsRtl;
+    // Note: any fields added to MinikinPaint must also be reflected here.
+    // TODO: language matching (possibly integrate into style)
+    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);
+    }
+};
+
+class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
+public:
+    void clear() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mCache.clear();
+    }
+
+    // Do not use LayoutCache inside the callback function, otherwise dead-lock may happen.
+    template <typename F>
+    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);
+            return;
+        }
+
+        mRequestCount++;
+        {
+            std::lock_guard<std::mutex> lock(mMutex);
+            Layout* layout = mCache.get(key);
+            if (layout != nullptr) {
+                mCacheHitCount++;
+                f(*layout);
+                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::lock_guard<std::mutex> lock(mMutex);
+            mCache.put(key, layout.release());
+        }
+    }
+
+    void dumpStats(int fd) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        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);
+    }
+
+    static LayoutCache& getInstance() {
+        static LayoutCache cache(kMaxEntries);
+        return cache;
+    }
+
+protected:
+    LayoutCache(uint32_t maxEntries) : mCache(maxEntries), mRequestCount(0), mCacheHitCount(0) {
+        mCache.setOnEntryRemovedListener(this);
+    }
+
+private:
+    // callback for OnEntryRemoved
+    void operator()(LayoutCacheKey& key, Layout*& value) {
+        key.freeText();
+        delete value;
+    }
+
+    android::LruCache<LayoutCacheKey, Layout*> mCache GUARDED_BY(mMutex);
+
+    int32_t mRequestCount;
+    int32_t mCacheHitCount;
+
+    // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
+
+    // TODO: eviction based on memory footprint; for now, we just use a constant
+    // number of strings
+    static const size_t kMaxEntries = 5000;
+
+    std::mutex mMutex;
+};
+
+inline android::hash_t hash_type(const LayoutCacheKey& key) {
+    return key.hash();
+}
+
+}  // namespace minikin
+#endif  // MINIKIN_LAYOUT_CACHE_H
diff --git a/include/minikin/LayoutPieces.h b/include/minikin/LayoutPieces.h
new file mode 100644
index 0000000..f581372
--- /dev/null
+++ b/include/minikin/LayoutPieces.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_LAYOUT_PIECES_H
+#define MINIKIN_LAYOUT_PIECES_H
+
+#include <unordered_map>
+
+#include "minikin/Layout.h"
+#include "minikin/LayoutCache.h"
+
+namespace minikin {
+
+struct LayoutPieces {
+    struct KeyHasher {
+        std::size_t operator()(const LayoutCacheKey& key) const { return key.hash(); }
+    };
+
+    LayoutPieces() {}
+
+    ~LayoutPieces() {
+        for (const auto it : offsetMap) {
+            const_cast<LayoutCacheKey*>(&it.first)->freeText();
+        }
+    }
+
+    std::unordered_map<LayoutCacheKey, Layout, KeyHasher> offsetMap;
+
+    void insert(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+                bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, const Layout& layout) {
+        auto result = offsetMap.emplace(
+                std::piecewise_construct,
+                std::forward_as_tuple(textBuf, range, paint, dir, startEdit, endEdit),
+                std::forward_as_tuple(layout));
+        if (result.second) {
+            const_cast<LayoutCacheKey*>(&result.first->first)->copyText();
+        }
+    }
+
+    template <typename F>
+    void getOrCreate(const U16StringPiece& textBuf, const Range& range, const MinikinPaint& paint,
+                     bool dir, StartHyphenEdit startEdit, EndHyphenEdit endEdit, F& f) const {
+        auto it = offsetMap.find(LayoutCacheKey(textBuf, range, paint, dir, startEdit, endEdit));
+        if (it == offsetMap.end()) {
+            LayoutCache::getInstance().getOrCreate(textBuf, range, paint, dir, startEdit, endEdit,
+                                                   f);
+        } else {
+            f(it->second);
+        }
+    }
+
+    uint32_t getMemoryUsage() const {
+        uint32_t result = 0;
+        for (const auto& i : offsetMap) {
+            result += i.first.getMemoryUsage() + i.second.getMemoryUsage();
+        }
+        return result;
+    }
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LAYOUT_PIECES_H
diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h
index d7a20e9..2974c20 100644
--- a/include/minikin/LineBreaker.h
+++ b/include/minikin/LineBreaker.h
@@ -22,243 +22,107 @@
 #ifndef MINIKIN_LINE_BREAKER_H
 #define MINIKIN_LINE_BREAKER_H
 
-#include <gtest/gtest_prod.h>
-#include "unicode/brkiter.h"
-#include "unicode/locid.h"
-#include <cmath>
+#include <deque>
 #include <vector>
+
 #include "minikin/FontCollection.h"
-#include "minikin/Hyphenator.h"
+#include "minikin/Layout.h"
+#include "minikin/Macros.h"
+#include "minikin/MeasuredText.h"
 #include "minikin/MinikinFont.h"
-#include "minikin/WordBreaker.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
 
 namespace minikin {
 
-enum BreakStrategy {
-    kBreakStrategy_Greedy = 0,
-    kBreakStrategy_HighQuality = 1,
-    kBreakStrategy_Balanced = 2
+enum class BreakStrategy : uint8_t {
+    Greedy = 0,
+    HighQuality = 1,
+    Balanced = 2,
 };
 
-enum HyphenationFrequency {
-    kHyphenationFrequency_None = 0,
-    kHyphenationFrequency_Normal = 1,
-    kHyphenationFrequency_Full = 2
+enum class HyphenationFrequency : uint8_t {
+    None = 0,
+    Normal = 1,
+    Full = 2,
 };
 
-// TODO: want to generalize to be able to handle array of line widths
-class LineWidths {
-    public:
-        void setWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
-            mFirstWidth = firstWidth;
-            mFirstWidthLineCount = firstWidthLineCount;
-            mRestWidth = restWidth;
-        }
-        void setIndents(const std::vector<float>& indents) {
-            mIndents = indents;
-        }
-        bool isConstant() const {
-            // technically mFirstWidthLineCount == 0 would count too, but doesn't actually happen
-            return mRestWidth == mFirstWidth && mIndents.empty();
-        }
-        float getLineWidth(int line) const {
-            float width = (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;
-            if (!mIndents.empty()) {
-                if ((size_t)line < mIndents.size()) {
-                    width -= mIndents[line];
-                } else {
-                    width -= mIndents.back();
-                }
-            }
-            return width;
-        }
-        void clear() {
-            mIndents.clear();
-        }
-    private:
-        float mFirstWidth;
-        int mFirstWidthLineCount;
-        float mRestWidth;
-        std::vector<float> mIndents;
-};
+class Hyphenator;
+class WordBreaker;
 
 class TabStops {
-    public:
-        void set(const int* stops, size_t nStops, int tabWidth) {
-            if (stops != nullptr) {
-                mStops.assign(stops, stops + nStops);
-            } else {
-                mStops.clear();
+public:
+    // Caller must free stops. stops can be nullprt.
+    TabStops(const int32_t* stops, size_t nStops, int32_t tabWidth)
+            : mStops(stops), mStopsSize(nStops), mTabWidth(tabWidth) {}
+
+    float nextTab(float widthSoFar) const {
+        for (size_t i = 0; i < mStopsSize; i++) {
+            if (mStops[i] > widthSoFar) {
+                return mStops[i];
             }
-            mTabWidth = tabWidth;
         }
-        float nextTab(float widthSoFar) const {
-            for (size_t i = 0; i < mStops.size(); i++) {
-                if (mStops[i] > widthSoFar) {
-                    return mStops[i];
-                }
-            }
-            return floor(widthSoFar / mTabWidth + 1) * mTabWidth;
-        }
-    private:
-        std::vector<int> mStops;
-        int mTabWidth;
+        return floor(widthSoFar / mTabWidth + 1) * mTabWidth;
+    }
+
+private:
+    const int32_t* mStops;
+    size_t mStopsSize;
+    int32_t mTabWidth;
 };
 
-class LineBreaker {
-    public:
-        const static int kTab_Shift = 29;  // keep synchronized with TAB_MASK in StaticLayout.java
+// Implement this for the additional information during line breaking.
+// The functions in this class's interface may be called several times. The implementation
+// must return the same value for the same input.
+class LineWidth {
+public:
+    virtual ~LineWidth() {}
 
-        // Note: Locale persists across multiple invocations (it is not cleaned up by finish()),
-        // explicitly to avoid the cost of creating ICU BreakIterator objects. It should always
-        // be set on the first invocation, but callers are encouraged not to call again unless
-        // locale has actually changed.
-        // That logic could be here but it's better for performance that it's upstream because of
-        // the cost of constructing and comparing the ICU Locale object.
-        // Note: caller is responsible for managing lifetime of hyphenator
-        void setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators);
+    // Called to find out the width for the line. This must not return negative values.
+    virtual float getAt(size_t lineNo) const = 0;
 
-        void resize(size_t size) {
-            mTextBuf.resize(size);
-            mCharWidths.resize(size);
-        }
+    // Called to find out the minimum line width. This mut not return negative values.
+    virtual float getMin() const = 0;
 
-        size_t size() const {
-            return mTextBuf.size();
-        }
+    // Called to find out the available left-side padding for the line.
+    virtual float getLeftPaddingAt(size_t lineNo) const = 0;
 
-        uint16_t* buffer() {
-            return mTextBuf.data();
-        }
-
-        float* charWidths() {
-            return mCharWidths.data();
-        }
-
-        // set text to current contents of buffer
-        void setText();
-
-        void setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth);
-
-        void setIndents(const std::vector<float>& indents);
-
-        void setTabStops(const int* stops, size_t nStops, int tabWidth) {
-            mTabStops.set(stops, nStops, tabWidth);
-        }
-
-        BreakStrategy getStrategy() const { return mStrategy; }
-
-        void setStrategy(BreakStrategy strategy) { mStrategy = strategy; }
-
-        void setJustified(bool justified) { mJustified = justified; }
-
-        HyphenationFrequency getHyphenationFrequency() const { return mHyphenationFrequency; }
-
-        void setHyphenationFrequency(HyphenationFrequency frequency) {
-            mHyphenationFrequency = frequency;
-        }
-
-        // TODO: this class is actually fairly close to being general and not tied to using
-        // Minikin to do the shaping of the strings. The main thing that would need to be changed
-        // is having some kind of callback (or virtual class, or maybe even template), which could
-        // easily be instantiated with Minikin's Layout. Future work for when needed.
-        float addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
-                FontStyle style, size_t start, size_t end, bool isRtl);
-
-        void addReplacement(size_t start, size_t end, float width);
-
-        size_t computeBreaks();
-
-        const int* getBreaks() const {
-            return mBreaks.data();
-        }
-
-        const float* getWidths() const {
-            return mWidths.data();
-        }
-
-        const int* getFlags() const {
-            return mFlags.data();
-        }
-
-        void finish();
-
-    private:
-        // ParaWidth is used to hold cumulative width from beginning of paragraph. Note that for
-        // very large paragraphs, accuracy could degrade using only 32-bit float. Note however
-        // that float is used extensively on the Java side for this. This is a typedef so that
-        // we can easily change it based on performance/accuracy tradeoff.
-        typedef double ParaWidth;
-
-        // A single candidate break
-        struct Candidate {
-            size_t offset;  // offset to text buffer, in code units
-            size_t prev;  // index to previous break
-            ParaWidth preBreak;  // width of text until this point, if we decide to not break here
-            ParaWidth postBreak;  // width of text until this point, if we decide to break here
-            float penalty;  // penalty of this break (for example, hyphen penalty)
-            float score;  // best score found for this break
-            size_t lineNumber;  // only updated for non-constant line widths
-            size_t preSpaceCount;  // preceding space count before breaking
-            size_t postSpaceCount;  // preceding space count after breaking
-            HyphenationType hyphenType;
-        };
-
-        float currentLineWidth() const;
-
-        void addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
-                size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph);
-
-        void addCandidate(Candidate cand);
-        void pushGreedyBreak();
-
-        // push an actual break to the output. Takes care of setting flags for tab
-        void pushBreak(int offset, float width, uint8_t hyphenEdit);
-
-        float getSpaceWidth() const;
-
-        void computeBreaksGreedy();
-
-        void computeBreaksOptimal(bool isRectangular);
-
-        void finishBreaksOptimal();
-
-        WordBreaker mWordBreaker;
-        icu::Locale mLocale;
-        std::vector<uint16_t>mTextBuf;
-        std::vector<float>mCharWidths;
-
-        Hyphenator* mHyphenator;
-        std::vector<HyphenationType> mHyphBuf;
-
-        // layout parameters
-        BreakStrategy mStrategy = kBreakStrategy_Greedy;
-        HyphenationFrequency mHyphenationFrequency = kHyphenationFrequency_Normal;
-        bool mJustified;
-        LineWidths mLineWidths;
-        TabStops mTabStops;
-
-        // result of line breaking
-        std::vector<int> mBreaks;
-        std::vector<float> mWidths;
-        std::vector<int> mFlags;
-
-        ParaWidth mWidth = 0;
-        std::vector<Candidate> mCandidates;
-        float mLinePenalty = 0.0f;
-
-        // the following are state for greedy breaker (updated while adding style runs)
-        size_t mLastBreak;
-        size_t mBestBreak;
-        float mBestScore;
-        ParaWidth mPreBreak;  // prebreak of last break
-        uint32_t mLastHyphenation;  // hyphen edit of last break kept for next line
-        int mFirstTabIndex;
-        size_t mSpaceCount;
-
-        FRIEND_TEST(LineBreakerTest, setLocales);
+    // Called to find out the available right-side padding for the line.
+    virtual float getRightPaddingAt(size_t lineNo) const = 0;
 };
 
+struct LineBreakResult {
+public:
+    LineBreakResult() = default;
+
+    // Following five vectors have the same length.
+    // TODO: Introduce individual line info struct if copy cost in JNI is negligible.
+    std::vector<int> breakPoints;
+    std::vector<float> widths;
+    std::vector<float> ascents;
+    std::vector<float> descents;
+    std::vector<int> flags;
+
+    LineBreakResult(LineBreakResult&&) = default;
+    LineBreakResult& operator=(LineBreakResult&&) = default;
+
+    void reverse() {
+        std::reverse(breakPoints.begin(), breakPoints.end());
+        std::reverse(widths.begin(), widths.end());
+        std::reverse(ascents.begin(), ascents.end());
+        std::reverse(descents.begin(), descents.end());
+        std::reverse(flags.begin(), flags.end());
+    }
+
+private:
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(LineBreakResult);
+};
+
+LineBreakResult breakIntoLines(const U16StringPiece& textBuffer, BreakStrategy strategy,
+                               HyphenationFrequency frequency, bool justified,
+                               const MeasuredText& measuredText, const LineWidth& lineWidth,
+                               const TabStops& tabStops);
+
 }  // namespace minikin
 
 #endif  // MINIKIN_LINE_BREAKER_H
diff --git a/include/minikin/LocaleList.h b/include/minikin/LocaleList.h
new file mode 100644
index 0000000..bfc26c7
--- /dev/null
+++ b/include/minikin/LocaleList.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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_LOCALE_H
+#define MINIKIN_LOCALE_H
+
+#include <string>
+
+namespace minikin {
+
+// Looks up a locale list from an internal cache and returns its ID.
+// If the passed locale list is not in the cache, registers it and returns newly assigned ID.
+// TODO: Introduce LocaleId type.
+uint32_t registerLocaleList(const std::string& locales);
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LOCALE_H
diff --git a/include/minikin/Macros.h b/include/minikin/Macros.h
new file mode 100644
index 0000000..be1b713
--- /dev/null
+++ b/include/minikin/Macros.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 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_MACROS_H
+#define MINIKIN_MACROS_H
+
+#define MINIKIN_PREVENT_COPY_AND_ASSIGN(Type) \
+    Type(const Type&) = delete;               \
+    Type& operator=(const Type&) = delete
+
+#define MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(Type) \
+    Type(const Type&) = delete;                    \
+    Type& operator=(const Type&) = delete;         \
+    Type(Type&&) = delete;                         \
+    Type& operator=(Type&&) = delete
+
+// Following thread annotations are partially copied from Abseil thread_annotations.h file.
+// https://github.com/abseil/abseil-cpp/blob/master/absl/base/thread_annotations.h
+
+#if defined(__clang__)
+#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
+#else
+#define THREAD_ANNOTATION_ATTRIBUTE__(x)  // no-op
+#endif
+
+// GUARDED_BY()
+//
+// Documents if a shared field or global variable needs to be protected by a
+// mutex. GUARDED_BY() allows the user to specify a particular mutex that
+// should be held when accessing the annotated variable.
+//
+// Example:
+//
+//   Mutex mu;
+//   int p1 GUARDED_BY(mu);
+#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
+
+// EXCLUSIVE_LOCKS_REQUIRED()
+//
+// Documents a function that expects a mutex to be held prior to entry.
+// The mutex is expected to be held both on entry to, and exit from, the
+// function.
+//
+// Example:
+//
+//   Mutex mu1, mu2;
+//   int a GUARDED_BY(mu1);
+//   int b GUARDED_BY(mu2);
+//
+//   void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... };
+#define EXCLUSIVE_LOCKS_REQUIRED(...) \
+    THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
+
+#endif  // MINIKIN_MACROS_H
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
new file mode 100644
index 0000000..d33a1ca
--- /dev/null
+++ b/include/minikin/MeasuredText.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2017 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_MEASURED_TEXT_H
+#define MINIKIN_MEASURED_TEXT_H
+
+#include <deque>
+#include <vector>
+
+#include "minikin/FontCollection.h"
+#include "minikin/Layout.h"
+#include "minikin/LayoutPieces.h"
+#include "minikin/Macros.h"
+#include "minikin/MinikinFont.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+class Run {
+public:
+    Run(const Range& range) : mRange(range) {}
+    virtual ~Run() {}
+
+    // 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 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 std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+                                                    const LayoutPieces& pieces) const = 0;
+
+    // Following two methods are only called when the implementation returns true for
+    // canHyphenate method.
+
+    // Returns the paint pointer used for this run.
+    // Returns null if canHyphenate 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 */,
+                                     LayoutPieces* /* pieces */) const {
+        return 0.0;
+    }
+
+    inline const Range& getRange() const { return mRange; }
+
+protected:
+    const Range mRange;
+};
+
+class StyleRun : public Run {
+public:
+    StyleRun(const Range& range, MinikinPaint&& paint, bool isRtl)
+            : Run(range), mPaint(std::move(paint)), mIsRtl(isRtl) {}
+
+    bool canHyphenate() 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);
+    }
+
+    std::pair<float, MinikinRect> getBounds(const U16StringPiece& text, const Range& range,
+                                            const LayoutPieces& pieces) const override {
+        Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
+        return Layout::getBoundsWithPrecomputedPieces(text, range, bidiFlag, mPaint, pieces);
+    }
+
+    const MinikinPaint* getPaint() const override { return &mPaint; }
+
+    float measureHyphenPiece(const U16StringPiece& text, const Range& range,
+                             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);
+    }
+
+private:
+    MinikinPaint mPaint;
+    const bool mIsRtl;
+};
+
+class ReplacementRun : public Run {
+public:
+    ReplacementRun(const Range& range, float width, uint32_t localeListId)
+            : Run(range), mWidth(width), mLocaleListId(localeListId) {}
+
+    bool isRtl() const { return false; }
+    bool canHyphenate() 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;
+        // TODO: Get the extents information from the caller.
+    }
+
+    std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+                                            const Range& /* range */,
+                                            const LayoutPieces& /* pieces */) const override {
+        // Bounding Box is not used in replacement run.
+        return std::make_pair(mWidth, MinikinRect());
+    }
+
+private:
+    const float mWidth;
+    const uint32_t mLocaleListId;
+};
+
+// Represents a hyphenation break point.
+struct HyphenBreak {
+    // The break offset.
+    uint32_t offset;
+
+    // The hyphenation type.
+    HyphenationType type;
+
+    // The width of preceding piece after break at hyphenation point.
+    float first;
+
+    // The width of following piece after break at hyphenation point.
+    float second;
+
+    HyphenBreak(uint32_t offset, HyphenationType type, float first, float second)
+            : offset(offset), type(type), first(first), second(second) {}
+};
+
+class MeasuredText {
+public:
+    // 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;
+
+    // The style information.
+    std::vector<std::unique_ptr<Run>> runs;
+
+    // The copied layout pieces for construcing final layouts.
+    // TODO: Stop assigning width/extents if layout pieces are available for reducing memory impact.
+    LayoutPieces layoutPieces;
+
+    uint32_t getMemoryUsage() const {
+        return sizeof(float) * widths.size() + sizeof(MinikinExtent) * extents.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);
+
+    MeasuredText(MeasuredText&&) = default;
+    MeasuredText& operator=(MeasuredText&&) = default;
+
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(MeasuredText);
+
+private:
+    friend class MeasuredTextBuilder;
+
+    void measure(const U16StringPiece& textBuf, bool computeHyphenation, bool computeLayout);
+
+    // 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);
+    }
+};
+
+class MeasuredTextBuilder {
+public:
+    MeasuredTextBuilder() {}
+
+    void addStyleRun(int32_t start, int32_t end, MinikinPaint&& paint, bool isRtl) {
+        mRuns.emplace_back(std::make_unique<StyleRun>(Range(start, end), std::move(paint), isRtl));
+    }
+
+    void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+        mRuns.emplace_back(
+                std::make_unique<ReplacementRun>(Range(start, end), width, localeListId));
+    }
+
+    template <class T, typename... Args>
+    void addCustomRun(Args&&... args) {
+        mRuns.emplace_back(std::make_unique<T>(std::forward<Args>(args)...));
+    }
+
+    std::unique_ptr<MeasuredText> build(const U16StringPiece& textBuf, bool computeHyphenation,
+                                        bool computeLayout) {
+        // 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));
+    }
+
+    MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(MeasuredTextBuilder);
+
+private:
+    std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_MEASURED_TEXT_H
diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h
index b00c212..55c8474 100644
--- a/include/minikin/Measurement.h
+++ b/include/minikin/Measurement.h
@@ -17,15 +17,15 @@
 #ifndef MINIKIN_MEASUREMENT_H
 #define MINIKIN_MEASUREMENT_H
 
-#include <minikin/Layout.h>
+#include <cstdint>
 
 namespace minikin {
 
 float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
-        size_t offset);
+                    size_t offset);
 
 size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
-        float advance);
+                           float advance);
 
 }  // namespace minikin
 
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index 01af786..f57e4f0 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -18,38 +18,64 @@
 #define MINIKIN_FONT_H
 
 #include <string>
-#include <memory>
 
-#include <minikin/FontFamily.h>
-#include <minikin/Hyphenator.h>
+#include "minikin/FontFamily.h"
+#include "minikin/Hyphenator.h"
 
 // An abstraction for platform fonts, allowing Minikin to be used with
 // multiple actual implementations of fonts.
 
 namespace minikin {
 
+class FontCollection;
 class MinikinFont;
 
 // Possibly move into own .h file?
 // Note: if you add a field here, either add it to LayoutCacheKey or to skipCache()
 struct MinikinPaint {
-    MinikinPaint() : font(nullptr), size(0), scaleX(0), skewX(0), letterSpacing(0), wordSpacing(0),
-            paintFlags(0), fakery(), hyphenEdit(), fontFeatureSettings() { }
+    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();
-    }
+    bool skipCache() const { return !fontFeatureSettings.empty(); }
 
-    MinikinFont *font;
     float size;
     float scaleX;
     float skewX;
     float letterSpacing;
     float wordSpacing;
     uint32_t paintFlags;
-    FontFakery fakery;
-    HyphenEdit hyphenEdit;
+    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
@@ -59,10 +85,11 @@
 };
 
 struct MinikinRect {
-    float mLeft, mTop, mRight, mBottom;
-    bool isEmpty() const {
-        return mLeft == mRight || mTop == mBottom;
-    }
+    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;
@@ -75,42 +102,49 @@
         mRight += dx;
         mBottom += dy;
     }
-    void setEmpty() {
-        mLeft = mTop = mRight = mBottom = 0;
-    }
+    void setEmpty() { mLeft = mTop = mRight = mBottom = 0.0; }
     void join(const MinikinRect& r);
 };
 
-// Callback for freeing data
-typedef void (*MinikinDestroyFunc) (void* data);
+// 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);
+    }
+};
 
 class MinikinFont {
 public:
     explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}
 
-    virtual ~MinikinFont();
+    virtual ~MinikinFont() {}
 
-    virtual float GetHorizontalAdvance(uint32_t glyph_id,
-        const MinikinPaint &paint) const = 0;
+    virtual float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
+                                       const FontFakery& fakery) const = 0;
 
-    virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
-        const MinikinPaint &paint) const = 0;
+    virtual void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
+                           const FontFakery& fakery) const = 0;
+
+    virtual void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
+                               const FontFakery& fakery) const = 0;
 
     // Override if font can provide access to raw data
-    virtual const void* GetFontData() const {
-        return nullptr;
-    }
+    virtual const void* GetFontData() const { return nullptr; }
 
     // Override if font can provide access to raw data
-    virtual size_t GetFontSize() const {
-        return 0;
-    }
+    virtual size_t GetFontSize() const { return 0; }
 
     // Override if font can provide access to raw data.
     // Returns index within OpenType collection
-    virtual int GetFontIndex() const {
-        return 0;
-    }
+    virtual int GetFontIndex() const { return 0; }
 
     virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0;
 
@@ -120,11 +154,11 @@
     }
 
     static uint32_t MakeTag(char c1, char c2, char c3, char c4) {
-        return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) |
-            ((uint32_t)c3 << 8) | (uint32_t)c4;
+        return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) | ((uint32_t)c3 << 8) | (uint32_t)c4;
     }
 
     int32_t GetUniqueId() const { return mUniqueId; }
+
 private:
     const int32_t mUniqueId;
 };
diff --git a/include/minikin/Range.h b/include/minikin/Range.h
new file mode 100644
index 0000000..4c7fbfa
--- /dev/null
+++ b/include/minikin/Range.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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_RANGE_H
+#define MINIKIN_RANGE_H
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+
+namespace minikin {
+
+// An undirected range.
+class Range {
+public:
+    static constexpr uint32_t NOWHERE = std::numeric_limits<uint32_t>::max();
+
+    // start must be smaller than or equal to end otherwise the behavior is undefined.
+    Range(uint32_t start, uint32_t end) : mStart(start), mEnd(end) {}
+    Range() : Range(NOWHERE, NOWHERE) {}
+
+    Range(const Range&) = default;
+    Range& operator=(const Range&) = default;
+
+    static Range invalidRange() { return Range(NOWHERE, NOWHERE); }
+    inline bool isValid() const { return mStart != NOWHERE && mEnd != NOWHERE; }
+
+    inline uint32_t getStart() const { return mStart; }       // inclusive
+    inline void setStart(uint32_t start) { mStart = start; }  // inclusive
+
+    inline uint32_t getEnd() const { return mEnd; }   // exclusive
+    inline void setEnd(uint32_t end) { mEnd = end; }  // exclusive
+
+    inline uint32_t getLength() const { return mEnd - mStart; }
+
+    inline bool isEmpty() const { return mStart == mEnd; }
+
+    inline uint32_t toRangeOffset(uint32_t globalPos) const { return globalPos - mStart; }
+    inline uint32_t toGlobalOffset(uint32_t rangePos) const { return mStart + rangePos; }
+
+    // The behavior is undefined if pos is out of range.
+    inline std::pair<Range, Range> split(uint32_t pos) const {
+        return std::make_pair(Range(mStart, pos), Range(pos, mEnd));
+    }
+
+    inline bool contains(const Range& other) const {
+        return mStart <= other.mStart && other.mEnd <= mEnd;
+    }
+
+    // Returns true if the pos is in this range.
+    // For example,
+    //   const Range range(1, 2);  // 1 is inclusive, 2 is exclusive.
+    //   range.contains(0);  // false
+    //   range.contains(1);  // true
+    //   range.contains(2);  // false
+    inline bool contains(uint32_t pos) const { return mStart <= pos && pos < mEnd; }
+
+    // Returns true if left and right intersect.
+    inline static bool intersects(const Range& left, const Range& right) {
+        return left.isValid() && right.isValid() && left.mStart < right.mEnd &&
+               right.mStart < left.mEnd;
+    }
+    inline static Range intersection(const Range& left, const Range& right) {
+        return Range(std::max(left.mStart, right.mStart), std::min(left.mEnd, right.mEnd));
+    }
+
+    // Returns merged range. This method assumes left and right are not invalid ranges and they have
+    // an intersection.
+    static Range merge(const Range& left, const Range& right) {
+        return Range({std::min(left.mStart, right.mStart), std::max(left.mEnd, right.mEnd)});
+    }
+
+    inline bool operator==(const Range& o) const { return mStart == o.mStart && mEnd == o.mEnd; }
+
+    inline bool operator!=(const Range& o) const { return !(*this == o); }
+
+private:
+    // Helper class for "for (uint32_t i : range)" style for-loop.
+    class RangeIterator {
+    public:
+        RangeIterator(uint32_t pos) : mPos(pos) {}
+
+        inline bool operator!=(const RangeIterator& o) const { return o.mPos != mPos; }
+        inline uint32_t operator*() const { return mPos; }
+        inline RangeIterator& operator++() {
+            mPos++;
+            return *this;
+        }
+
+    private:
+        uint32_t mPos;
+    };
+
+public:
+    inline RangeIterator begin() const { return RangeIterator(mStart); }
+    inline RangeIterator end() const { return RangeIterator(mEnd); }
+
+private:
+    uint32_t mStart;
+    uint32_t mEnd;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_RANGE_H
diff --git a/include/minikin/SparseBitSet.h b/include/minikin/SparseBitSet.h
index 62aece2..9ccef12 100644
--- a/include/minikin/SparseBitSet.h
+++ b/include/minikin/SparseBitSet.h
@@ -17,9 +17,8 @@
 #ifndef MINIKIN_SPARSE_BIT_SET_H
 #define MINIKIN_SPARSE_BIT_SET_H
 
-#include <stdint.h>
 #include <sys/types.h>
-
+#include <cstdint>
 #include <memory>
 
 // ---------------------------------------------------------------------------
@@ -49,15 +48,13 @@
     // Determine whether the value is included in the set
     bool get(uint32_t ch) const {
         if (ch >= mMaxVal) return false;
-        const uint32_t *bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];
+        const uint32_t* bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];
         uint32_t index = ch & kPageMask;
         return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) != 0;
     }
 
     // One more than the maximum value in the set, or zero if empty
-    uint32_t length() const {
-        return mMaxVal;
-    }
+    uint32_t length() const { return mMaxVal; }
 
     // The next set bit starting at fromIndex, inclusive, or kNotFound
     // if none exists.
@@ -96,4 +93,4 @@
 
 }  // namespace minikin
 
-#endif // MINIKIN_SPARSE_BIT_SET_H
+#endif  // MINIKIN_SPARSE_BIT_SET_H
diff --git a/include/minikin/U16StringPiece.h b/include/minikin/U16StringPiece.h
new file mode 100644
index 0000000..fe92635
--- /dev/null
+++ b/include/minikin/U16StringPiece.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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_U16STRING_PIECE_H
+#define MINIKIN_U16STRING_PIECE_H
+
+#include <vector>
+
+#include "minikin/Range.h"
+
+namespace minikin {
+
+class U16StringPiece {
+public:
+    U16StringPiece() : mData(nullptr), mLength(0) {}
+    U16StringPiece(const uint16_t* data, uint32_t length) : mData(data), mLength(length) {}
+    U16StringPiece(const std::vector<uint16_t>& v)  // Intentionally not explicit.
+            : mData(v.data()), mLength(static_cast<uint32_t>(v.size())) {}
+    template <uint32_t length>
+    U16StringPiece(uint16_t const (&data)[length]) : mData(data), mLength(length) {}
+
+    U16StringPiece(const U16StringPiece&) = default;
+    U16StringPiece& operator=(const U16StringPiece&) = default;
+
+    inline const uint16_t* data() const { return mData; }
+    inline uint32_t size() const { return mLength; }
+    inline uint32_t length() const { return mLength; }
+
+    // Undefined behavior if pos is out of range.
+    inline const uint16_t& at(uint32_t pos) const { return mData[pos]; }
+    inline const uint16_t& operator[](uint32_t pos) const { return mData[pos]; }
+
+    inline U16StringPiece substr(const Range& range) const {
+        return U16StringPiece(mData + range.getStart(), range.getLength());
+    }
+
+    inline bool hasChar(uint16_t c) const {
+        const uint16_t* end = mData + mLength;
+        return std::find(mData, end, c) != end;
+    }
+
+private:
+    const uint16_t* mData;
+    uint32_t mLength;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_U16STRING_PIECE_H
diff --git a/include/minikin/WordBreaker.h b/include/minikin/WordBreaker.h
deleted file mode 100644
index 6971ce2..0000000
--- a/include/minikin/WordBreaker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * A wrapper around ICU's line break iterator, that gives customized line
- * break opportunities, as well as identifying words for the purpose of
- * hyphenation.
- */
-
-#ifndef MINIKIN_WORD_BREAKER_H
-#define MINIKIN_WORD_BREAKER_H
-
-#include "unicode/brkiter.h"
-#include <memory>
-
-namespace minikin {
-
-class WordBreaker {
-public:
-    ~WordBreaker() {
-        finish();
-    }
-
-    void setLocale(const icu::Locale& locale);
-
-    void setText(const uint16_t* data, size_t size);
-
-    // Advance iterator to next word break. Return offset, or -1 if EOT
-    ssize_t next();
-
-    // Current offset of iterator, equal to 0 at BOT or last return from next()
-    ssize_t current() const;
-
-    // After calling next(), wordStart() and wordEnd() are offsets defining the previous
-    // word. If wordEnd <= wordStart, it's not a word for the purpose of hyphenation.
-    ssize_t wordStart() const;
-
-    ssize_t wordEnd() const;
-
-    int breakBadness() const;
-
-    void finish();
-
-private:
-    int32_t iteratorNext();
-    void detectEmailOrUrl();
-    ssize_t findNextBreakInEmailOrUrl();
-
-    std::unique_ptr<icu::BreakIterator> mBreakIterator;
-    UText mUText = UTEXT_INITIALIZER;
-    const uint16_t* mText = nullptr;
-    size_t mTextSize;
-    ssize_t mLast;
-    ssize_t mCurrent;
-    bool mIteratorWasReset;
-
-    // state for the email address / url detector
-    ssize_t mScanOffset;
-    bool mInEmailOrUrl;
-};
-
-}  // namespace minikin
-
-#endif  // MINIKIN_WORD_BREAKER_H
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index df8ab9a..4793850 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -25,24 +25,29 @@
     srcs: [
         "Hyphenator.cpp",
     ],
+    cflags: ["-Wall", "-Werror"],
     target: {
         android: {
             srcs: [
+                "BidiUtils.cpp",
                 "CmapCoverage.cpp",
                 "Emoji.cpp",
                 "FontCollection.cpp",
                 "FontFamily.cpp",
-                "FontLanguage.cpp",
-                "FontLanguageListCache.cpp",
                 "FontUtils.cpp",
                 "GraphemeBreak.cpp",
-                "HbFontCache.cpp",
+                "GreedyLineBreaker.cpp",
+                "HyphenatorMap.cpp",
                 "Layout.cpp",
                 "LayoutUtils.cpp",
                 "LineBreaker.cpp",
+                "LineBreakerUtil.cpp",
+                "Locale.cpp",
+                "LocaleListCache.cpp",
+                "MeasuredText.cpp",
                 "Measurement.cpp",
                 "MinikinInternal.cpp",
-                "MinikinFont.cpp",
+                "OptimalLineBreaker.cpp",
                 "SparseBitSet.cpp",
                 "WordBreaker.cpp",
             ],
@@ -68,11 +73,12 @@
         "-Werror",
         "-Wall",
         "-Wextra",
+        "-Wthread-safety",
     ],
     product_variables: {
         debuggable: {
-            // Enable race detection on eng and userdebug build.
-            cppflags: ["-DENABLE_RACE_DETECTION"],
+            // Enable assertion on eng and userdebug build.
+            cppflags: ["-DENABLE_ASSERTION"],
         },
     },
     shared_libs: [
diff --git a/libs/minikin/BidiUtils.cpp b/libs/minikin/BidiUtils.cpp
new file mode 100644
index 0000000..c397837
--- /dev/null
+++ b/libs/minikin/BidiUtils.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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 "BidiUtils.h"
+
+#include <algorithm>
+
+#include <unicode/ubidi.h>
+#include <unicode/utf16.h>
+
+#include "minikin/Emoji.h"
+
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) {
+    switch (bidi) {
+        case Bidi::LTR:
+            return 0x00;
+        case Bidi::RTL:
+            return 0x01;
+        case Bidi::DEFAULT_LTR:
+            return UBIDI_DEFAULT_LTR;
+        case Bidi::DEFAULT_RTL:
+            return UBIDI_DEFAULT_RTL;
+        case Bidi::FORCE_LTR:
+        case Bidi::FORCE_RTL:
+            MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel.");
+            return 0x00;
+        default:
+            MINIKIN_NOT_REACHED("Unknown Bidi value.");
+            return 0x00;
+    }
+}
+
+BidiText::RunInfo BidiText::getRunInfoAt(uint32_t runOffset) const {
+    MINIKIN_ASSERT(runOffset < mRunCount, "Out of range access. %d/%d", runOffset, mRunCount);
+    if (mRunCount == 1) {
+        // Single run. No need to iteract with UBiDi.
+        return {mRange, mIsRtl};
+    }
+
+    int32_t startRun = -1;
+    int32_t lengthRun = -1;
+    const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), runOffset, &startRun, &lengthRun);
+    if (startRun == -1 || lengthRun == -1) {
+        ALOGE("invalid visual run");
+        return {Range::invalidRange(), false};
+    }
+    const uint32_t runStart = std::max(static_cast<uint32_t>(startRun), mRange.getStart());
+    const uint32_t runEnd = std::min(static_cast<uint32_t>(startRun + lengthRun), mRange.getEnd());
+    if (runEnd <= runStart) {
+        // skip the empty run.
+        return {Range::invalidRange(), false};
+    }
+    return {Range(runStart, runEnd), (runDir == UBIDI_RTL)};
+}
+
+BidiText::BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags)
+        : mRange(range), mIsRtl(isRtl(bidiFlags)), mRunCount(1 /* by default, single run */) {
+    if (isOverride(bidiFlags)) {
+        // force single run.
+        return;
+    }
+
+    mBidi.reset(ubidi_open());
+    if (!mBidi) {
+        ALOGE("error creating bidi object");
+        return;
+    }
+    UErrorCode status = U_ZERO_ERROR;
+    // Set callbacks to override bidi classes of new emoji
+    ubidi_setClassCallback(mBidi.get(), emojiBidiOverride, nullptr, nullptr, nullptr, &status);
+    if (!U_SUCCESS(status)) {
+        ALOGE("error setting bidi callback function, status = %d", status);
+        return;
+    }
+
+    const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags);
+    ubidi_setPara(mBidi.get(), reinterpret_cast<const UChar*>(textBuf.data()), textBuf.size(),
+                  bidiReq, nullptr, &status);
+    if (!U_SUCCESS(status)) {
+        ALOGE("error calling ubidi_setPara, status = %d", status);
+        return;
+    }
+    // RTL paragraphs get an odd level, while LTR paragraphs get an even level,
+    const bool paraIsRTL = ubidi_getParaLevel(mBidi.get()) & 0x01;
+    const ssize_t rc = ubidi_countRuns(mBidi.get(), &status);
+    if (!U_SUCCESS(status) || rc < 0) {
+        ALOGW("error counting bidi runs, status = %d", status);
+        return;
+    }
+    if (rc == 0) {
+        mIsRtl = paraIsRTL;
+        return;
+    }
+    if (rc == 1) {
+        // If the paragraph is a single run, override the paragraph dirction with the run
+        // (actually the whole text) direction.
+        const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), 0, nullptr, nullptr);
+        mIsRtl = (runDir == UBIDI_RTL);
+        return;
+    }
+    mRunCount = rc;
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/BidiUtils.h b/libs/minikin/BidiUtils.h
new file mode 100644
index 0000000..14678d8
--- /dev/null
+++ b/libs/minikin/BidiUtils.h
@@ -0,0 +1,90 @@
+/*
+ * 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_BIDI_UTILS_H
+#define MINIKIN_BIDI_UTILS_H
+
+#define LOG_TAG "Minikin"
+
+#include "minikin/Layout.h"
+
+#include <memory>
+
+#include <unicode/ubidi.h>
+
+#include "minikin/Macros.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+struct UBiDiDeleter {
+    void operator()(UBiDi* v) { ubidi_close(v); }
+};
+
+using UBiDiUniquePtr = std::unique_ptr<UBiDi, UBiDiDeleter>;
+
+// A helper class for iterating the bidi run transitions.
+class BidiText {
+public:
+    struct RunInfo {
+        Range range;
+        bool isRtl;
+    };
+
+    BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags);
+
+    RunInfo getRunInfoAt(uint32_t runOffset) const;
+
+    class iterator {
+    public:
+        inline bool operator==(const iterator& o) const {
+            return mRunOffset == o.mRunOffset && mParent == o.mParent;
+        }
+
+        inline bool operator!=(const iterator& o) const { return !(*this == o); }
+
+        inline RunInfo operator*() const { return mParent->getRunInfoAt(mRunOffset); }
+
+        inline iterator& operator++() {
+            mRunOffset++;
+            return *this;
+        }
+
+    private:
+        friend class BidiText;
+
+        iterator(const BidiText* parent, uint32_t runOffset)
+                : mParent(parent), mRunOffset(runOffset) {}
+
+        const BidiText* mParent;
+        uint32_t mRunOffset;
+    };
+
+    inline iterator begin() const { return iterator(this, 0); }
+    inline iterator end() const { return iterator(this, mRunCount); }
+
+private:
+    UBiDiUniquePtr mBidi;  // Maybe null for single run.
+    const Range mRange;    // The range in the original buffer. Used for range check.
+    bool mIsRtl;           // The paragraph direction.
+    uint32_t mRunCount;    // The number of the bidi run in this text.
+
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(BidiText);
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_BIDI_UTILS_H
diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp
index 78e7b0d..f6143d3 100644
--- a/libs/minikin/CmapCoverage.cpp
+++ b/libs/minikin/CmapCoverage.cpp
@@ -16,24 +16,17 @@
 
 // Determine coverage of font given its raw "cmap" OpenType table
 
-#define LOG_TAG "Minikin"
+#include "minikin/CmapCoverage.h"
 
 #include <algorithm>
 #include <vector>
-using std::vector;
 
-#include <log/log.h>
+#include "minikin/SparseBitSet.h"
 
-#include <minikin/SparseBitSet.h>
-#include <minikin/CmapCoverage.h>
 #include "MinikinInternal.h"
 
-#include <MinikinInternal.h>
-
 namespace minikin {
 
-constexpr uint32_t U32MAX = std::numeric_limits<uint32_t>::max();
-
 // These could perhaps be optimized to use __builtin_bswap16 and friends.
 static uint32_t readU16(const uint8_t* data, size_t offset) {
     return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);
@@ -41,20 +34,17 @@
 
 static uint32_t readU24(const uint8_t* data, size_t offset) {
     return ((uint32_t)data[offset]) << 16 | ((uint32_t)data[offset + 1]) << 8 |
-        ((uint32_t)data[offset + 2]);
+           ((uint32_t)data[offset + 2]);
 }
 
 static uint32_t readU32(const uint8_t* data, size_t offset) {
     return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
-        ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
+           ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
 }
 
 // The start must be larger than or equal to coverage.back() if coverage is not empty.
 // Returns true if the range is appended. Otherwise returns false as an error.
-static bool addRange(vector<uint32_t> &coverage, uint32_t start, uint32_t end) {
-#ifdef VERBOSE_DEBUG
-    ALOGD("adding range %d-%d\n", start, end);
-#endif
+static bool addRange(std::vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
     if (coverage.empty() || coverage.back() < start) {
         coverage.push_back(start);
         coverage.push_back(end);
@@ -71,39 +61,30 @@
     }
 }
 
-struct Range {
-    uint32_t start;  // inclusive
-    uint32_t end;  // exclusive
-
-    static Range InvalidRange() {
-        return Range({ U32MAX, U32MAX });
+// Returns true if the range is appended. Otherwise returns false as an error.
+static bool addRangeCmap4(std::vector<uint32_t>& coverage, uint32_t start, uint32_t end) {
+    if (!coverage.empty() && coverage.back() > end) {
+        // Reject unordered end code points.
+        return false;
     }
-
-    inline bool isValid() const {
-        return start != U32MAX && end != U32MAX;
+    if (coverage.empty() || coverage.back() < start) {
+        coverage.push_back(start);
+        coverage.push_back(end);
+        return true;
+    } else {
+        coverage.back() = end;
+        return true;
     }
+}
 
-    // Returns true if left and right intersect.
-    inline static bool intersects(const Range& left, const Range& right) {
-        return left.isValid() && right.isValid() &&
-                left.start < right.end && right.start < left.end;
-    }
-
-    // Returns merged range. This method assumes left and right are not invalid ranges and they have
-    // an intersection.
-    static Range merge(const Range& left, const Range& right) {
-        return Range({ std::min(left.start, right.start), std::max(left.end, right.end) });
-    }
-};
-
-// Returns Range from given ranges vector. Returns InvalidRange if i is out of range.
+// Returns Range from given ranges vector. Returns invalidRange if i is out of range.
 static inline Range getRange(const std::vector<uint32_t>& r, size_t i) {
-    return i + 1 < r.size() ? Range({ r[i], r[i + 1] }) : Range::InvalidRange();
+    return i + 1 < r.size() ? Range({r[i], r[i + 1]}) : Range::invalidRange();
 }
 
 // Merge two sorted lists of ranges into one sorted list.
-static std::vector<uint32_t> mergeRanges(
-        const std::vector<uint32_t>& lRanges, const std::vector<uint32_t>& rRanges) {
+static std::vector<uint32_t> mergeRanges(const std::vector<uint32_t>& lRanges,
+                                         const std::vector<uint32_t>& rRanges) {
     std::vector<uint32_t> out;
 
     const size_t lsize = lRanges.size();
@@ -119,7 +100,7 @@
             // No ranges left in rRanges. Just put all remaining ranges in lRanges.
             do {
                 Range r = getRange(lRanges, li);
-                addRange(out, r.start, r.end);  // Input is sorted. Never returns false.
+                addRange(out, r.getStart(), r.getEnd());  // Input is sorted. Never returns false.
                 li += 2;
             } while (li < lsize);
             break;
@@ -127,17 +108,19 @@
             // No ranges left in lRanges. Just put all remaining ranges in rRanges.
             do {
                 Range r = getRange(rRanges, ri);
-                addRange(out, r.start, r.end);  // Input is sorted. Never returns false.
+                addRange(out, r.getStart(), r.getEnd());  // Input is sorted. Never returns false.
                 ri += 2;
             } while (ri < rsize);
             break;
         } else if (!Range::intersects(left, right)) {
             // No intersection. Add smaller range.
-            if (left.start < right.start) {
-                addRange(out, left.start, left.end);  // Input is sorted. Never returns false.
+            if (left.getStart() < right.getStart()) {
+                // Input is sorted. Never returns false.
+                addRange(out, left.getStart(), left.getEnd());
                 li += 2;
             } else {
-                addRange(out, right.start, right.end);  // Input is sorted. Never returns false.
+                // Input is sorted. Never returns false.
+                addRange(out, right.getStart(), right.getEnd());
                 ri += 2;
             }
         } else {
@@ -157,7 +140,8 @@
                     right = getRange(rRanges, ri);
                 }
             }
-            addRange(out, merged.start, merged.end);  // Input is sorted. Never returns false.
+            // Input is sorted. Never returns false.
+            addRange(out, merged.getStart(), merged.getEnd());
         }
     }
 
@@ -165,7 +149,7 @@
 }
 
 // Get the coverage information out of a Format 4 subtable, storing it in the coverage vector
-static bool getCoverageFormat4(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
+static bool getCoverageFormat4(std::vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
     const size_t kSegCountOffset = 6;
     const size_t kEndCountOffset = 14;
     const size_t kHeaderSize = 16;
@@ -189,13 +173,13 @@
         if (rangeOffset == 0) {
             uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));
             if (((end + delta) & 0xffff) > end - start) {
-                if (!addRange(coverage, start, end + 1)) {
+                if (!addRangeCmap4(coverage, start, end + 1)) {
                     return false;
                 }
             } else {
                 for (uint32_t j = start; j < end + 1; j++) {
                     if (((j + delta) & 0xffff) != 0) {
-                        if (!addRange(coverage, j, j + 1)) {
+                        if (!addRangeCmap4(coverage, j, j + 1)) {
                             return false;
                         }
                     }
@@ -203,15 +187,15 @@
             }
         } else {
             for (uint32_t j = start; j < end + 1; j++) {
-                uint32_t actualRangeOffset = kHeaderSize + 6 * segCount + rangeOffset +
-                    (i + j - start) * 2;
+                uint32_t actualRangeOffset =
+                        kHeaderSize + 6 * segCount + rangeOffset + (i + j - start) * 2;
                 if (actualRangeOffset + 2 > size) {
                     // invalid rangeOffset is considered a "warning" by OpenType Sanitizer
                     continue;
                 }
                 uint32_t glyphId = readU16(data, actualRangeOffset);
                 if (glyphId != 0) {
-                    if (!addRange(coverage, j, j + 1)) {
+                    if (!addRangeCmap4(coverage, j, j + 1)) {
                         return false;
                     }
                 }
@@ -222,7 +206,7 @@
 }
 
 // Get the coverage information out of a Format 12 subtable, storing it in the coverage vector
-static bool getCoverageFormat12(vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
+static bool getCoverageFormat12(std::vector<uint32_t>& coverage, const uint8_t* data, size_t size) {
     const size_t kNGroupsOffset = 12;
     const size_t kFirstGroupOffset = 16;
     const size_t kGroupSize = 12;
@@ -303,8 +287,8 @@
 // function assumes code points in both default UVS Table and non-default UVS table are stored in
 // ascending order. This is required by the standard.
 static bool getVSCoverage(std::vector<uint32_t>* out_ranges, const uint8_t* data, size_t size,
-        uint32_t defaultUVSTableOffset, uint32_t nonDefaultUVSTableOffset,
-        const SparseBitSet& baseCoverage) {
+                          uint32_t defaultUVSTableOffset, uint32_t nonDefaultUVSTableOffset,
+                          const SparseBitSet& baseCoverage) {
     // Need to merge supported ranges from default UVS Table and non-default UVS Table.
     // First, collect all supported code points from non default UVS table.
     std::vector<uint32_t> rangesFromNonDefaultUVSTable;
@@ -384,7 +368,8 @@
 }
 
 static void getCoverageFormat14(std::vector<std::unique_ptr<SparseBitSet>>* out,
-        const uint8_t* data, size_t size, const SparseBitSet& baseCoverage) {
+                                const uint8_t* data, size_t size,
+                                const SparseBitSet& baseCoverage) {
     constexpr size_t kHeaderSize = 10;
     constexpr size_t kRecordSize = 11;
     constexpr size_t kLengthOffset = 2;
@@ -425,7 +410,7 @@
         }
         std::vector<uint32_t> ranges;
         if (!getVSCoverage(&ranges, data, length, defaultUVSOffset, nonDefaultUVSOffset,
-                baseCoverage)) {
+                           baseCoverage)) {
             continue;
         }
         if (out->size() < vsIndex + 1) {
@@ -438,7 +423,7 @@
 }
 
 SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size,
-        std::vector<std::unique_ptr<SparseBitSet>>* out) {
+                                       std::vector<std::unique_ptr<SparseBitSet>>* out) {
     constexpr size_t kHeaderSize = 4;
     constexpr size_t kNumTablesOffset = 2;
     constexpr size_t kTableSize = 8;
@@ -533,7 +518,7 @@
         const uint8_t* tableData = cmap_data + bestTableOffset;
         const size_t tableSize = cmap_size - bestTableOffset;
         bool success;
-        vector<uint32_t> coverageVec;
+        std::vector<uint32_t> coverageVec;
         if (bestTableFormat == 4) {
             success = getCoverageFormat4(coverageVec, tableData, tableSize);
         } else {
diff --git a/libs/minikin/Emoji.cpp b/libs/minikin/Emoji.cpp
index fbe68ca..ad83189 100644
--- a/libs/minikin/Emoji.cpp
+++ b/libs/minikin/Emoji.cpp
@@ -14,26 +14,23 @@
  * limitations under the License.
  */
 
-#include <minikin/Emoji.h>
+#include "minikin/Emoji.h"
 
 namespace minikin {
 
 bool isNewEmoji(uint32_t c) {
-    // Emoji characters new in Unicode emoji 5.0.
-    // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
-    // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
-    if (c < 0x1F6F7 || c > 0x1F9E6) {
+    // 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) {
         // Optimization for characters outside the new emoji range.
         return false;
     }
-    return (0x1F6F7 <= c && c <= 0x1F6F8)
-            || c == 0x1F91F
-            || (0x1F928 <= c && c <= 0x1F92F)
-            || (0x1F931 <= c && c <= 0x1F932)
-            || c == 0x1F94C
-            || (0x1F95F <= c && c <= 0x1F96B)
-            || (0x1F992 <= c && c <= 0x1F997)
-            || (0x1F9D0 <= c && c <= 0x1F9E6);
+    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);
 }
 
 bool isEmoji(uint32_t c) {
@@ -53,12 +50,10 @@
     if (c == 0x1F91D || c == 0x1F93C) {
         return true;
     }
-    // Emoji Modifier Base characters new in Unicode emoji 5.0.
-    // From http://www.unicode.org/Public/emoji/5.0/emoji-data.txt
-    // TODO: Remove once emoji-data.text 5.0 is in ICU or update to 6.0.
-    if (c == 0x1F91F
-            || (0x1F931 <= c && c <= 0x1F932)
-            || (0x1F9D1 <= c && c <= 0x1F9DD)) {
+    // Emoji Modifier Base 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 ((0x1F9B5 <= c && c <= 0x1F9B6) || (0x1F9B8 <= c && c <= 0x1F9B9)) {
         return true;
     }
     return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);
@@ -74,4 +69,3 @@
 }
 
 }  // namespace minikin
-
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index 9abe84d..3d44ab5 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -14,21 +14,21 @@
  * limitations under the License.
  */
 
-// #define VERBOSE_DEBUG
-
 #define LOG_TAG "Minikin"
 
+#include "minikin/FontCollection.h"
+
 #include <algorithm>
 
 #include <log/log.h>
-#include "unicode/unistr.h"
-#include "unicode/unorm2.h"
+#include <unicode/unistr.h>
+#include <unicode/unorm2.h>
 
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
+#include "minikin/Emoji.h"
+
+#include "Locale.h"
+#include "LocaleListCache.h"
 #include "MinikinInternal.h"
-#include <minikin/Emoji.h>
-#include <minikin/FontCollection.h>
 
 using std::vector;
 
@@ -36,13 +36,13 @@
 
 template <typename T>
 static inline T max(T a, T b) {
-    return a>b ? a : b;
+    return a > b ? a : b;
 }
 
 const uint32_t EMOJI_STYLE_VS = 0xFE0F;
 const uint32_t TEXT_STYLE_VS = 0xFE0E;
 
-uint32_t FontCollection::sNextId = 0;
+static std::atomic<uint32_t> gNextCollectionId = {0};
 
 FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface) : mMaxChar(0) {
     std::vector<std::shared_ptr<FontFamily>> typefaces;
@@ -50,19 +50,14 @@
     init(typefaces);
 }
 
-FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) :
-    mMaxChar(0) {
+FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) : mMaxChar(0) {
     init(typefaces);
 }
 
 void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) {
-    android::AutoMutex _l(gMinikinLock);
-    mId = sNextId++;
+    mId = gNextCollectionId++;
     vector<uint32_t> lastChar;
     size_t nTypefaces = typefaces.size();
-#ifdef VERBOSE_DEBUG
-    ALOGD("nTypefaces = %zd\n", nTypefaces);
-#endif
     const FontStyle defaultStyle;
     for (size_t i = 0; i < nTypefaces; i++) {
         const std::shared_ptr<FontFamily>& family = typefaces[i];
@@ -81,10 +76,9 @@
         mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
     }
     nTypefaces = mFamilies.size();
-    LOG_ALWAYS_FATAL_IF(nTypefaces == 0,
-        "Font collection must have at least one valid typeface");
-    LOG_ALWAYS_FATAL_IF(nTypefaces > 254,
-        "Font collection may only have up to 254 font families.");
+    MINIKIN_ASSERT(nTypefaces > 0, "Font collection must have at least one valid typeface");
+    MINIKIN_ASSERT(nTypefaces <= MAX_FAMILY_COUNT,
+                   "Font collection may only have up to %d font families.", MAX_FAMILY_COUNT);
     size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;
     // TODO: Use variation selector map for mRanges construction.
     // A font can have a glyph for a base code point and variation selector pair but no glyph for
@@ -94,18 +88,12 @@
         Range dummy;
         mRanges.push_back(dummy);
         Range* range = &mRanges.back();
-#ifdef VERBOSE_DEBUG
-        ALOGD("i=%zd: range start = %zd\n", i, offset);
-#endif
         range->start = mFamilyVec.size();
         for (size_t j = 0; j < nTypefaces; j++) {
             if (lastChar[j] < (i + 1) << kLogCharsPerPage) {
                 const std::shared_ptr<FontFamily>& family = mFamilies[j];
                 mFamilyVec.push_back(static_cast<uint8_t>(j));
                 uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage);
-#ifdef VERBOSE_DEBUG
-                ALOGD("nextChar = %d (j = %zd)\n", nextChar, j);
-#endif
                 lastChar[j] = nextChar;
             }
         }
@@ -113,7 +101,7 @@
     }
     // See the comment in Range for more details.
     LOG_ALWAYS_FATAL_IF(mFamilyVec.size() >= 0xFFFF,
-        "Exceeded the maximum indexable cmap coverage.");
+                        "Exceeded the maximum indexable cmap coverage.");
 }
 
 // Special scores for the font fallback.
@@ -123,12 +111,12 @@
 // Calculates a font score.
 // The score of the font family is based on three subscores.
 //  - Coverage Score: How well the font family covers the given character or variation sequence.
-//  - Language Score: How well the font family is appropriate for the language.
+//  - Locale Score: How well the font family is appropriate for the locale.
 //  - Variant Score: Whether the font family matches the variant. Note that this variant is not the
 //    one in BCP47. This is our own font variant (e.g., elegant, compact).
 //
 // Then, there is a priority for these three subscores as follow:
-//   Coverage Score > Language Score > Variant Score
+//   Coverage Score > Locale Score > Variant Score
 // The returned score reflects this priority order.
 //
 // Note that there are two special scores.
@@ -136,22 +124,22 @@
 //    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, int variant, uint32_t langListId,
-        const std::shared_ptr<FontFamily>& fontFamily) const {
-
-    const uint32_t coverageScore = calcCoverageScore(ch, vs, fontFamily);
+uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FontFamily::Variant variant,
+                                         uint32_t localeListId,
+                                         const std::shared_ptr<FontFamily>& fontFamily) const {
+    const uint32_t coverageScore = calcCoverageScore(ch, vs, localeListId, fontFamily);
     if (coverageScore == kFirstFontScore || coverageScore == kUnsupportedFontScore) {
         // No need to calculate other scores.
         return coverageScore;
     }
 
-    const uint32_t languageScore = calcLanguageMatchingScore(langListId, *fontFamily);
+    const uint32_t localeScore = calcLocaleMatchingScore(localeListId, *fontFamily);
     const uint32_t variantScore = calcVariantMatchingScore(variant, *fontFamily);
 
     // Subscores are encoded into 31 bits representation to meet the subscore priority.
-    // The highest 2 bits are for coverage score, then following 28 bits are for language score,
+    // The highest 2 bits are for coverage score, then following 28 bits are for locale score,
     // then the last 1 bit is for variant score.
-    return coverageScore << 29 | languageScore << 1 | variantScore;
+    return coverageScore << 29 | localeScore << 1 | variantScore;
 }
 
 // Calculates a font score based on variation sequence coverage.
@@ -164,8 +152,8 @@
 // - Returns 2 if the vs is a text variation selector (U+FE0E) and if the font is not an emoji font.
 // - Returns 1 if the variation selector is not specified or if the font family only supports the
 //   variation sequence's base character.
-uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs,
-        const std::shared_ptr<FontFamily>& fontFamily) const {
+uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId,
+                                           const std::shared_ptr<FontFamily>& fontFamily) const {
     const bool hasVSGlyph = (vs != 0) && fontFamily->hasGlyph(ch, vs);
     if (!hasVSGlyph && !fontFamily->getCoverage().get(ch)) {
         // The font doesn't support either variation sequence or even the base character.
@@ -178,34 +166,36 @@
         return kFirstFontScore;
     }
 
-    if (vs == 0) {
-        return 1;
-    }
-
-    if (hasVSGlyph) {
+    if (vs != 0 && hasVSGlyph) {
         return 3;
     }
 
-    if (vs == EMOJI_STYLE_VS || vs == TEXT_STYLE_VS) {
-        const FontLanguages& langs = FontLanguageListCache::getById(fontFamily->langId());
-        bool hasEmojiFlag = false;
-        for (size_t i = 0; i < langs.size(); ++i) {
-            if (langs[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
-                hasEmojiFlag = true;
+    bool colorEmojiRequest;
+    if (vs == EMOJI_STYLE_VS) {
+        colorEmojiRequest = true;
+    } else if (vs == TEXT_STYLE_VS) {
+        colorEmojiRequest = false;
+    } else {
+        switch (LocaleListCache::getById(localeListId).getEmojiStyle()) {
+            case EmojiStyle::EMOJI:
+                colorEmojiRequest = true;
                 break;
-            }
-        }
-
-        if (vs == EMOJI_STYLE_VS) {
-            return hasEmojiFlag ? 2 : 1;
-        } else {  // vs == TEXT_STYLE_VS
-            return hasEmojiFlag ? 1 : 2;
+            case EmojiStyle::TEXT:
+                colorEmojiRequest = false;
+                break;
+            case EmojiStyle::EMPTY:
+            case EmojiStyle::DEFAULT:
+            default:
+                // Do not give any extra score for the default emoji style.
+                return 1;
+                break;
         }
     }
-    return 1;
+
+    return colorEmojiRequest == fontFamily->isColorEmojiFamily() ? 2 : 1;
 }
 
-// Calculate font scores based on the script matching, subtag matching and primary langauge matching.
+// Calculate font scores based on the script matching, subtag matching and primary locale matching.
 //
 // 1. If only the font's language matches or there is no matches between requested font and
 //    supported font, then the font obtains a score of 0.
@@ -214,24 +204,24 @@
 // 3. Regarding to two elements matchings, language-and-subtag matching has a score of 4, while
 //    language-and-script obtains a socre of 3 with the same reason above.
 //
-// If two languages in the requested list have the same language score, the font matching with
-// higher priority language gets a higher score. For example, in the case the user requested
-// language list is "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score
-// than the font of "en-Latn".
+// If two locales in the requested list have the same locale score, the font matching with higher
+// priority locale gets a higher score. For example, in the case the user requested locale list is
+// "ja-Jpan,en-Latn". The score of for the font of "ja-Jpan" gets a higher score than the font of
+// "en-Latn".
 //
-// To achieve score calculation with priorities, the language score is determined as follows:
-//   LanguageScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 + s(m - 1)
-// Here, m is the maximum number of languages to be compared, and s(i) is the i-th language's
-// matching score. The possible values of s(i) are 0, 1, 2, 3 and 4.
-uint32_t FontCollection::calcLanguageMatchingScore(
-        uint32_t userLangListId, const FontFamily& fontFamily) {
-    const FontLanguages& langList = FontLanguageListCache::getById(userLangListId);
-    const FontLanguages& fontLanguages = FontLanguageListCache::getById(fontFamily.langId());
+// To achieve score calculation with priorities, the locale score is determined as follows:
+//   LocaleScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 + s(m - 1)
+// Here, m is the maximum number of locales to be compared, and s(i) is the i-th locale's matching
+// score. The possible values of s(i) are 0, 1, 2, 3 and 4.
+uint32_t FontCollection::calcLocaleMatchingScore(uint32_t userLocaleListId,
+                                                 const FontFamily& fontFamily) {
+    const LocaleList& localeList = LocaleListCache::getById(userLocaleListId);
+    const LocaleList& fontLocaleList = LocaleListCache::getById(fontFamily.localeListId());
 
-    const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);
+    const size_t maxCompareNum = std::min(localeList.size(), FONT_LOCALE_LIMIT);
     uint32_t score = 0;
     for (size_t i = 0; i < maxCompareNum; ++i) {
-        score = score * 5u + langList[i].calcScoreFor(fontLanguages);
+        score = score * 5u + localeList[i].calcScoreFor(fontLocaleList);
     }
     return score;
 }
@@ -239,8 +229,20 @@
 // 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(int variant, const FontFamily& fontFamily) {
-    return (fontFamily.variant() == 0 || fontFamily.variant() == variant) ? 1 : 0;
+uint32_t FontCollection::calcVariantMatchingScore(FontFamily::Variant variant,
+                                                  const FontFamily& fontFamily) {
+    const FontFamily::Variant familyVariant = fontFamily.variant();
+    if (familyVariant == FontFamily::Variant::DEFAULT) {
+        return 1;
+    }
+    if (familyVariant == variant) {
+        return 1;
+    }
+    if (variant == FontFamily::Variant::DEFAULT && familyVariant == FontFamily::Variant::COMPACT) {
+        // If default is requested, prefer compat variation.
+        return 1;
+    }
+    return 0;
 }
 
 // Implement heuristic for choosing best-match font. Here are the rules:
@@ -248,8 +250,8 @@
 // 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 langListId, int variant) const {
+const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(
+        uint32_t ch, uint32_t vs, uint32_t localeListId, FontFamily::Variant variant) const {
     if (ch >= mMaxChar) {
         return mFamilies[0];
     }
@@ -257,18 +259,15 @@
     Range range = mRanges[ch >> kLogCharsPerPage];
 
     if (vs != 0) {
-        range = { 0, static_cast<uint16_t>(mFamilies.size()) };
+        range = {0, static_cast<uint16_t>(mFamilies.size())};
     }
 
-#ifdef VERBOSE_DEBUG
-    ALOGD("querying range %zd:%zd\n", range.start, range.end);
-#endif
     int bestFamilyIndex = -1;
     uint32_t bestScore = kUnsupportedFontScore;
     for (size_t i = range.start; i < range.end; i++) {
         const std::shared_ptr<FontFamily>& family =
                 vs == 0 ? mFamilies[mFamilyVec[i]] : mFamilies[i];
-        const uint32_t score = calcFamilyScore(ch, vs, variant, langListId, family);
+        const uint32_t score = calcFamilyScore(ch, vs, variant, localeListId, family);
         if (score == kFirstFontScore) {
             // If the first font family supports the given character or variation sequence, always
             // use it.
@@ -288,7 +287,7 @@
             if (U_SUCCESS(errorCode) && len > 0) {
                 int off = 0;
                 U16_NEXT_UNSAFE(decomposed, off, ch);
-                return getFamilyForChar(ch, vs, langListId, variant);
+                return getFamilyForChar(ch, vs, localeListId, variant);
             }
         }
         return mFamilies[0];
@@ -301,33 +300,27 @@
 // properly by Minikin or HarfBuzz even if the font does not explicitly support them and it's
 // usually meaningless to switch to a different font to display them.
 static bool doesNotNeedFontSupport(uint32_t c) {
-    return c == 0x00AD // SOFT HYPHEN
-            || c == 0x034F // COMBINING GRAPHEME JOINER
-            || c == 0x061C // ARABIC LETTER MARK
-            || (0x200C <= c && c <= 0x200F) // ZERO WIDTH NON-JOINER..RIGHT-TO-LEFT MARK
-            || (0x202A <= c && c <= 0x202E) // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
-            || (0x2066 <= c && c <= 0x2069) // LEFT-TO-RIGHT ISOLATE..POP DIRECTIONAL ISOLATE
-            || c == 0xFEFF // BYTE ORDER MARK
-            || isVariationSelector(c);
+    return c == 0x00AD                      // SOFT HYPHEN
+           || c == 0x034F                   // COMBINING GRAPHEME JOINER
+           || c == 0x061C                   // ARABIC LETTER MARK
+           || (0x200C <= c && c <= 0x200F)  // ZERO WIDTH NON-JOINER..RIGHT-TO-LEFT MARK
+           || (0x202A <= c && c <= 0x202E)  // LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE
+           || (0x2066 <= c && c <= 0x2069)  // LEFT-TO-RIGHT ISOLATE..POP DIRECTIONAL ISOLATE
+           || c == 0xFEFF                   // BYTE ORDER MARK
+           || isVariationSelector(c);
 }
 
 // Characters where we want to continue using existing font run instead of
 // recomputing the best match in the fallback list.
 static const uint32_t stickyWhitelist[] = {
-    '!',
-    ',',
-    '-',
-    '.',
-    ':',
-    ';',
-    '?',
-    0x00A0, // NBSP
-    0x2010, // HYPHEN
-    0x2011, // NB_HYPHEN
-    0x202F, // NNBSP
-    0x2640, // FEMALE_SIGN,
-    0x2642, // MALE_SIGN,
-    0x2695, // STAFF_OF_AESCULAPIUS
+        '!',    ',', '-', '.', ':', ';', '?',
+        0x00A0,  // NBSP
+        0x2010,  // HYPHEN
+        0x2011,  // NB_HYPHEN
+        0x202F,  // NNBSP
+        0x2640,  // FEMALE_SIGN,
+        0x2642,  // MALE_SIGN,
+        0x2695,  // STAFF_OF_AESCULAPIUS
 };
 
 static bool isStickyWhitelisted(uint32_t c) {
@@ -342,7 +335,7 @@
 }
 
 bool FontCollection::hasVariationSelector(uint32_t baseCodepoint,
-        uint32_t variationSelector) const {
+                                          uint32_t variationSelector) const {
     if (!isVariationSelector(variationSelector)) {
         return false;
     }
@@ -357,9 +350,6 @@
         }
     }
 
-    // TODO: We can remove this lock by precomputing color emoji information.
-    android::AutoMutex _l(gMinikinLock);
-
     // Even if there is no cmap format 14 subtable entry for the given sequence, should return true
     // for <char, text presentation selector> case since we have special fallback rule for the
     // sequence. Note that we don't need to restrict this to already standardized variation
@@ -378,10 +368,12 @@
 
 constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD;
 
-void FontCollection::itemize(const uint16_t *string, size_t string_size, FontStyle style,
-        vector<Run>* result) const {
-    const uint32_t langListId = style.getLanguageListId();
-    int variant = style.getVariant();
+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;
+
     const FontFamily* lastFamily = nullptr;
     Run* run = nullptr;
 
@@ -424,7 +416,7 @@
 
         if (!shouldContinueRun) {
             const std::shared_ptr<FontFamily>& family = getFamilyForChar(
-                    ch, isVariationSelector(nextCh) ? nextCh : 0, langListId, variant);
+                    ch, isVariationSelector(nextCh) ? nextCh : 0, localeListId, familyVariant);
             if (utf16Pos == 0 || family.get() != lastFamily) {
                 size_t start = utf16Pos;
                 // Workaround for combining marks and emoji modifiers until we implement
@@ -433,8 +425,8 @@
                 // character to the new run. U+20E3 COMBINING ENCLOSING KEYCAP, used in emoji, is
                 // handled properly by this since it's a combining mark too.
                 if (utf16Pos != 0 &&
-                        (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
-                        family != nullptr && family->getCoverage().get(prevCh)) {
+                    (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&
+                    family != nullptr && family->getCoverage().get(prevCh)) {
                     const size_t prevChLength = U16_LENGTH(prevCh);
                     if (run != nullptr) {
                         run->end -= prevChLength;
@@ -491,7 +483,7 @@
         return nullptr;
     }
 
-    std::vector<std::shared_ptr<FontFamily> > families;
+    std::vector<std::shared_ptr<FontFamily>> families;
     for (const std::shared_ptr<FontFamily>& family : mFamilies) {
         std::shared_ptr<FontFamily> newFamily = family->createFamilyWithVariation(variations);
         if (newFamily) {
diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp
index a93cb4f..b95d858 100644
--- a/libs/minikin/FontFamily.cpp
+++ b/libs/minikin/FontFamily.cpp
@@ -16,113 +16,115 @@
 
 #define LOG_TAG "Minikin"
 
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
+#include "minikin/FontFamily.h"
 
+#include <cstdint>
+#include <vector>
+
+#include <hb-ot.h>
+#include <hb.h>
 #include <log/log.h>
 #include <utils/JenkinsHash.h>
 
-#include <hb.h>
-#include <hb-ot.h>
+#include "minikin/CmapCoverage.h"
+#include "minikin/HbUtils.h"
+#include "minikin/MinikinFont.h"
 
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
 #include "FontUtils.h"
-#include "HbFontCache.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
 #include "MinikinInternal.h"
-#include <minikin/CmapCoverage.h>
-#include <minikin/MinikinFont.h>
-#include <minikin/FontFamily.h>
-#include <minikin/MinikinFont.h>
-
-using std::vector;
 
 namespace minikin {
 
-FontStyle::FontStyle(int variant, int weight, bool italic)
-        : FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {
-}
-
-FontStyle::FontStyle(uint32_t languageListId, int variant, int weight, bool italic)
-        : bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {
-}
-
-android::hash_t FontStyle::hash() const {
-    uint32_t hash = android::JenkinsHashMix(0, bits);
-    hash = android::JenkinsHashMix(hash, mLanguageListId);
-    return android::JenkinsHashWhiten(hash);
-}
-
-// static
-uint32_t FontStyle::registerLanguageList(const std::string& languages) {
-    android::AutoMutex _l(gMinikinLock);
-    return FontLanguageListCache::getId(languages);
-}
-
-// static
-uint32_t FontStyle::pack(int variant, int weight, bool italic) {
-    return (weight & kWeightMask) | (italic ? kItalicMask : 0) | (variant << kVariantShift);
-}
-
-Font::Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style)
-    : typeface(typeface), style(style) {
-}
-
-Font::Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style)
-    : typeface(typeface), style(style) {
-}
-
-std::unordered_set<AxisTag> Font::getSupportedAxesLocked() const {
-    const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');
-    HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));
-    if (fvarTable.size() == 0) {
-        return std::unordered_set<AxisTag>();
+Font Font::Builder::build() {
+    if (mIsWeightSet && mIsSlantSet) {
+        // No need to read OS/2 header of the font file.
+        return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), prepareFont(mTypeface));
     }
 
+    HbFontUniquePtr font = prepareFont(mTypeface);
+    FontStyle styleFromFont = analyzeStyle(font);
+    if (!mIsWeightSet) {
+        mWeight = styleFromFont.weight();
+    }
+    if (!mIsSlantSet) {
+        mSlant = styleFromFont.slant();
+    }
+    return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), std::move(font));
+}
+
+// static
+HbFontUniquePtr Font::prepareFont(const std::shared_ptr<MinikinFont>& typeface) {
+    const char* buf = reinterpret_cast<const char*>(typeface->GetFontData());
+    size_t size = typeface->GetFontSize();
+    uint32_t ttcIndex = typeface->GetFontIndex();
+
+    HbBlobUniquePtr blob(hb_blob_create(buf, size, HB_MEMORY_MODE_READONLY, nullptr, nullptr));
+    HbFaceUniquePtr face(hb_face_create(blob.get(), ttcIndex));
+    HbFontUniquePtr parent(hb_font_create(face.get()));
+    hb_ot_font_set_funcs(parent.get());
+
+    uint32_t upem = hb_face_get_upem(face.get());
+    hb_font_set_scale(parent.get(), upem, upem);
+
+    HbFontUniquePtr font(hb_font_create_sub_font(parent.get()));
+    std::vector<hb_variation_t> variations;
+    variations.reserve(typeface->GetAxes().size());
+    for (const FontVariation& variation : typeface->GetAxes()) {
+        variations.push_back({variation.axisTag, variation.value});
+    }
+    hb_font_set_variations(font.get(), variations.data(), variations.size());
+    return font;
+}
+
+// static
+FontStyle Font::analyzeStyle(const HbFontUniquePtr& font) {
+    HbBlob os2Table(font, MinikinFont::MakeTag('O', 'S', '/', '2'));
+    if (!os2Table) {
+        return FontStyle();
+    }
+
+    int weight;
+    bool italic;
+    if (!::minikin::analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) {
+        return FontStyle();
+    }
+    // TODO: Update weight/italic based on fvar value.
+    return FontStyle(static_cast<uint16_t>(weight), static_cast<FontStyle::Slant>(italic));
+}
+
+std::unordered_set<AxisTag> Font::getSupportedAxes() const {
+    HbBlob fvarTable(mBaseFont, MinikinFont::MakeTag('f', 'v', 'a', 'r'));
+    if (!fvarTable) {
+        return std::unordered_set<AxisTag>();
+    }
     std::unordered_set<AxisTag> supportedAxes;
     analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);
     return supportedAxes;
 }
 
-Font::Font(Font&& o) {
-    typeface = std::move(o.typeface);
-    style = o.style;
-    o.typeface = nullptr;
-}
+FontFamily::FontFamily(std::vector<Font>&& fonts)
+        : FontFamily(Variant::DEFAULT, std::move(fonts)) {}
 
-Font::Font(const Font& o) {
-    typeface = o.typeface;
-    style = o.style;
-}
+FontFamily::FontFamily(Variant variant, std::vector<Font>&& fonts)
+        : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts)) {}
 
-// static
-FontFamily::FontFamily(std::vector<Font>&& fonts) : FontFamily(0 /* variant */, std::move(fonts)) {
-}
-
-FontFamily::FontFamily(int variant, std::vector<Font>&& fonts)
-    : FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts)) {
-}
-
-FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)
-    : mLangId(langId), mVariant(variant), mFonts(std::move(fonts)) {
+FontFamily::FontFamily(uint32_t localeListId, Variant variant, std::vector<Font>&& fonts)
+        : mLocaleListId(localeListId),
+          mVariant(variant),
+          mFonts(std::move(fonts)),
+          mIsColorEmoji(LocaleListCache::getById(localeListId).getEmojiStyle() ==
+                        EmojiStyle::EMOJI) {
+    MINIKIN_ASSERT(!mFonts.empty(), "FontFamily must contain at least one font.");
     computeCoverage();
 }
 
-bool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface, int* weight,
-        bool* italic) {
-    android::AutoMutex _l(gMinikinLock);
-    const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');
-    HbBlob os2Table(getFontTable(typeface.get(), os2Tag));
-    if (os2Table.get() == nullptr) return false;
-    return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight, italic);
-}
-
 // Compute a matching metric between two styles - 0 is an exact match
 static int computeMatch(FontStyle style1, FontStyle style2) {
     if (style1 == style2) return 0;
-    int score = abs(style1.getWeight() - style2.getWeight());
-    if (style1.getItalic() != style2.getItalic()) {
+    int score = abs(style1.weight() / 100 - style2.weight() / 100);
+    if (style1.slant() != style2.slant()) {
         score += 2;
     }
     return score;
@@ -132,53 +134,38 @@
     // If desired weight is semibold or darker, and 2 or more grades
     // higher than actual (for example, medium 500 -> bold 700), then
     // select fake bold.
-    int wantedWeight = wanted.getWeight();
-    bool isFakeBold = wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;
-    bool isFakeItalic = wanted.getItalic() && !actual.getItalic();
+    bool isFakeBold = wanted.weight() >= 600 && (wanted.weight() - actual.weight()) >= 200;
+    bool isFakeItalic = wanted.slant() == FontStyle::Slant::ITALIC &&
+                        actual.slant() == FontStyle::Slant::UPRIGHT;
     return FontFakery(isFakeBold, isFakeItalic);
 }
 
 FakedFont FontFamily::getClosestMatch(FontStyle style) const {
-    const Font* bestFont = nullptr;
-    int bestMatch = 0;
-    for (size_t i = 0; i < mFonts.size(); i++) {
+    const Font* bestFont = &mFonts[0];
+    int bestMatch = computeMatch(bestFont->style(), style);
+    for (size_t i = 1; i < mFonts.size(); i++) {
         const Font& font = mFonts[i];
-        int match = computeMatch(font.style, style);
+        int match = computeMatch(font.style(), style);
         if (i == 0 || match < bestMatch) {
             bestFont = &font;
             bestMatch = match;
         }
     }
-    if (bestFont != nullptr) {
-        return FakedFont{ bestFont->typeface.get(), computeFakery(style, bestFont->style) };
-    }
-    return FakedFont{ nullptr, FontFakery() };
-}
-
-bool FontFamily::isColorEmojiFamily() const {
-    const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);
-    for (size_t i = 0; i < languageList.size(); ++i) {
-        if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {
-            return true;
-        }
-    }
-    return false;
+    return FakedFont{bestFont, computeFakery(style, bestFont->style())};
 }
 
 void FontFamily::computeCoverage() {
-    android::AutoMutex _l(gMinikinLock);
-    const FontStyle defaultStyle;
-    const MinikinFont* typeface = getClosestMatch(defaultStyle).font;
-    const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');
-    HbBlob cmapTable(getFontTable(typeface, cmapTag));
+    const Font* font = getClosestMatch(FontStyle()).font;
+    HbBlob cmapTable(font->baseFont(), MinikinFont::MakeTag('c', 'm', 'a', 'p'));
     if (cmapTable.get() == nullptr) {
         ALOGE("Could not get cmap table size!\n");
         return;
     }
+
     mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mCmapFmt14Coverage);
 
     for (size_t i = 0; i < mFonts.size(); ++i) {
-        std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked();
+        std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxes();
         mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());
     }
 }
@@ -229,8 +216,7 @@
     std::vector<Font> fonts;
     for (const Font& font : mFonts) {
         bool supportedVariations = false;
-        android::AutoMutex _l(gMinikinLock);
-        std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();
+        std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxes();
         if (!supportedAxes.empty()) {
             for (const FontVariation& variation : variations) {
                 if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {
@@ -241,15 +227,15 @@
         }
         std::shared_ptr<MinikinFont> minikinFont;
         if (supportedVariations) {
-            minikinFont = font.typeface->createFontWithVariation(variations);
+            minikinFont = font.typeface()->createFontWithVariation(variations);
         }
         if (minikinFont == nullptr) {
-            minikinFont = font.typeface;
+            minikinFont = font.typeface();
         }
-        fonts.push_back(Font(std::move(minikinFont), font.style));
+        fonts.push_back(Font::Builder(minikinFont).setStyle(font.style()).build());
     }
 
-    return std::shared_ptr<FontFamily>(new FontFamily(mLangId, mVariant, std::move(fonts)));
+    return std::shared_ptr<FontFamily>(new FontFamily(mLocaleListId, mVariant, std::move(fonts)));
 }
 
 }  // namespace minikin
diff --git a/libs/minikin/FontLanguage.cpp b/libs/minikin/FontLanguage.cpp
deleted file mode 100644
index 0897c06..0000000
--- a/libs/minikin/FontLanguage.cpp
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "FontLanguage.h"
-
-#include <algorithm>
-#include <hb.h>
-#include <string.h>
-#include <unicode/uloc.h>
-
-namespace minikin {
-
-#define SCRIPT_TAG(c1, c2, c3, c4) \
-        (((uint32_t)(c1)) << 24 | ((uint32_t)(c2)) << 16 | ((uint32_t)(c3)) <<  8 | \
-         ((uint32_t)(c4)))
-
-// 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) {
-    if (bufLen < subtagLen) {
-        return false;
-    }
-    if (strncmp(buf, subtag, subtagLen) != 0) {
-        return false;  // no match between two strings
-    }
-    return (bufLen == subtagLen || buf[subtagLen] == '\0' ||
-            buf[subtagLen] == '-' || buf[subtagLen] == '_');
-}
-
-// Pack the three letter code into 15 bits and stored to 16 bit integer. The highest bit is 0.
-// For the region code, the letters must be all digits in three letter case, so the number of
-// possible values are 10. For the language code, the letters must be all small alphabets, so the
-// number of possible values are 26. Thus, 5 bits are sufficient for each case and we can pack the
-// three letter language code or region code to 15 bits.
-//
-// In case of two letter code, use fullbit(0x1f) for the first letter instead.
-static uint16_t packLanguageOrRegion(const char* c, size_t length, uint8_t twoLetterBase,
-        uint8_t threeLetterBase) {
-    if (length == 2) {
-        return 0x7c00u |  // 0x1fu << 10
-                (uint16_t)(c[0] - twoLetterBase) << 5 |
-                (uint16_t)(c[1] - twoLetterBase);
-    } else {
-        return ((uint16_t)(c[0] - threeLetterBase) << 10) |
-                (uint16_t)(c[1] - threeLetterBase) << 5 |
-                (uint16_t)(c[2] - threeLetterBase);
-    }
-}
-
-static size_t unpackLanguageOrRegion(uint16_t in, char* out, uint8_t twoLetterBase,
-        uint8_t threeLetterBase) {
-    uint8_t first = (in >> 10) & 0x1f;
-    uint8_t second = (in >> 5) & 0x1f;
-    uint8_t third = in & 0x1f;
-
-    if (first == 0x1f) {
-        out[0] = second + twoLetterBase;
-        out[1] = third + twoLetterBase;
-        return 2;
-    } else {
-        out[0] = first + threeLetterBase;
-        out[1] = second + threeLetterBase;
-        out[2] = third + threeLetterBase;
-        return 3;
-    }
-}
-
-// Find the next '-' or '_' index from startOffset position. If not found, returns bufferLength.
-static size_t nextDelimiterIndex(const char* buffer, size_t bufferLength, size_t startOffset) {
-    for (size_t i = startOffset; i < bufferLength; ++i) {
-        if (buffer[i] == '-' || buffer[i] == '_') {
-            return i;
-        }
-    }
-    return bufferLength;
-}
-
-static inline bool isLowercase(char c) {
-    return 'a' <= c && c <= 'z';
-}
-
-static inline bool isUppercase(char c) {
-    return 'A' <= c && c <= 'Z';
-}
-
-static inline bool isDigit(char c) {
-    return '0' <= c && c <= '9';
-}
-
-// Returns true if the buffer is valid for language code.
-static inline bool isValidLanguageCode(const char* buffer, size_t length) {
-    if (length != 2 && length != 3) return false;
-    if (!isLowercase(buffer[0])) return false;
-    if (!isLowercase(buffer[1])) return false;
-    if (length == 3 && !isLowercase(buffer[2])) return false;
-    return true;
-}
-
-// Returns true if buffer is valid for script code. The length of buffer must be 4.
-static inline bool isValidScriptCode(const char* buffer) {
-    return isUppercase(buffer[0]) && isLowercase(buffer[1]) && isLowercase(buffer[2]) &&
-        isLowercase(buffer[3]);
-}
-
-// Returns true if the buffer is valid for region code.
-static inline bool isValidRegionCode(const char* buffer, size_t length) {
-    return (length == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||
-            (length == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) && isDigit(buffer[2]));
-}
-
-// Parse BCP 47 language identifier into internal structure
-FontLanguage::FontLanguage(const char* buf, size_t length) : FontLanguage() {
-    size_t firstDelimiterPos = nextDelimiterIndex(buf, length, 0);
-    if (isValidLanguageCode(buf, firstDelimiterPos)) {
-        mLanguage = packLanguageOrRegion(buf, firstDelimiterPos, 'a', 'a');
-    } else {
-        // We don't understand anything other than two-letter or three-letter
-        // language codes, so we skip parsing the rest of the string.
-        return;
-    }
-
-    if (firstDelimiterPos == length) {
-        mHbLanguage = hb_language_from_string(getString().c_str(), -1);
-        return;  // Language code only.
-    }
-
-    size_t nextComponentStartPos = firstDelimiterPos + 1;
-    size_t nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
-    size_t componentLength = nextDelimiterPos - nextComponentStartPos;
-
-    if (componentLength == 4) {
-        // Possibly script code.
-        const char* p = buf + nextComponentStartPos;
-        if (isValidScriptCode(p)) {
-            mScript = SCRIPT_TAG(p[0], p[1], p[2], p[3]);
-            mSubScriptBits = scriptToSubScriptBits(mScript);
-        }
-
-        if (nextDelimiterPos == length) {
-            mHbLanguage = hb_language_from_string(getString().c_str(), -1);
-            mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
-            return;  // No region code.
-        }
-
-        nextComponentStartPos = nextDelimiterPos + 1;
-        nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);
-        componentLength = nextDelimiterPos - nextComponentStartPos;
-    }
-
-    if (componentLength == 2 || componentLength == 3) {
-        // Possibly region code.
-        const char* p = buf + nextComponentStartPos;
-        if (isValidRegionCode(p, componentLength)) {
-            mRegion = packLanguageOrRegion(p, componentLength, 'A', '0');
-        }
-    }
-
-    mHbLanguage = hb_language_from_string(getString().c_str(), -1);
-    mEmojiStyle = resolveEmojiStyle(buf, length, mScript);
-}
-
-// static
-FontLanguage::EmojiStyle FontLanguage::resolveEmojiStyle(const char* buf, size_t length,
-        uint32_t script) {
-    // 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;
-    if (length >= kMinSubtagLength) {
-        static const char kPrefix[] = "-u-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 (isEmojiSubtag(pos, remainingLength, "emoji", 5)){
-                return EMSTYLE_EMOJI;
-            } else if (isEmojiSubtag(pos, remainingLength, "text", 4)){
-                return EMSTYLE_TEXT;
-            } else if (isEmojiSubtag(pos, remainingLength, "default", 7)){
-                return EMSTYLE_DEFAULT;
-            }
-        }
-    }
-
-    // If no emoji subtag was provided, resolve the emoji style from script code.
-    if (script == SCRIPT_TAG('Z', 's', 'y', 'e')) {
-        return EMSTYLE_EMOJI;
-    } else if (script == SCRIPT_TAG('Z', 's', 'y', 'm')) {
-        return EMSTYLE_TEXT;
-    }
-
-    return EMSTYLE_EMPTY;
-}
-
-//static
-uint8_t FontLanguage::scriptToSubScriptBits(uint32_t script) {
-    uint8_t subScriptBits = 0u;
-    switch (script) {
-        case SCRIPT_TAG('B', 'o', 'p', 'o'):
-            subScriptBits = kBopomofoFlag;
-            break;
-        case SCRIPT_TAG('H', 'a', 'n', 'g'):
-            subScriptBits = kHangulFlag;
-            break;
-        case SCRIPT_TAG('H', 'a', 'n', 'b'):
-            // Bopomofo is almost exclusively used in Taiwan.
-            subScriptBits = kHanFlag | kBopomofoFlag;
-            break;
-        case SCRIPT_TAG('H', 'a', 'n', 'i'):
-            subScriptBits = kHanFlag;
-            break;
-        case SCRIPT_TAG('H', 'a', 'n', 's'):
-            subScriptBits = kHanFlag | kSimplifiedChineseFlag;
-            break;
-        case SCRIPT_TAG('H', 'a', 'n', 't'):
-            subScriptBits = kHanFlag | kTraditionalChineseFlag;
-            break;
-        case SCRIPT_TAG('H', 'i', 'r', 'a'):
-            subScriptBits = kHiraganaFlag;
-            break;
-        case SCRIPT_TAG('H', 'r', 'k', 't'):
-            subScriptBits = kKatakanaFlag | kHiraganaFlag;
-            break;
-        case SCRIPT_TAG('J', 'p', 'a', 'n'):
-            subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;
-            break;
-        case SCRIPT_TAG('K', 'a', 'n', 'a'):
-            subScriptBits = kKatakanaFlag;
-            break;
-        case SCRIPT_TAG('K', 'o', 'r', 'e'):
-            subScriptBits = kHanFlag | kHangulFlag;
-            break;
-    }
-    return subScriptBits;
-}
-
-std::string FontLanguage::getString() const {
-    if (isUnsupported()) {
-        return "und";
-    }
-    char buf[16];
-    size_t i = unpackLanguageOrRegion(mLanguage, buf, 'a', 'a');
-    if (mScript != 0) {
-        buf[i++] = '-';
-        buf[i++] = (mScript >> 24) & 0xFFu;
-        buf[i++] = (mScript >> 16) & 0xFFu;
-        buf[i++] = (mScript >> 8) & 0xFFu;
-        buf[i++] = mScript & 0xFFu;
-    }
-    if (mRegion != INVALID_CODE) {
-        buf[i++] = '-';
-        i += unpackLanguageOrRegion(mRegion, buf + i, 'A', '0');
-    }
-    return std::string(buf, i);
-}
-
-bool FontLanguage::isEqualScript(const FontLanguage& other) const {
-    return other.mScript == mScript;
-}
-
-// static
-bool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
-    return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
-}
-
-bool FontLanguage::supportsHbScript(hb_script_t script) const {
-    static_assert(SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),
-                  "The Minikin script and HarfBuzz hb_script_t have different encodings.");
-    if (script == mScript) return true;
-    return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));
-}
-
-int FontLanguage::calcScoreFor(const FontLanguages& supported) const {
-    bool languageScriptMatch = false;
-    bool subtagMatch = false;
-    bool scriptMatch = false;
-
-    for (size_t i = 0; i < supported.size(); ++i) {
-        if (mEmojiStyle != EMSTYLE_EMPTY &&
-               mEmojiStyle == supported[i].mEmojiStyle) {
-            subtagMatch = true;
-            if (mLanguage == supported[i].mLanguage) {
-                return 4;
-            }
-        }
-        if (isEqualScript(supported[i]) ||
-                supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
-            scriptMatch = true;
-            if (mLanguage == supported[i].mLanguage) {
-                languageScriptMatch = true;
-            }
-        }
-    }
-
-    if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
-        scriptMatch = true;
-        if (mLanguage == supported[0].mLanguage && supported.isAllTheSameLanguage()) {
-            return 3;
-        }
-    }
-
-    if (languageScriptMatch) {
-        return 3;
-    } else if (subtagMatch) {
-        return 2;
-    } else if (scriptMatch) {
-        return 1;
-    }
-    return 0;
-}
-
-FontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)
-    : mLanguages(std::move(languages)) {
-    if (mLanguages.empty()) {
-        return;
-    }
-
-    const FontLanguage& lang = mLanguages[0];
-
-    mIsAllTheSameLanguage = true;
-    mUnionOfSubScriptBits = lang.mSubScriptBits;
-    for (size_t i = 1; i < mLanguages.size(); ++i) {
-        mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;
-        if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {
-            mIsAllTheSameLanguage = false;
-        }
-    }
-}
-
-#undef SCRIPT_TAG
-}  // namespace minikin
diff --git a/libs/minikin/FontLanguage.h b/libs/minikin/FontLanguage.h
deleted file mode 100644
index 6a50b1d..0000000
--- a/libs/minikin/FontLanguage.h
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MINIKIN_FONT_LANGUAGE_H
-#define MINIKIN_FONT_LANGUAGE_H
-
-#include <string>
-#include <vector>
-
-#include <hb.h>
-
-namespace minikin {
-
-// Due to the limits in font fallback score calculation, we can't use anything more than 12
-// languages.
-const size_t FONT_LANGUAGES_LIMIT = 12;
-
-// The language or region code is encoded to 15 bits.
-const uint16_t INVALID_CODE = 0x7fff;
-
-class FontLanguages;
-
-// FontLanguage is a compact representation of a BCP 47 language tag. It
-// does not capture all possible information, only what directly affects
-// font rendering.
-struct FontLanguage {
-public:
-    enum EmojiStyle : uint8_t {
-        EMSTYLE_EMPTY = 0,
-        EMSTYLE_DEFAULT = 1,
-        EMSTYLE_EMOJI = 2,
-        EMSTYLE_TEXT = 3,
-    };
-    // Default constructor creates the unsupported language.
-    FontLanguage()
-            : mScript(0ul),
-            mLanguage(INVALID_CODE),
-            mRegion(INVALID_CODE),
-            mHbLanguage(HB_LANGUAGE_INVALID),
-            mSubScriptBits(0ul),
-            mEmojiStyle(EMSTYLE_EMPTY) {}
-
-    // Parse from string
-    FontLanguage(const char* buf, size_t length);
-
-    bool operator==(const FontLanguage other) const {
-        return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
-                mRegion == other.mRegion && mEmojiStyle == other.mEmojiStyle;
-    }
-
-    bool operator!=(const FontLanguage other) const {
-        return !(*this == other);
-    }
-
-    bool isUnsupported() const { return mLanguage == INVALID_CODE; }
-    EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
-    hb_language_t getHbLanguage() const { return mHbLanguage; }
-
-
-    bool isEqualScript(const FontLanguage& other) const;
-
-    // Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
-    // ja-Hira doesn't support Jpan.
-    bool supportsHbScript(hb_script_t script) const;
-
-    std::string getString() const;
-
-    // Calculates a matching score. This score represents how well the input languages cover this
-    // language. The maximum score in the language list is returned.
-    // 0 = no match, 1 = script match, 2 = script and primary language match.
-    int calcScoreFor(const FontLanguages& supported) const;
-
-    uint64_t getIdentifier() const {
-        return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) | ((uint64_t)mRegion << 2) |
-                mEmojiStyle;
-    }
-
-private:
-    friend class FontLanguages;  // for FontLanguages constructor
-
-    // ISO 15924 compliant script code. The 4 chars script code are packed into a 32 bit integer.
-    uint32_t mScript;
-
-    // ISO 639-1 or ISO 639-2 compliant language code.
-    // The two- or three-letter language code is packed into a 15 bit integer.
-    // mLanguage = 0 means the FontLanguage is unsupported.
-    uint16_t mLanguage;
-
-    // ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
-    // packed into a 15 bit integer.
-    uint16_t mRegion;
-
-    // The language to be passed HarfBuzz shaper.
-    hb_language_t mHbLanguage;
-
-    // For faster comparing, use 7 bits for specific scripts.
-    static const uint8_t kBopomofoFlag = 1u;
-    static const uint8_t kHanFlag = 1u << 1;
-    static const uint8_t kHangulFlag = 1u << 2;
-    static const uint8_t kHiraganaFlag = 1u << 3;
-    static const uint8_t kKatakanaFlag = 1u << 4;
-    static const uint8_t kSimplifiedChineseFlag = 1u << 5;
-    static const uint8_t kTraditionalChineseFlag = 1u << 6;
-    uint8_t mSubScriptBits;
-
-    EmojiStyle mEmojiStyle;
-
-    static uint8_t scriptToSubScriptBits(uint32_t script);
-
-    static EmojiStyle resolveEmojiStyle(const char* buf, size_t length, uint32_t script);
-
-    // Returns true if the provide subscript bits has the requested subscript bits.
-    // Note that this function returns false if the requested subscript bits are empty.
-    static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
-};
-
-// An immutable list of languages.
-class FontLanguages {
-public:
-    explicit FontLanguages(std::vector<FontLanguage>&& languages);
-    FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}
-    FontLanguages(FontLanguages&&) = default;
-
-    size_t size() const { return mLanguages.size(); }
-    bool empty() const { return mLanguages.empty(); }
-    const FontLanguage& operator[] (size_t n) const { return mLanguages[n]; }
-
-private:
-    friend struct FontLanguage;  // for calcScoreFor
-
-    std::vector<FontLanguage> mLanguages;
-    uint8_t mUnionOfSubScriptBits;
-    bool mIsAllTheSameLanguage;
-
-    uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
-    bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }
-
-    // Do not copy and assign.
-    FontLanguages(const FontLanguages&) = delete;
-    void operator=(const FontLanguages&) = delete;
-};
-
-}  // namespace minikin
-
-#endif  // MINIKIN_FONT_LANGUAGE_H
diff --git a/libs/minikin/FontLanguageListCache.cpp b/libs/minikin/FontLanguageListCache.cpp
deleted file mode 100644
index f1e14f0..0000000
--- a/libs/minikin/FontLanguageListCache.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "FontLanguageListCache.h"
-
-#include <unicode/uloc.h>
-#include <unordered_set>
-
-#include <log/log.h>
-
-#include "FontLanguage.h"
-#include "MinikinInternal.h"
-
-namespace minikin {
-
-const uint32_t FontLanguageListCache::kEmptyListId;
-
-// Returns the text length of output.
-static size_t toLanguageTag(char* output, size_t outSize, const std::string& locale) {
-    output[0] = '\0';
-    if (locale.empty()) {
-        return 0;
-    }
-
-    size_t outLength = 0;
-    UErrorCode uErr = U_ZERO_ERROR;
-    outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);
-    if (U_FAILURE(uErr)) {
-        // unable to build a proper language identifier
-        ALOGD("uloc_canonicalize(\"%s\") failed: %s", locale.c_str(), u_errorName(uErr));
-        output[0] = '\0';
-        return 0;
-    }
-
-    // Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
-    if (strncmp(output, "und", 3) == 0 &&
-        (outLength == 3 || (outLength == 8 && output[3]  == '_'))) {
-        return outLength;
-    }
-
-    char likelyChars[ULOC_FULLNAME_CAPACITY];
-    uErr = U_ZERO_ERROR;
-    uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
-    if (U_FAILURE(uErr)) {
-        // unable to build a proper language identifier
-        ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
-        output[0] = '\0';
-        return 0;
-    }
-
-    uErr = U_ZERO_ERROR;
-    outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
-    if (U_FAILURE(uErr)) {
-        // unable to build a proper language identifier
-        ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
-        output[0] = '\0';
-        return 0;
-    }
-#ifdef VERBOSE_DEBUG
-    ALOGD("ICU normalized '%s' to '%s'", locale.c_str(), output);
-#endif
-    return outLength;
-}
-
-static std::vector<FontLanguage> parseLanguageList(const std::string& input) {
-    std::vector<FontLanguage> result;
-    size_t currentIdx = 0;
-    size_t commaLoc = 0;
-    char langTag[ULOC_FULLNAME_CAPACITY];
-    std::unordered_set<uint64_t> seen;
-    std::string locale(input.size(), 0);
-
-    while ((commaLoc = input.find_first_of(',', currentIdx)) != std::string::npos) {
-        locale.assign(input, currentIdx, commaLoc - currentIdx);
-        currentIdx = commaLoc + 1;
-        size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
-        FontLanguage lang(langTag, length);
-        uint64_t identifier = lang.getIdentifier();
-        if (!lang.isUnsupported() && seen.count(identifier) == 0) {
-            result.push_back(lang);
-            if (result.size() == FONT_LANGUAGES_LIMIT) {
-              break;
-            }
-            seen.insert(identifier);
-        }
-    }
-    if (result.size() < FONT_LANGUAGES_LIMIT) {
-      locale.assign(input, currentIdx, input.size() - currentIdx);
-      size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);
-      FontLanguage lang(langTag, length);
-      uint64_t identifier = lang.getIdentifier();
-      if (!lang.isUnsupported() && seen.count(identifier) == 0) {
-          result.push_back(lang);
-      }
-    }
-    return result;
-}
-
-// static
-uint32_t FontLanguageListCache::getId(const std::string& languages) {
-    FontLanguageListCache* inst = FontLanguageListCache::getInstance();
-    std::unordered_map<std::string, uint32_t>::const_iterator it =
-            inst->mLanguageListLookupTable.find(languages);
-    if (it != inst->mLanguageListLookupTable.end()) {
-        return it->second;
-    }
-
-    // Given language list is not in cache. Insert it and return newly assigned ID.
-    const uint32_t nextId = inst->mLanguageLists.size();
-    FontLanguages fontLanguages(parseLanguageList(languages));
-    if (fontLanguages.empty()) {
-        return kEmptyListId;
-    }
-    inst->mLanguageLists.push_back(std::move(fontLanguages));
-    inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));
-    return nextId;
-}
-
-// static
-const FontLanguages& FontLanguageListCache::getById(uint32_t id) {
-    FontLanguageListCache* inst = FontLanguageListCache::getInstance();
-    LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(), "Lookup by unknown language list ID.");
-    return inst->mLanguageLists[id];
-}
-
-// static
-FontLanguageListCache* FontLanguageListCache::getInstance() {
-    assertMinikinLocked();
-    static FontLanguageListCache* instance = nullptr;
-    if (instance == nullptr) {
-        instance = new FontLanguageListCache();
-
-        // Insert an empty language list for mapping default language list to kEmptyListId.
-        // The default language list has only one FontLanguage and it is the unsupported language.
-        instance->mLanguageLists.push_back(FontLanguages());
-        instance->mLanguageListLookupTable.insert(std::make_pair("", kEmptyListId));
-    }
-    return instance;
-}
-
-}  // namespace minikin
diff --git a/libs/minikin/FontLanguageListCache.h b/libs/minikin/FontLanguageListCache.h
deleted file mode 100644
index 9bf156f..0000000
--- a/libs/minikin/FontLanguageListCache.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
-#define MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
-
-#include <unordered_map>
-
-#include <minikin/FontFamily.h>
-#include "FontLanguage.h"
-
-namespace minikin {
-
-class FontLanguageListCache {
-public:
-    // A special ID for the empty language list.
-    // This value must be 0 since the empty language list is inserted into mLanguageLists by
-    // default.
-    const static uint32_t kEmptyListId = 0;
-
-    // Returns language list ID for the given string representation of FontLanguages.
-    // Caller should acquire a lock before calling the method.
-    static uint32_t getId(const std::string& languages);
-
-    // Caller should acquire a lock before calling the method.
-    static const FontLanguages& getById(uint32_t id);
-
-private:
-    FontLanguageListCache() {}  // Singleton
-    ~FontLanguageListCache() {}
-
-    // Caller should acquire a lock before calling the method.
-    static FontLanguageListCache* getInstance();
-
-    std::vector<FontLanguages> mLanguageLists;
-
-    // A map from string representation of the font language list to the ID.
-    std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;
-};
-
-}  // namespace minikin
-
-#endif  // MINIKIN_FONT_LANGUAGE_LIST_CACHE_H
diff --git a/libs/minikin/FontUtils.cpp b/libs/minikin/FontUtils.cpp
index 60b3163..560b309 100644
--- a/libs/minikin/FontUtils.cpp
+++ b/libs/minikin/FontUtils.cpp
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#include <stdlib.h>
-#include <stdint.h>
-
 #include "FontUtils.h"
 
+#include <cstdint>
+
 #include <log/log.h>
 
 namespace minikin {
@@ -29,7 +28,7 @@
 
 static uint32_t readU32(const uint8_t* data, size_t offset) {
     return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |
-            ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
+           ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);
 }
 
 bool analyzeStyle(const uint8_t* os2_data, size_t os2_size, int* weight, bool* italic) {
@@ -40,7 +39,7 @@
         return false;
     }
     uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);
-    *weight = weightClass / 100;
+    *weight = weightClass;
     uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);
     *italic = (fsSelection & kItalicFlag) != 0;
     return true;
diff --git a/libs/minikin/FontUtils.h b/libs/minikin/FontUtils.h
index ecb9cde..15ee4d0 100644
--- a/libs/minikin/FontUtils.h
+++ b/libs/minikin/FontUtils.h
@@ -17,6 +17,7 @@
 #ifndef MINIKIN_FONT_UTILS_H
 #define MINIKIN_FONT_UTILS_H
 
+#include <cstdint>
 #include <unordered_set>
 
 namespace minikin {
diff --git a/libs/minikin/GraphemeBreak.cpp b/libs/minikin/GraphemeBreak.cpp
index 87de421..7d06d8f 100644
--- a/libs/minikin/GraphemeBreak.cpp
+++ b/libs/minikin/GraphemeBreak.cpp
@@ -14,31 +14,32 @@
  * limitations under the License.
  */
 
-#include <stdint.h>
+#include "minikin/GraphemeBreak.h"
+
 #include <algorithm>
+#include <cstdint>
+
 #include <unicode/uchar.h>
 #include <unicode/utf16.h>
 
-#include <minikin/GraphemeBreak.h>
-#include <minikin/Emoji.h>
-#include "MinikinInternal.h"
+#include "minikin/Emoji.h"
 
 namespace minikin {
 
 int32_t tailoredGraphemeClusterBreak(uint32_t c) {
     // Characters defined as Control that we want to treat them as Extend.
     // These are curated manually.
-    if (c == 0x00AD                         // SHY
-            || c == 0x061C                  // ALM
-            || c == 0x180E                  // MONGOLIAN VOWEL SEPARATOR
-            || c == 0x200B                  // ZWSP
-            || c == 0x200E                  // LRM
-            || c == 0x200F                  // RLM
-            || (0x202A <= c && c <= 0x202E) // LRE, RLE, PDF, LRO, RLO
-            || ((c | 0xF) == 0x206F)        // WJ, invisible math operators, LRI, RLI, FSI, PDI,
-                                            // and the deprecated invisible format controls
-            || c == 0xFEFF                  // BOM
-            || ((c | 0x7F) == 0xE007F))     // recently undeprecated tag characters in Plane 14
+    if (c == 0x00AD                      // SHY
+        || c == 0x061C                   // ALM
+        || c == 0x180E                   // MONGOLIAN VOWEL SEPARATOR
+        || c == 0x200B                   // ZWSP
+        || c == 0x200E                   // LRM
+        || c == 0x200F                   // RLM
+        || (0x202A <= c && c <= 0x202E)  // LRE, RLE, PDF, LRO, RLO
+        || ((c | 0xF) == 0x206F)         // WJ, invisible math operators, LRI, RLI, FSI, PDI,
+                                         // and the deprecated invisible format controls
+        || c == 0xFEFF                   // BOM
+        || ((c | 0x7F) == 0xE007F))      // recently undeprecated tag characters in Plane 14
         return U_GCB_EXTEND;
     // THAI CHARACTER SARA AM is treated as a normal letter by most other implementations: they
     // allow a grapheme break before it.
@@ -51,13 +52,14 @@
 // Returns true for all characters whose IndicSyllabicCategory is Pure_Killer.
 // From http://www.unicode.org/Public/9.0.0/ucd/IndicSyllabicCategory.txt
 bool isPureKiller(uint32_t c) {
-    return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 || c == 0x1734
-            || c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 || c == 0xA806
-            || c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);
+    return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A || c == 0x1714 ||
+            c == 0x1734 || c == 0x17D1 || c == 0x1BAA || c == 0x1BF2 || c == 0x1BF3 ||
+            c == 0xA806 || c == 0xA953 || c == 0xABED || c == 0x11134 || c == 0x112EA ||
+            c == 0x1172B);
 }
 
 bool GraphemeBreak::isGraphemeBreak(const float* advances, const uint16_t* buf, size_t start,
-        size_t count, const size_t offset) {
+                                    size_t count, const size_t offset) {
     // This implementation closely follows Unicode Standard Annex #29 on
     // Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),
     // implementing a tailored version of extended grapheme clusters.
@@ -194,8 +196,8 @@
     // disallow grapheme breaks (if we are here, we don't know about advances, or we already know
     // that c2 has no advance).
     if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9  // virama
-            && !isPureKiller(c1)
-            && u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
+        && !isPureKiller(c1) &&
+        u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {
         return false;
     }
     // Rule GB999, Any ÷ Any
@@ -203,33 +205,33 @@
 }
 
 size_t GraphemeBreak::getTextRunCursor(const float* advances, const uint16_t* buf, size_t start,
-        size_t count, size_t offset, MoveOpt opt) {
+                                       size_t count, size_t offset, MoveOpt opt) {
     switch (opt) {
-    case AFTER:
-        if (offset < start + count) {
-            offset++;
-        }
+        case AFTER:
+            if (offset < start + count) {
+                offset++;
+            }
         // fall through
-    case AT_OR_AFTER:
-        while (!isGraphemeBreak(advances, buf, start, count, offset)) {
-            offset++;
-        }
-        break;
-    case BEFORE:
-        if (offset > start) {
-            offset--;
-        }
+        case AT_OR_AFTER:
+            while (!isGraphemeBreak(advances, buf, start, count, offset)) {
+                offset++;
+            }
+            break;
+        case BEFORE:
+            if (offset > start) {
+                offset--;
+            }
         // fall through
-    case AT_OR_BEFORE:
-        while (!isGraphemeBreak(advances, buf, start, count, offset)) {
-            offset--;
-        }
-        break;
-    case AT:
-        if (!isGraphemeBreak(advances, buf, start, count, offset)) {
-            offset = (size_t)-1;
-        }
-        break;
+        case AT_OR_BEFORE:
+            while (!isGraphemeBreak(advances, buf, start, count, offset)) {
+                offset--;
+            }
+            break;
+        case AT:
+            if (!isGraphemeBreak(advances, buf, start, count, offset)) {
+                offset = (size_t)-1;
+            }
+            break;
     }
     return offset;
 }
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
new file mode 100644
index 0000000..661a42e
--- /dev/null
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2017 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 "GreedyLineBreak"
+
+#include "minikin/Characters.h"
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LineBreakerUtil.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+namespace {
+
+constexpr uint32_t NOWHERE = 0xFFFFFFFF;
+
+class GreedyLineBreaker {
+public:
+    // User of this class must keep measured, lineWidthLimit, tabStop alive until the instance is
+    // destructed.
+    GreedyLineBreaker(const U16StringPiece& textBuf, const MeasuredText& measured,
+                      const LineWidth& lineWidthLimits, const TabStops& tabStops,
+                      bool enableHyphenation)
+            : mLineWidthLimit(lineWidthLimits.getAt(0)),
+              mTextBuf(textBuf),
+              mMeasuredText(measured),
+              mLineWidthLimits(lineWidthLimits),
+              mTabStops(tabStops),
+              mEnableHyphenation(enableHyphenation) {}
+
+    void process();
+
+    LineBreakResult getResult() const;
+
+private:
+    struct BreakPoint {
+        BreakPoint(uint32_t offset, float lineWidth, StartHyphenEdit startHyphen,
+                   EndHyphenEdit endHyphen)
+                : offset(offset),
+                  lineWidth(lineWidth),
+                  hyphenEdit(packHyphenEdit(startHyphen, endHyphen)) {}
+
+        uint32_t offset;
+        float lineWidth;
+        HyphenEdit hyphenEdit;
+    };
+
+    inline uint32_t getPrevLineBreakOffset() {
+        return mBreakPoints.empty() ? 0 : mBreakPoints.back().offset;
+    }
+
+    // Registers the break point and prepares for next line computation.
+    void breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
+                     float remainingNextSumOfCharWidths, EndHyphenEdit thisLineEndHyphen,
+                     StartHyphenEdit nextLineStartHyphen);
+
+    // Update current line width.
+    void updateLineWidth(uint16_t c, float width);
+
+    // Break line if current line exceeds the line limit.
+    void processLineBreak(uint32_t offset, WordBreaker* breaker);
+
+    // Try to break with previous word boundary.
+    // Returns false if unable to break by word boundary.
+    bool tryLineBreakWithWordBreak();
+
+    // Try to break with hyphenation.
+    // Returns false if unable to hyphenate.
+    //
+    // This method keeps hyphenation until the line width after line break meets the line width
+    // limit.
+    bool tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker);
+
+    // Do line break with each characters.
+    //
+    // 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);
+
+    // Info about the line currently processing.
+    uint32_t mLineNum = 0;
+    double mLineWidth = 0;
+    double mSumOfCharWidths = 0;
+    double mLineWidthLimit;
+    StartHyphenEdit mStartHyphenEdit = StartHyphenEdit::NO_EDIT;
+
+    // Previous word break point info.
+    uint32_t mPrevWordBoundsOffset = NOWHERE;
+    double mLineWidthAtPrevWordBoundary = 0;
+    double mSumOfCharWidthsAtPrevWordBoundary = 0;
+    bool mIsPrevWordBreakIsInEmailOrUrl = false;
+
+    // The hyphenator currently used.
+    const Hyphenator* mHyphenator = nullptr;
+
+    // Input parameters.
+    const U16StringPiece& mTextBuf;
+    const MeasuredText& mMeasuredText;
+    const LineWidth& mLineWidthLimits;
+    const TabStops& mTabStops;
+    bool mEnableHyphenation;
+
+    // The result of line breaking.
+    std::vector<BreakPoint> mBreakPoints;
+
+    MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(GreedyLineBreaker);
+};
+
+void GreedyLineBreaker::breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
+                                    float remainingNextSumOfCharWidths,
+                                    EndHyphenEdit thisLineEndHyphen,
+                                    StartHyphenEdit nextLineStartHyphen) {
+    // First, push the break to result.
+    mBreakPoints.emplace_back(offset, lineWidth, mStartHyphenEdit, thisLineEndHyphen);
+
+    // Update the current line info.
+    mLineWidthLimit = mLineWidthLimits.getAt(++mLineNum);
+    mLineWidth = remainingNextLineWidth;
+    mSumOfCharWidths = remainingNextSumOfCharWidths;
+    mStartHyphenEdit = nextLineStartHyphen;
+    mPrevWordBoundsOffset = NOWHERE;
+    mLineWidthAtPrevWordBoundary = 0;
+    mSumOfCharWidthsAtPrevWordBoundary = 0;
+    mIsPrevWordBreakIsInEmailOrUrl = false;
+}
+
+bool GreedyLineBreaker::tryLineBreakWithWordBreak() {
+    if (mPrevWordBoundsOffset == NOWHERE) {
+        return false;  // No word break point before..
+    }
+
+    breakLineAt(mPrevWordBoundsOffset,                            // break offset
+                mLineWidthAtPrevWordBoundary,                     // line width
+                mLineWidth - mSumOfCharWidthsAtPrevWordBoundary,  // remaining next line width
+                // remaining next sum of char widths.
+                mSumOfCharWidths - mSumOfCharWidthsAtPrevWordBoundary, EndHyphenEdit::NO_EDIT,
+                StartHyphenEdit::NO_EDIT);  // No hyphen modification.
+    return true;
+}
+
+bool GreedyLineBreaker::tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker) {
+    if (!mEnableHyphenation || mHyphenator == nullptr) {
+        return false;
+    }
+
+    Run* targetRun = nullptr;
+    for (const auto& run : mMeasuredText.runs) {
+        if (run->getRange().contains(range)) {
+            targetRun = run.get();
+        }
+    }
+
+    if (targetRun == nullptr) {
+        return false;  // The target range may lay on multiple run. Unable to hyphenate.
+    }
+
+    const Range targetRange = breaker->wordRange();
+    if (!range.contains(targetRange)) {
+        return false;
+    }
+
+    const std::vector<HyphenationType> hyphenResult =
+            hyphenate(mTextBuf.substr(targetRange), *mHyphenator);
+    Range contextRange = range;
+    uint32_t prevOffset = NOWHERE;
+    float prevWidth = 0;
+
+    // Look up the hyphenation point from the begining.
+    for (uint32_t i = targetRange.getStart(); i < targetRange.getEnd(); ++i) {
+        const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(i)];
+        if (hyph == HyphenationType::DONT_BREAK) {
+            continue;  // Not a hyphenation point.
+        }
+
+        const float width = targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
+                                                          mStartHyphenEdit, editForThisLine(hyph),
+                                                          nullptr /* advances */, nullptr);
+
+        if (width <= mLineWidthLimit) {
+            // There are still space, remember current offset and look up next hyphenation point.
+            prevOffset = i;
+            prevWidth = width;
+            continue;
+        }
+
+        if (prevOffset == NOWHERE) {
+            // Even with hyphenation, the piece is too long for line. Give up and break in
+            // character bounds.
+            doLineBreakWithGraphemeBounds(contextRange);
+        } else {
+            // Previous offset is the longest hyphenation piece. Break with it.
+            const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
+            const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
+            const float remainingCharWidths = targetRun->measureHyphenPiece(
+                    mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
+                    EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+            breakLineAt(prevOffset, prevWidth,
+                        remainingCharWidths - (mSumOfCharWidths - mLineWidth), remainingCharWidths,
+                        editForThisLine(hyph), nextLineStartHyphenEdit);
+        }
+
+        if (mLineWidth <= mLineWidthLimit) {
+            // The remaining hyphenation piece is less than line width. No more hyphenation is
+            // needed. Go to next word.
+            return true;
+        }
+
+        // Even after line break, the remaining hyphenation piece is still too long for the limit.
+        // Keep hyphenating for the rest.
+        i = getPrevLineBreakOffset();
+        contextRange.setStart(i);  // Update the hyphenation start point.
+        prevOffset = NOWHERE;
+    }
+
+    // Do the same line break at the end of text.
+    // TODO: Remove code duplication. This is the same as in the for loop but extracting function
+    //       may not clear.
+    if (prevOffset == NOWHERE) {
+        doLineBreakWithGraphemeBounds(contextRange);
+    } else {
+        const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
+        const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
+        const float remainingCharWidths = targetRun->measureHyphenPiece(
+                mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
+                EndHyphenEdit::NO_EDIT, nullptr /* advances */, nullptr);
+
+        breakLineAt(prevOffset, prevWidth, remainingCharWidths - (mSumOfCharWidths - mLineWidth),
+                    remainingCharWidths, editForThisLine(hyph), nextLineStartHyphenEdit);
+    }
+
+    return true;
+}
+
+// TODO: Respect trailing line end spaces.
+void 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.
+    for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
+        const float w = mMeasuredText.widths[i];
+        if (w == 0) {
+            continue;  // w == 0 means here is not a grapheme bounds. Don't break here.
+        }
+        if (width + w > mLineWidthLimit) {
+            // Okay, here is the longest position.
+            breakLineAt(i, width, mLineWidth - width, mSumOfCharWidths - width,
+                        EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
+
+            // This method only breaks at the first longest offset, since we may want to hyphenate
+            // the rest of the word.
+            return;
+        } else {
+            width += w;
+        }
+    }
+
+    // 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);
+}
+
+void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
+    if (c == CHAR_TAB) {
+        mSumOfCharWidths = mTabStops.nextTab(mSumOfCharWidths);
+        mLineWidth = mSumOfCharWidths;
+    } else {
+        mSumOfCharWidths += width;
+        if (!isLineEndSpace(c)) {
+            mLineWidth = mSumOfCharWidths;
+        }
+    }
+}
+
+void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker) {
+    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)) {
+            continue;  // TODO: we may be able to return here.
+        } else {
+            doLineBreakWithGraphemeBounds(lineRange);
+        }
+    }
+
+    // There is still spaces, remember current word break point as a candidate and wait next word.
+    const bool isInEmailOrUrl = breaker->breakBadness() != 0;
+    if (mPrevWordBoundsOffset == NOWHERE || mIsPrevWordBreakIsInEmailOrUrl | !isInEmailOrUrl) {
+        mPrevWordBoundsOffset = offset;
+        mLineWidthAtPrevWordBoundary = mLineWidth;
+        mSumOfCharWidthsAtPrevWordBoundary = mSumOfCharWidths;
+        mIsPrevWordBreakIsInEmailOrUrl = isInEmailOrUrl;
+    }
+}
+
+void GreedyLineBreaker::process() {
+    WordBreaker wordBreaker;
+    wordBreaker.setText(mTextBuf.data(), mTextBuf.size());
+
+    // Following two will be initialized after the first iteration.
+    uint32_t localeListId = LocaleListCache::kInvalidListId;
+    uint32_t nextWordBoundaryOffset = 0;
+    for (const auto& run : mMeasuredText.runs) {
+        const Range range = run->getRange();
+
+        // Update locale if necessary.
+        uint32_t newLocaleListId = run->getLocaleListId();
+        if (localeListId != newLocaleListId) {
+            Locale locale = getEffectiveLocale(newLocaleListId);
+            nextWordBoundaryOffset = wordBreaker.followingWithLocale(locale, range.getStart());
+            mHyphenator = HyphenatorMap::lookup(locale);
+            localeListId = newLocaleListId;
+        }
+
+        for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+            updateLineWidth(mTextBuf[i], mMeasuredText.widths[i]);
+
+            if ((i + 1) == nextWordBoundaryOffset) {
+                // Only process line break at word boundary.
+                processLineBreak(i + 1, &wordBreaker);
+                nextWordBoundaryOffset = wordBreaker.next();
+            }
+        }
+    }
+
+    if (getPrevLineBreakOffset() != mTextBuf.size() && mPrevWordBoundsOffset != NOWHERE) {
+        // The remaining words in the last line.
+        breakLineAt(mPrevWordBoundsOffset, mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT,
+                    StartHyphenEdit::NO_EDIT);
+    }
+}
+
+LineBreakResult GreedyLineBreaker::getResult() const {
+    constexpr int TAB_BIT = 1 << 29;  // Must be the same in StaticLayout.java
+
+    LineBreakResult out;
+    uint32_t prevBreakOffset = 0;
+    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]);
+            }
+        }
+
+        out.breakPoints.push_back(breakPoint.offset);
+        out.widths.push_back(breakPoint.lineWidth);
+        out.ascents.push_back(extent.ascent);
+        out.descents.push_back(extent.descent);
+        out.flags.push_back((hasTabChar ? TAB_BIT : 0) | static_cast<int>(breakPoint.hyphenEdit));
+
+        prevBreakOffset = breakPoint.offset;
+    }
+    return out;
+}
+
+}  // namespace
+
+LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
+                                const LineWidth& lineWidthLimits, const TabStops& tabStops,
+                                bool enableHyphenation) {
+    if (textBuf.size() == 0) {
+        return LineBreakResult();
+    }
+    GreedyLineBreaker lineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation);
+    lineBreaker.process();
+    return lineBreaker.getResult();
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/GreedyLineBreaker.h b/libs/minikin/GreedyLineBreaker.h
new file mode 100644
index 0000000..ead93e4
--- /dev/null
+++ b/libs/minikin/GreedyLineBreaker.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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_GREEDY_LINE_BREAKER_H
+#define MINIKIN_GREEDY_LINE_BREAKER_H
+
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+#include "WordBreaker.h"
+
+namespace minikin {
+
+LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
+                                const LineWidth& lineWidthLimits, const TabStops& tabStops,
+                                bool enableHyphenation);
+
+}  // namespace minikin
+
+#endif  // MINIKIN_GREEDY_LINE_BREAKER_H
diff --git a/libs/minikin/HbFontCache.cpp b/libs/minikin/HbFontCache.cpp
deleted file mode 100644
index af3d783..0000000
--- a/libs/minikin/HbFontCache.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "HbFontCache.h"
-
-#include <log/log.h>
-#include <utils/LruCache.h>
-
-#include <hb.h>
-#include <hb-ot.h>
-
-#include <minikin/MinikinFont.h>
-#include "MinikinInternal.h"
-
-namespace minikin {
-
-class HbFontCache : private android::OnEntryRemoved<int32_t, hb_font_t*> {
-public:
-    HbFontCache() : mCache(kMaxEntries) {
-        mCache.setOnEntryRemovedListener(this);
-    }
-
-    // callback for OnEntryRemoved
-    void operator()(int32_t& /* key */, hb_font_t*& value) {
-        hb_font_destroy(value);
-    }
-
-    hb_font_t* get(int32_t fontId) {
-        return mCache.get(fontId);
-    }
-
-    void put(int32_t fontId, hb_font_t* font) {
-        mCache.put(fontId, font);
-    }
-
-    void clear() {
-        mCache.clear();
-    }
-
-    void remove(int32_t fontId) {
-        mCache.remove(fontId);
-    }
-
-private:
-    static const size_t kMaxEntries = 100;
-
-    android::LruCache<int32_t, hb_font_t*> mCache;
-};
-
-HbFontCache* getFontCacheLocked() {
-    assertMinikinLocked();
-    static HbFontCache* cache = nullptr;
-    if (cache == nullptr) {
-        cache = new HbFontCache();
-    }
-    return cache;
-}
-
-void purgeHbFontCacheLocked() {
-    assertMinikinLocked();
-    getFontCacheLocked()->clear();
-}
-
-void purgeHbFontLocked(const MinikinFont* minikinFont) {
-    assertMinikinLocked();
-    const int32_t fontId = minikinFont->GetUniqueId();
-    getFontCacheLocked()->remove(fontId);
-}
-
-// Returns a new reference to a hb_font_t object, caller is
-// responsible for calling hb_font_destroy() on it.
-hb_font_t* getHbFontLocked(const MinikinFont* minikinFont) {
-    assertMinikinLocked();
-    // TODO: get rid of nullFaceFont
-    static hb_font_t* nullFaceFont = nullptr;
-    if (minikinFont == nullptr) {
-        if (nullFaceFont == nullptr) {
-            nullFaceFont = hb_font_create(nullptr);
-        }
-        return hb_font_reference(nullFaceFont);
-    }
-
-    HbFontCache* fontCache = getFontCacheLocked();
-    const int32_t fontId = minikinFont->GetUniqueId();
-    hb_font_t* font = fontCache->get(fontId);
-    if (font != nullptr) {
-        return hb_font_reference(font);
-    }
-
-    hb_face_t* face;
-    const void* buf = minikinFont->GetFontData();
-    size_t size = minikinFont->GetFontSize();
-    hb_blob_t* blob = hb_blob_create(reinterpret_cast<const char*>(buf), size,
-        HB_MEMORY_MODE_READONLY, nullptr, nullptr);
-    face = hb_face_create(blob, minikinFont->GetFontIndex());
-    hb_blob_destroy(blob);
-
-    hb_font_t* parent_font = hb_font_create(face);
-    hb_ot_font_set_funcs(parent_font);
-
-    unsigned int upem = hb_face_get_upem(face);
-    hb_font_set_scale(parent_font, upem, upem);
-
-    font = hb_font_create_sub_font(parent_font);
-    std::vector<hb_variation_t> variations;
-    for (const FontVariation& variation : minikinFont->GetAxes()) {
-        variations.push_back({variation.axisTag, variation.value});
-    }
-    hb_font_set_variations(font, variations.data(), variations.size());
-    hb_font_destroy(parent_font);
-    hb_face_destroy(face);
-    fontCache->put(fontId, font);
-    return hb_font_reference(font);
-}
-
-}  // namespace minikin
diff --git a/libs/minikin/HbFontCache.h b/libs/minikin/HbFontCache.h
deleted file mode 100644
index 59969e2..0000000
--- a/libs/minikin/HbFontCache.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MINIKIN_HBFONT_CACHE_H
-#define MINIKIN_HBFONT_CACHE_H
-
-struct hb_font_t;
-
-namespace minikin {
-class MinikinFont;
-
-void purgeHbFontCacheLocked();
-void purgeHbFontLocked(const MinikinFont* minikinFont);
-hb_font_t* getHbFontLocked(const MinikinFont* minikinFont);
-
-}  // namespace minikin
-#endif  // MINIKIN_HBFONT_CACHE_H
diff --git a/libs/minikin/Hyphenator.cpp b/libs/minikin/Hyphenator.cpp
index 0605b27..c755a87 100644
--- a/libs/minikin/Hyphenator.cpp
+++ b/libs/minikin/Hyphenator.cpp
@@ -14,30 +14,20 @@
  * limitations under the License.
  */
 
-#include <vector>
-#include <memory>
+#include "minikin/Hyphenator.h"
+
 #include <algorithm>
+#include <memory>
 #include <string>
+#include <vector>
+
 #include <unicode/uchar.h>
 #include <unicode/uscript.h>
 
-// HACK: for reading pattern file
-#include <fcntl.h>
-
-#define LOG_TAG "Minikin"
-#include "utils/Log.h"
-
-#include "minikin/Hyphenator.h"
-
-using std::vector;
+#include "minikin/Characters.h"
 
 namespace minikin {
 
-static const uint16_t CHAR_HYPHEN_MINUS = 0x002D;
-static const uint16_t CHAR_SOFT_HYPHEN = 0x00AD;
-static const uint16_t CHAR_MIDDLE_DOT = 0x00B7;
-static const uint16_t CHAR_HYPHEN = 0x2010;
-
 // The following are structs that correspond to tables inside the hyb file format
 
 struct AlphabetTable0 {
@@ -50,7 +40,7 @@
 struct AlphabetTable1 {
     uint32_t version;
     uint32_t n_entries;
-    uint32_t data[1]; // actually flexible array, size is known at runtime
+    uint32_t data[1];  // actually flexible array, size is known at runtime
 
     static uint32_t codepoint(uint32_t entry) { return entry >> 11; }
     static uint32_t value(uint32_t entry) { return entry & 0x7ff; }
@@ -100,33 +90,42 @@
     const AlphabetTable1* alphabetTable1() const {
         return reinterpret_cast<const AlphabetTable1*>(bytes() + alphabet_offset);
     }
-    const Trie* trieTable() const {
-        return reinterpret_cast<const Trie*>(bytes() + trie_offset);
-    }
+    const Trie* trieTable() const { return reinterpret_cast<const Trie*>(bytes() + trie_offset); }
     const Pattern* patternTable() const {
         return reinterpret_cast<const Pattern*>(bytes() + pattern_offset);
     }
 };
 
-Hyphenator* Hyphenator::loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix) {
-    Hyphenator* result = new Hyphenator;
-    result->patternData = patternData;
-    result->minPrefix = minPrefix;
-    result->minSuffix = minSuffix;
-    return result;
+// static
+Hyphenator* Hyphenator::loadBinary(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+                                   const std::string& locale) {
+    HyphenationLocale hyphenLocale = HyphenationLocale::OTHER;
+    if (locale == "pl") {
+        hyphenLocale = HyphenationLocale::POLISH;
+    } else if (locale == "ca") {
+        hyphenLocale = HyphenationLocale::CATALAN;
+    } else if (locale == "sl") {
+        hyphenLocale = HyphenationLocale::SLOVENIAN;
+    }
+    return new Hyphenator(patternData, minPrefix, minSuffix, hyphenLocale);
 }
 
-void Hyphenator::hyphenate(vector<HyphenationType>* result, const uint16_t* word, size_t len,
-        const icu::Locale& locale) {
-    result->clear();
-    result->resize(len);
+Hyphenator::Hyphenator(const uint8_t* patternData, size_t minPrefix, size_t minSuffix,
+                       HyphenationLocale hyphenLocale)
+        : mPatternData(patternData),
+          mMinPrefix(minPrefix),
+          mMinSuffix(minSuffix),
+          mHyphenationLocale(hyphenLocale) {}
+
+void Hyphenator::hyphenate(const U16StringPiece& word, HyphenationType* out) const {
+    const size_t len = word.size();
     const size_t paddedLen = len + 2;  // start and stop code each count for 1
-    if (patternData != nullptr &&
-            len >= minPrefix + minSuffix && paddedLen <= MAX_HYPHENATED_SIZE) {
+    if (mPatternData != nullptr && len >= mMinPrefix + mMinSuffix &&
+        paddedLen <= MAX_HYPHENATED_SIZE) {
         uint16_t alpha_codes[MAX_HYPHENATED_SIZE];
-        const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word, len);
+        const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word);
         if (hyphenValue != HyphenationType::DONT_BREAK) {
-            hyphenateFromCodes(result->data(), alpha_codes, paddedLen, hyphenValue);
+            hyphenateFromCodes(alpha_codes, paddedLen, hyphenValue, out);
             return;
         }
         // TODO: try NFC normalization
@@ -135,7 +134,7 @@
     // Note that we will always get here if the word contains a hyphen or a soft hyphen, because the
     // alphabet is not expected to contain a hyphen or a soft hyphen character, so alphabetLookup
     // would return DONT_BREAK.
-    hyphenateWithNoPatterns(result->data(), word, len, locale);
+    hyphenateWithNoPatterns(word, out);
 }
 
 // This function determines whether a character is like U+2010 HYPHEN in
@@ -145,76 +144,46 @@
 // inspecting all the characters that have the Unicode line breaking
 // property of BA or HY and seeing which ones are hyphens.
 bool Hyphenator::isLineBreakingHyphen(uint32_t c) {
-    return (c == 0x002D || // HYPHEN-MINUS
-            c == 0x058A || // ARMENIAN HYPHEN
-            c == 0x05BE || // HEBREW PUNCTUATION MAQAF
-            c == 0x1400 || // CANADIAN SYLLABICS HYPHEN
-            c == 0x2010 || // HYPHEN
-            c == 0x2013 || // EN DASH
-            c == 0x2027 || // HYPHENATION POINT
-            c == 0x2E17 || // DOUBLE OBLIQUE HYPHEN
-            c == 0x2E40);  // DOUBLE HYPHEN
+    return (c == 0x002D ||  // HYPHEN-MINUS
+            c == 0x058A ||  // ARMENIAN HYPHEN
+            c == 0x05BE ||  // HEBREW PUNCTUATION MAQAF
+            c == 0x1400 ||  // CANADIAN SYLLABICS HYPHEN
+            c == 0x2010 ||  // HYPHEN
+            c == 0x2013 ||  // EN DASH
+            c == 0x2027 ||  // HYPHENATION POINT
+            c == 0x2E17 ||  // DOUBLE OBLIQUE HYPHEN
+            c == 0x2E40);   // DOUBLE HYPHEN
 }
 
-const static uint32_t HYPHEN_STR[] = {0x2010, 0};
-const static uint32_t ARMENIAN_HYPHEN_STR[] = {0x058A, 0};
-const static uint32_t MAQAF_STR[] = {0x05BE, 0};
-const static uint32_t UCAS_HYPHEN_STR[] = {0x1400, 0};
-const static uint32_t ZWJ_STR[] = {0x200D, 0};
-const static uint32_t ZWJ_AND_HYPHEN_STR[] = {0x200D, 0x2010, 0};
-
-const uint32_t* HyphenEdit::getHyphenString(uint32_t hyph) {
-    switch (hyph) {
-        case INSERT_HYPHEN_AT_END:
-        case REPLACE_WITH_HYPHEN_AT_END:
-        case INSERT_HYPHEN_AT_START:
-            return HYPHEN_STR;
-        case INSERT_ARMENIAN_HYPHEN_AT_END:
-            return ARMENIAN_HYPHEN_STR;
-        case INSERT_MAQAF_AT_END:
-            return MAQAF_STR;
-        case INSERT_UCAS_HYPHEN_AT_END:
-            return UCAS_HYPHEN_STR;
-        case INSERT_ZWJ_AND_HYPHEN_AT_END:
-            return ZWJ_AND_HYPHEN_STR;
-        case INSERT_ZWJ_AT_START:
-            return ZWJ_STR;
-        default:
-            return nullptr;
-    }
-}
-
-uint32_t HyphenEdit::editForThisLine(HyphenationType type) {
+EndHyphenEdit editForThisLine(HyphenationType type) {
     switch (type) {
-        case HyphenationType::DONT_BREAK:
-            return NO_EDIT;
         case HyphenationType::BREAK_AND_INSERT_HYPHEN:
-            return INSERT_HYPHEN_AT_END;
+            return EndHyphenEdit::INSERT_HYPHEN;
         case HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN:
-            return INSERT_ARMENIAN_HYPHEN_AT_END;
+            return EndHyphenEdit::INSERT_ARMENIAN_HYPHEN;
         case HyphenationType::BREAK_AND_INSERT_MAQAF:
-            return INSERT_MAQAF_AT_END;
+            return EndHyphenEdit::INSERT_MAQAF;
         case HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN:
-            return INSERT_UCAS_HYPHEN_AT_END;
+            return EndHyphenEdit::INSERT_UCAS_HYPHEN;
         case HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN:
-            return REPLACE_WITH_HYPHEN_AT_END;
+            return EndHyphenEdit::REPLACE_WITH_HYPHEN;
         case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
-            return INSERT_ZWJ_AND_HYPHEN_AT_END;
+            return EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN;
+        case HyphenationType::DONT_BREAK:  // Hyphen edit for non breaking case doesn't make sense.
         default:
-            return BREAK_AT_END;
+            return EndHyphenEdit::NO_EDIT;
     }
 }
 
-uint32_t HyphenEdit::editForNextLine(HyphenationType type) {
+StartHyphenEdit editForNextLine(HyphenationType type) {
     switch (type) {
-        case HyphenationType::DONT_BREAK:
-            return NO_EDIT;
         case HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE:
-            return INSERT_HYPHEN_AT_START;
+            return StartHyphenEdit::INSERT_HYPHEN;
         case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:
-            return INSERT_ZWJ_AT_START;
+            return StartHyphenEdit::INSERT_ZWJ;
+        case HyphenationType::DONT_BREAK:  // Hyphen edit for non breaking case doesn't make sense.
         default:
-            return BREAK_AT_START;
+            return StartHyphenEdit::NO_EDIT;
     }
 }
 
@@ -234,10 +203,8 @@
     // for now to be safe.  BREAK_AND_INSERT_MAQAF is already implemented, so if we want to switch
     // to maqaf for Hebrew, we can simply add a condition here.
     const UScriptCode script = getScript(codePoint);
-    if (script == USCRIPT_KANNADA
-            || script == USCRIPT_MALAYALAM
-            || script == USCRIPT_TAMIL
-            || script == USCRIPT_TELUGU) {
+    if (script == USCRIPT_KANNADA || script == USCRIPT_MALAYALAM || script == USCRIPT_TAMIL ||
+        script == USCRIPT_TELUGU) {
         // Grantha is not included, since we don't support non-BMP hyphenation yet.
         return HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
     } else if (script == USCRIPT_ARMENIAN) {
@@ -255,17 +222,17 @@
 
 // Assumption for caller: location must be >= 2 and word[location] == CHAR_SOFT_HYPHEN.
 // This function decides if the letters before and after the hyphen should appear as joining.
-static inline HyphenationType getHyphTypeForArabic(const uint16_t* word, size_t len,
-        size_t location) {
+static inline HyphenationType getHyphTypeForArabic(const U16StringPiece& word, size_t location) {
     ssize_t i = location;
     int32_t type = U_JT_NON_JOINING;
-    while (static_cast<size_t>(i) < len && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
+    while (static_cast<size_t>(i) < word.size() &&
+           (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
         i++;
     }
     if (type == U_JT_DUAL_JOINING || type == U_JT_RIGHT_JOINING || type == U_JT_JOIN_CAUSING) {
         // The next character is of the type that may join the last character. See if the last
         // character is also of the right type.
-        i = location - 2; // Skip the soft hyphen
+        i = location - 2;  // Skip the soft hyphen
         type = U_JT_NON_JOINING;
         while (i >= 0 && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {
             i--;
@@ -280,22 +247,22 @@
 // Use various recommendations of UAX #14 Unicode Line Breaking Algorithm for hyphenating words
 // that didn't match patterns, especially words that contain hyphens or soft hyphens (See sections
 // 5.3, Use of Hyphen, and 5.4, Use of Soft Hyphen).
-void Hyphenator::hyphenateWithNoPatterns(HyphenationType* result, const uint16_t* word, size_t len,
-        const icu::Locale& locale) {
-    result[0] = HyphenationType::DONT_BREAK;
-    for (size_t i = 1; i < len; i++) {
+void Hyphenator::hyphenateWithNoPatterns(const U16StringPiece& word, HyphenationType* out) const {
+    out[0] = HyphenationType::DONT_BREAK;
+    for (size_t i = 1; i < word.size(); i++) {
         const uint16_t prevChar = word[i - 1];
         if (i > 1 && isLineBreakingHyphen(prevChar)) {
             // Break after hyphens, but only if they don't start the word.
 
-            if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN)
-                    && strcmp(locale.getLanguage(), "pl") == 0
-                    && getScript(word[i]) == USCRIPT_LATIN ) {
-                // In Polish, hyphens get repeated at the next line. To be safe,
+            if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN) &&
+                (mHyphenationLocale == HyphenationLocale::POLISH ||
+                 mHyphenationLocale == HyphenationLocale::SLOVENIAN) &&
+                getScript(word[i]) == USCRIPT_LATIN) {
+                // In Polish and Slovenian, hyphens get repeated at the next line. To be safe,
                 // we will do this only if the next character is Latin.
-                result[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;
+                out[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;
             } else {
-                result[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
+                out[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
             }
         } else if (i > 1 && prevChar == CHAR_SOFT_HYPHEN) {
             // Break after soft hyphens, but only if they don't start the word (a soft hyphen
@@ -304,26 +271,25 @@
             if (getScript(word[i]) == USCRIPT_ARABIC) {
                 // For Arabic, we need to look and see if the characters around the soft hyphen
                 // actually join. If they don't, we'll just insert a normal hyphen.
-                result[i] = getHyphTypeForArabic(word, len, i);
+                out[i] = getHyphTypeForArabic(word, i);
             } else {
-                result[i] = hyphenationTypeBasedOnScript(word[i]);
+                out[i] = hyphenationTypeBasedOnScript(word[i]);
             }
-        } else if (prevChar == CHAR_MIDDLE_DOT
-                && minPrefix < i && i <= len - minSuffix
-                && ((word[i - 2] == 'l' && word[i] == 'l')
-                        || (word[i - 2] == 'L' && word[i] == 'L'))
-                && strcmp(locale.getLanguage(), "ca") == 0) {
+        } else if (prevChar == CHAR_MIDDLE_DOT && mMinPrefix < i && i <= word.size() - mMinSuffix &&
+                   ((word[i - 2] == 'l' && word[i] == 'l') ||
+                    (word[i - 2] == 'L' && word[i] == 'L')) &&
+                   mHyphenationLocale == HyphenationLocale::CATALAN) {
             // In Catalan, "l·l" should break as "l-" on the first line
             // and "l" on the next line.
-            result[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;
+            out[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;
         } else {
-            result[i] = HyphenationType::DONT_BREAK;
+            out[i] = HyphenationType::DONT_BREAK;
         }
-     }
+    }
 }
 
-HyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes, const uint16_t* word,
-        size_t len) {
+HyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes,
+                                           const U16StringPiece& word) const {
     const Header* header = getHeader();
     HyphenationType result = HyphenationType::BREAK_AND_INSERT_HYPHEN;
     // TODO: check header magic
@@ -333,7 +299,7 @@
         uint32_t min_codepoint = alphabet->min_codepoint;
         uint32_t max_codepoint = alphabet->max_codepoint;
         alpha_codes[0] = 0;  // word start
-        for (size_t i = 0; i < len; i++) {
+        for (size_t i = 0; i < word.size(); i++) {
             uint16_t c = word[i];
             if (c < min_codepoint || c >= max_codepoint) {
                 return HyphenationType::DONT_BREAK;
@@ -347,7 +313,7 @@
             }
             alpha_codes[i + 1] = code;
         }
-        alpha_codes[len + 1] = 0;  // word termination
+        alpha_codes[word.size() + 1] = 0;  // word termination
         return result;
     } else if (alphabetVersion == 1) {
         const AlphabetTable1* alphabet = header->alphabetTable1();
@@ -355,7 +321,7 @@
         const uint32_t* begin = alphabet->data;
         const uint32_t* end = begin + n_entries;
         alpha_codes[0] = 0;
-        for (size_t i = 0; i < len; i++) {
+        for (size_t i = 0; i < word.size(); i++) {
             uint16_t c = word[i];
             auto p = std::lower_bound(begin, end, c << 11);
             if (p == end) {
@@ -370,7 +336,7 @@
             }
             alpha_codes[i + 1] = AlphabetTable1::value(entry);
         }
-        alpha_codes[len + 1] = 0;
+        alpha_codes[word.size() + 1] = 0;
         return result;
     }
     return HyphenationType::DONT_BREAK;
@@ -381,11 +347,11 @@
  * has been done by now, and all characters have been found in the alphabet.
  * Note: len here is the padded length including 0 codes at start and end.
  **/
-void Hyphenator::hyphenateFromCodes(HyphenationType* result, const uint16_t* codes, size_t len,
-        HyphenationType hyphenValue) {
+void Hyphenator::hyphenateFromCodes(const uint16_t* codes, size_t len, HyphenationType hyphenValue,
+                                    HyphenationType* out) const {
     static_assert(sizeof(HyphenationType) == sizeof(uint8_t), "HyphnationType must be uint8_t.");
     // Reuse the result array as a buffer for calculating intermediate hyphenation numbers.
-    uint8_t* buffer = reinterpret_cast<uint8_t*>(result);
+    uint8_t* buffer = reinterpret_cast<uint8_t*>(out);
 
     const Header* header = getHeader();
     const Trie* trie = header->trieTable();
@@ -394,7 +360,7 @@
     uint32_t link_shift = trie->link_shift;
     uint32_t link_mask = trie->link_mask;
     uint32_t pattern_shift = trie->pattern_shift;
-    size_t maxOffset = len - minSuffix - 1;
+    size_t maxOffset = len - mMinSuffix - 1;
     for (size_t i = 0; i < len - 1; i++) {
         uint32_t node = 0;  // index into Trie table
         for (size_t j = i; j < len; j++) {
@@ -416,7 +382,7 @@
                 const uint8_t* pat_buf = pattern->buf(pat_entry);
                 int offset = j + 1 - (pat_len + pat_shift);
                 // offset is the index within buffer that lines up with the start of pat_buf
-                int start = std::max((int)minPrefix - offset, 0);
+                int start = std::max((int)mMinPrefix - offset, 0);
                 int end = std::min(pat_len, (int)maxOffset - offset);
                 for (int k = start; k < end; k++) {
                     buffer[offset + k] = std::max(buffer[offset + k], pat_buf[k]);
@@ -425,10 +391,10 @@
         }
     }
     // Since the above calculation does not modify values outside
-    // [minPrefix, len - minSuffix], they are left as 0 = DONT_BREAK.
-    for (size_t i = minPrefix; i < maxOffset; i++) {
+    // [mMinPrefix, len - mMinSuffix], they are left as 0 = DONT_BREAK.
+    for (size_t i = mMinPrefix; i < maxOffset; i++) {
         // Hyphenation opportunities happen when the hyphenation numbers are odd.
-        result[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;
+        out[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;
     }
 }
 
diff --git a/libs/minikin/HyphenatorMap.cpp b/libs/minikin/HyphenatorMap.cpp
new file mode 100644
index 0000000..40c785a
--- /dev/null
+++ b/libs/minikin/HyphenatorMap.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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 "HyphenatorMap.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+namespace {
+constexpr SubtagBits LANGUAGE = SubtagBits::LANGUAGE;
+constexpr SubtagBits SCRIPT = SubtagBits::SCRIPT;
+constexpr SubtagBits REGION = SubtagBits::REGION;
+constexpr SubtagBits VARIANT = SubtagBits::VARIANT;
+
+constexpr int DEFAULT_MIN_PREFIX = 2;
+constexpr int DEFAULT_MAX_PREFIX = 2;
+}  // namespace
+
+// Following two function's implementations are here since Hyphenator.cpp can't include
+// HyphenatorMap.h due to harfbuzz dependency on the host binary.
+void addHyphenator(const std::string& localeStr, const Hyphenator* hyphenator) {
+    HyphenatorMap::add(localeStr, hyphenator);
+}
+
+void addHyphenatorAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr) {
+    HyphenatorMap::addAlias(fromLocaleStr, toLocaleStr);
+}
+
+HyphenatorMap::HyphenatorMap()
+        : mSoftHyphenOnlyHyphenator(
+                  Hyphenator::loadBinary(nullptr, DEFAULT_MIN_PREFIX, DEFAULT_MAX_PREFIX, "")) {}
+
+void HyphenatorMap::addInternal(const std::string& localeStr, const Hyphenator* hyphenator) {
+    const Locale locale(localeStr);
+    std::lock_guard<std::mutex> lock(mMutex);
+    // Overwrite even if there is already a fallback entry.
+    mMap[locale.getIdentifier()] = hyphenator;
+}
+
+void HyphenatorMap::clearInternal() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mMap.clear();
+}
+void HyphenatorMap::addAliasInternal(const std::string& fromLocaleStr,
+                                     const std::string& toLocaleStr) {
+    const Locale fromLocale(fromLocaleStr);
+    const Locale toLocale(toLocaleStr);
+    std::lock_guard<std::mutex> lock(mMutex);
+    auto it = mMap.find(toLocale.getIdentifier());
+    if (it == mMap.end()) {
+        ALOGE("Target Hyphenator not found.");
+        return;
+    }
+    // Overwrite even if there is already a fallback entry.
+    mMap[fromLocale.getIdentifier()] = it->second;
+}
+
+const Hyphenator* HyphenatorMap::lookupInternal(const Locale& locale) {
+    const uint64_t id = locale.getIdentifier();
+    std::lock_guard<std::mutex> lock(mMutex);
+    const Hyphenator* result = lookupByIdentifier(id);
+    if (result != nullptr) {
+        return result;  // Found with exact match.
+    }
+
+    // First, try with dropping emoji extensions.
+    result = lookupBySubtag(locale, LANGUAGE | REGION | SCRIPT | VARIANT);
+    if (result != nullptr) {
+        goto insert_result_and_return;
+    }
+    // If not found, try with dropping script.
+    result = lookupBySubtag(locale, LANGUAGE | REGION | VARIANT);
+    if (result != nullptr) {
+        goto insert_result_and_return;
+    }
+    // If not found, try with dropping script and region code.
+    result = lookupBySubtag(locale, LANGUAGE | VARIANT);
+    if (result != nullptr) {
+        goto insert_result_and_return;
+    }
+    // If not found, try only with language code.
+    result = lookupBySubtag(locale, LANGUAGE);
+    if (result != nullptr) {
+        goto insert_result_and_return;
+    }
+    // Still not found, try only with script.
+    result = lookupBySubtag(locale, SCRIPT);
+    if (result != nullptr) {
+        goto insert_result_and_return;
+    }
+
+    // If not found, use soft hyphen only hyphenator.
+    result = mSoftHyphenOnlyHyphenator;
+
+insert_result_and_return:
+    mMap.insert(std::make_pair(id, result));
+    return result;
+}
+
+const Hyphenator* HyphenatorMap::lookupByIdentifier(uint64_t id) const {
+    auto it = mMap.find(id);
+    return it == mMap.end() ? nullptr : it->second;
+}
+
+const Hyphenator* HyphenatorMap::lookupBySubtag(const Locale& locale, SubtagBits bits) const {
+    const Locale partialLocale = locale.getPartialLocale(bits);
+    if (!partialLocale.isSupported() || partialLocale == locale) {
+        return nullptr;  // Skip the partial locale result in the same locale or not supported.
+    }
+    return lookupByIdentifier(partialLocale.getIdentifier());
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/HyphenatorMap.h b/libs/minikin/HyphenatorMap.h
new file mode 100644
index 0000000..26b5e61
--- /dev/null
+++ b/libs/minikin/HyphenatorMap.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 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_HYPHENATOR_MAP_H
+#define MINIKIN_HYPHENATOR_MAP_H
+
+#include <map>
+#include <mutex>
+
+#include "minikin/Hyphenator.h"
+#include "minikin/Macros.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+class HyphenatorMap {
+public:
+    // This map doesn't take ownership of the hyphenator but we don't need to care about the
+    // ownership. In Android, the Hyphenator is allocated in Zygote and never gets released.
+    static void add(const std::string& localeStr, const Hyphenator* hyphenator) {
+        getInstance().addInternal(localeStr, hyphenator);
+    }
+
+    static void addAlias(const std::string& fromLocaleStr, const std::string& toLocaleStr) {
+        getInstance().addAliasInternal(fromLocaleStr, toLocaleStr);
+    }
+
+    // Remove all hyphenators from the map. This is test only method.
+    static void clear() { getInstance().clearInternal(); }
+
+    // The returned pointer is never a dangling pointer. If nothing found for a given locale,
+    // returns a hyphenator which only processes soft hyphens.
+    //
+    // The Hyphenator lookup works with the following rules:
+    // 1. Search for the Hyphenator with the given locale.
+    // 2. If not found, try again with language + script + region + variant.
+    // 3. If not found, try again with language + region + variant.
+    // 4. If not found, try again with language + variant.
+    // 5. If not found, try again with language.
+    // 6. If not found, try again with script.
+    static const Hyphenator* lookup(const Locale& locale) {
+        return getInstance().lookupInternal(locale);
+    }
+
+protected:
+    // The following five methods are protected for testing purposes.
+    HyphenatorMap();  // Use getInstance() instead.
+    void addInternal(const std::string& localeStr, const Hyphenator* hyphenator);
+    void addAliasInternal(const std::string& fromLocaleStr, const std::string& toLocaleStr);
+    const Hyphenator* lookupInternal(const Locale& locale);
+
+private:
+    static HyphenatorMap& getInstance() {  // Singleton.
+        static HyphenatorMap map;
+        return map;
+    }
+
+    void clearInternal();
+
+    const Hyphenator* lookupByIdentifier(uint64_t id) const EXCLUSIVE_LOCKS_REQUIRED(mMutex);
+    const Hyphenator* lookupBySubtag(const Locale& locale, SubtagBits bits) const
+            EXCLUSIVE_LOCKS_REQUIRED(mMutex);
+
+    const Hyphenator* mSoftHyphenOnlyHyphenator;
+    std::map<uint64_t, const Hyphenator*> mMap GUARDED_BY(mMutex);
+
+    std::mutex mMutex;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_HYPHENATOR_MAP_H
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index cbfb430..1b05274 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -16,206 +16,109 @@
 
 #define LOG_TAG "Minikin"
 
-#include <algorithm>
-#include <fstream>
-#include <iostream>  // for debugging
-#include <math.h>
-#include <string>
-#include <unicode/ubidi.h>
-#include <unicode/utf16.h>
-#include <vector>
+#include "minikin/Layout.h"
 
-#include <log/log.h>
-#include <utils/JenkinsHash.h>
-#include <utils/LruCache.h>
-#include <utils/Singleton.h>
-#include <utils/String16.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/JenkinsHash.h>
+#include <utils/LruCache.h>
 
-#include "FontLanguage.h"
-#include "FontLanguageListCache.h"
-#include "HbFontCache.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"
-#include <minikin/Emoji.h>
-#include <minikin/Layout.h>
-
-using std::string;
-using std::vector;
 
 namespace minikin {
 
-const int kDirection_Mask = 0x1;
+namespace {
 
-struct LayoutContext {
-    MinikinPaint paint;
-    FontStyle style;
-    std::vector<hb_font_t*> hbFonts;  // parallel to mFaces
-
-    void clearHbFonts() {
-        for (size_t i = 0; i < hbFonts.size(); i++) {
-            hb_font_set_funcs(hbFonts[i], nullptr, nullptr, nullptr);
-            hb_font_destroy(hbFonts[i]);
-        }
-        hbFonts.clear();
-    }
+struct SkiaArguments {
+    const MinikinFont* font;
+    const MinikinPaint* paint;
+    FontFakery fakery;
 };
 
-// Layout cache datatypes
-
-class LayoutCacheKey {
-public:
-    LayoutCacheKey(const std::shared_ptr<FontCollection>& collection, const MinikinPaint& paint,
-            FontStyle style, const uint16_t* chars, size_t start, size_t count, size_t nchars,
-            bool dir)
-            : mChars(chars), mNchars(nchars),
-            mStart(start), mCount(count), mId(collection->getId()), mStyle(style),
-            mSize(paint.size), mScaleX(paint.scaleX), mSkewX(paint.skewX),
-            mLetterSpacing(paint.letterSpacing),
-            mPaintFlags(paint.paintFlags), mHyphenEdit(paint.hyphenEdit), mIsRtl(dir),
-            mHash(computeHash()) {
-    }
-    bool operator==(const LayoutCacheKey &other) const;
-
-    android::hash_t hash() const {
-        return mHash;
-    }
-
-    void copyText() {
-        uint16_t* charsCopy = new uint16_t[mNchars];
-        memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
-        mChars = charsCopy;
-    }
-    void freeText() {
-        delete[] mChars;
-        mChars = NULL;
-    }
-
-    void doLayout(Layout* layout, LayoutContext* ctx,
-            const std::shared_ptr<FontCollection>& collection) const {
-        layout->mAdvances.resize(mCount, 0);
-        ctx->clearHbFonts();
-        layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx, collection);
-    }
-
-private:
-    const uint16_t* mChars;
-    size_t mNchars;
-    size_t mStart;
-    size_t mCount;
-    uint32_t mId;  // for the font collection
-    FontStyle mStyle;
-    float mSize;
-    float mScaleX;
-    float mSkewX;
-    float mLetterSpacing;
-    int32_t mPaintFlags;
-    HyphenEdit mHyphenEdit;
-    bool mIsRtl;
-    // Note: any fields added to MinikinPaint must also be reflected here.
-    // TODO: language matching (possibly integrate into style)
-    android::hash_t mHash;
-
-    android::hash_t computeHash() const;
-};
-
-class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
-public:
-    LayoutCache() : mCache(kMaxEntries) {
-        mCache.setOnEntryRemovedListener(this);
-    }
-
-    void clear() {
-        mCache.clear();
-    }
-
-    Layout* get(LayoutCacheKey& key, LayoutContext* ctx,
-            const std::shared_ptr<FontCollection>& collection) {
-        Layout* layout = mCache.get(key);
-        if (layout == NULL) {
-            key.copyText();
-            layout = new Layout();
-            key.doLayout(layout, ctx, collection);
-            mCache.put(key, layout);
-        }
-        return layout;
-    }
-
-private:
-    // callback for OnEntryRemoved
-    void operator()(LayoutCacheKey& key, Layout*& value) {
-        key.freeText();
-        delete value;
-    }
-
-    android::LruCache<LayoutCacheKey, Layout*> mCache;
-
-    //static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
-
-    // TODO: eviction based on memory footprint; for now, we just use a constant
-    // number of strings
-    static const size_t kMaxEntries = 5000;
-};
-
-static unsigned int disabledDecomposeCompatibility(hb_unicode_funcs_t*, hb_codepoint_t,
-                                                   hb_codepoint_t*, void*) {
-    return 0;
+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;
 }
 
-class LayoutEngine : public ::android::Singleton<LayoutEngine> {
-public:
-    LayoutEngine() {
+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_unicode_funcs_t* getUnicodeFunctions() {
+    static hb_unicode_funcs_t* unicodeFunctions = nullptr;
+    static std::once_flag once;
+    std::call_once(once, [&]() {
         unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
         /* Disable the function used for compatibility decomposition */
         hb_unicode_funcs_set_decompose_compatibility_func(
-                unicodeFunctions, disabledDecomposeCompatibility, NULL, NULL);
-        hbBuffer = hb_buffer_create();
-        hb_buffer_set_unicode_funcs(hbBuffer, unicodeFunctions);
-    }
-
-    hb_buffer_t* hbBuffer;
-    hb_unicode_funcs_t* unicodeFunctions;
-    LayoutCache layoutCache;
-};
-
-bool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {
-    return mId == other.mId
-            && mStart == other.mStart
-            && mCount == other.mCount
-            && mStyle == other.mStyle
-            && mSize == other.mSize
-            && mScaleX == other.mScaleX
-            && mSkewX == other.mSkewX
-            && mLetterSpacing == other.mLetterSpacing
-            && mPaintFlags == other.mPaintFlags
-            && mHyphenEdit == other.mHyphenEdit
-            && mIsRtl == other.mIsRtl
-            && mNchars == other.mNchars
-            && !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));
+                unicodeFunctions,
+                [](hb_unicode_funcs_t*, hb_codepoint_t, hb_codepoint_t*, void*) -> unsigned int {
+                    return 0;
+                },
+                nullptr, nullptr);
+    });
+    return unicodeFunctions;
 }
 
-android::hash_t LayoutCacheKey::computeHash() const {
-    uint32_t hash = android::JenkinsHashMix(0, mId);
-    hash = android::JenkinsHashMix(hash, mStart);
-    hash = android::JenkinsHashMix(hash, mCount);
-    hash = android::JenkinsHashMix(hash, hash_type(mStyle));
-    hash = android::JenkinsHashMix(hash, hash_type(mSize));
-    hash = android::JenkinsHashMix(hash, hash_type(mScaleX));
-    hash = android::JenkinsHashMix(hash, hash_type(mSkewX));
-    hash = android::JenkinsHashMix(hash, hash_type(mLetterSpacing));
-    hash = android::JenkinsHashMix(hash, hash_type(mPaintFlags));
-    hash = android::JenkinsHashMix(hash, hash_type(mHyphenEdit.getHyphen()));
-    hash = android::JenkinsHashMix(hash, hash_type(mIsRtl));
-    hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
-    return android::JenkinsHashWhiten(hash);
+// 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;
 }
 
-android::hash_t hash_type(const LayoutCacheKey& key) {
-    return key.hash();
+// 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);
@@ -232,64 +135,21 @@
     mFaces.clear();
     mBounds.setEmpty();
     mAdvances.clear();
+    mExtents.clear();
     mAdvance = 0;
 }
 
-static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
-        hb_codepoint_t glyph, void* /* userData */) {
-    MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
-    float advance = paint->font->GetHorizontalAdvance(glyph, *paint);
-    return 256 * advance + 0.5;
+static bool isColorBitmapFont(const HbFontUniquePtr& font) {
+    HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
+    return cbdt;
 }
 
-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;
+static float HBFixedToFloat(hb_position_t v) {
+    return scalbnf(v, -8);
 }
 
-hb_font_funcs_t* getHbFontFuncs(bool forColorBitmapFont) {
-    assertMinikinLocked();
-
-    static hb_font_funcs_t* hbFuncs = nullptr;
-    static hb_font_funcs_t* hbFuncsForColorBitmap = nullptr;
-
-    hb_font_funcs_t** funcs = forColorBitmapFont ? &hbFuncs : &hbFuncsForColorBitmap;
-    if (*funcs == nullptr) {
-        *funcs = hb_font_funcs_create();
-        if (forColorBitmapFont) {
-            // 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.
-        } else {
-            // 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(*funcs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
-        }
-        hb_font_funcs_set_glyph_h_origin_func(*funcs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
-        hb_font_funcs_make_immutable(*funcs);
-    }
-    return *funcs;
-}
-
-static bool isColorBitmapFont(hb_font_t* font) {
-    hb_face_t* face = hb_font_get_face(font);
-    HbBlob cbdt(hb_face_reference_table(face, HB_TAG('C', 'B', 'D', 'T')));
-    return cbdt.size() > 0;
-}
-
-static float HBFixedToFloat(hb_position_t v)
-{
-    return scalbnf (v, -8);
-}
-
-static hb_position_t HBFloatToFixed(float v)
-{
-    return scalbnf (v, +8);
+static hb_position_t HBFloatToFixed(float v) {
+    return scalbnf(v, +8);
 }
 
 void Layout::dump() const {
@@ -299,39 +159,27 @@
     }
 }
 
-int Layout::findFace(const FakedFont& face, LayoutContext* ctx) {
-    unsigned int ix;
-    for (ix = 0; ix < mFaces.size(); ix++) {
+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);
-    // Note: ctx == NULL means we're copying from the cache, no need to create
-    // corresponding hb_font object.
-    if (ctx != NULL) {
-        hb_font_t* font = getHbFontLocked(face.font);
-        hb_font_set_funcs(font, getHbFontFuncs(isColorBitmapFont(font)), &ctx->paint, 0);
-        ctx->hbFonts.push_back(font);
-    }
     return ix;
 }
 
-static hb_script_t codePointToScript(hb_codepoint_t codepoint) {
-    static hb_unicode_funcs_t* u = 0;
-    if (!u) {
-        u = LayoutEngine::getInstance().unicodeFunctions;
-    }
-    return hb_unicode_script(u, codepoint);
-}
-
 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
+    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;
+    return (hb_codepoint_t)result;
 }
 
 static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
@@ -339,19 +187,16 @@
         return HB_SCRIPT_UNKNOWN;
     }
     uint32_t cp = decodeUtf16(chars, len, iter);
-    hb_script_t current_script = codePointToScript(cp);
+    hb_script_t current_script = hb_unicode_script(getUnicodeFunctions(), cp);
     for (;;) {
-        if (size_t(*iter) == len)
-            break;
+        if (size_t(*iter) == len) break;
         const ssize_t prev_iter = *iter;
         cp = decodeUtf16(chars, len, iter);
-        const hb_script_t script = codePointToScript(cp);
+        const hb_script_t script = hb_unicode_script(getUnicodeFunctions(), cp);
         if (script != current_script) {
-            if (current_script == HB_SCRIPT_INHERITED ||
-                current_script == HB_SCRIPT_COMMON) {
+            if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
                 current_script = script;
-            } else if (script == HB_SCRIPT_INHERITED ||
-                script == HB_SCRIPT_COMMON) {
+            } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
                 continue;
             } else {
                 *iter = prev_iter;
@@ -371,338 +216,236 @@
  * 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
-            );
+    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);
 }
 
-class BidiText {
-public:
-    class Iter {
-    public:
-        struct RunInfo {
-            int32_t mRunStart;
-            int32_t mRunLength;
-            bool mIsRtl;
-        };
-
-        Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, bool isRtl);
-
-        bool operator!= (const Iter& other) const {
-            return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex
-                    || mBidi != other.mBidi;
-        }
-
-        const RunInfo& operator* () const {
-            return mRunInfo;
-        }
-
-        const Iter& operator++ () {
-            updateRunInfo();
-            return *this;
-        }
-
-    private:
-        UBiDi* const mBidi;
-        bool mIsEnd;
-        size_t mNextRunIndex;
-        const size_t mRunCount;
-        const int32_t mStart;
-        const int32_t mEnd;
-        RunInfo mRunInfo;
-
-        void updateRunInfo();
-    };
-
-    BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags);
-
-    ~BidiText() {
-        if (mBidi) {
-            ubidi_close(mBidi);
-        }
-    }
-
-    Iter begin () const {
-        return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl);
-    }
-
-    Iter end() const {
-        return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl);
-    }
-
-private:
-    const size_t mStart;
-    const size_t mEnd;
-    const size_t mBufSize;
-    UBiDi* mBidi;
-    size_t mRunCount;
-    bool mIsRtl;
-
-    BidiText(const BidiText&) = delete;
-    void operator=(const BidiText&) = delete;
-};
-
-BidiText::Iter::Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount,
-        bool isRtl)
-    : mBidi(bidi), mIsEnd(runIndex == runCount), mNextRunIndex(runIndex), mRunCount(runCount),
-      mStart(start), mEnd(end), mRunInfo() {
-    if (mRunCount == 1) {
-        mRunInfo.mRunStart = start;
-        mRunInfo.mRunLength = end - start;
-        mRunInfo.mIsRtl = isRtl;
-        mNextRunIndex = mRunCount;
-        return;
-    }
-    updateRunInfo();
-}
-
-void BidiText::Iter::updateRunInfo() {
-    if (mNextRunIndex == mRunCount) {
-        // All runs have been iterated.
-        mIsEnd = true;
-        return;
-    }
-    int32_t startRun = -1;
-    int32_t lengthRun = -1;
-    const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun);
-    mNextRunIndex++;
-    if (startRun == -1 || lengthRun == -1) {
-        ALOGE("invalid visual run");
-        // skip the invalid run.
-        updateRunInfo();
-        return;
-    }
-    const int32_t runEnd = std::min(startRun + lengthRun, mEnd);
-    mRunInfo.mRunStart = std::max(startRun, mStart);
-    mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart;
-    if (mRunInfo.mRunLength <= 0) {
-        // skip the empty run.
-        updateRunInfo();
-        return;
-    }
-    mRunInfo.mIsRtl = (runDir == UBIDI_RTL);
-}
-
-BidiText::BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags)
-    : mStart(start), mEnd(start + count), mBufSize(bufSize), mBidi(NULL), mRunCount(1),
-      mIsRtl((bidiFlags & kDirection_Mask) != 0) {
-    if (bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL) {
-        // force single run.
-        return;
-    }
-    mBidi = ubidi_open();
-    if (!mBidi) {
-        ALOGE("error creating bidi object");
-        return;
-    }
-    UErrorCode status = U_ZERO_ERROR;
-    // Set callbacks to override bidi classes of new emoji
-    ubidi_setClassCallback(mBidi, emojiBidiOverride, nullptr, nullptr, nullptr, &status);
-    if (!U_SUCCESS(status)) {
-        ALOGE("error setting bidi callback function, status = %d", status);
-        return;
-    }
-
-    UBiDiLevel bidiReq = bidiFlags;
-    if (bidiFlags == kBidi_Default_LTR) {
-        bidiReq = UBIDI_DEFAULT_LTR;
-    } else if (bidiFlags == kBidi_Default_RTL) {
-        bidiReq = UBIDI_DEFAULT_RTL;
-    }
-    ubidi_setPara(mBidi, reinterpret_cast<const UChar*>(buf), mBufSize, bidiReq, NULL, &status);
-    if (!U_SUCCESS(status)) {
-        ALOGE("error calling ubidi_setPara, status = %d", status);
-        return;
-    }
-    const int paraDir = ubidi_getParaLevel(mBidi) & kDirection_Mask;
-    const ssize_t rc = ubidi_countRuns(mBidi, &status);
-    if (!U_SUCCESS(status) || rc < 0) {
-        ALOGW("error counting bidi runs, status = %d", status);
-    }
-    if (!U_SUCCESS(status) || rc <= 0) {
-        mIsRtl = (paraDir == kBidi_RTL);
-        return;
-    }
-    if (rc == 1) {
-        const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, 0, nullptr, nullptr);
-        mIsRtl = (runDir == UBIDI_RTL);
-        return;
-    }
-    mRunCount = rc;
-}
-
-void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
-        const std::shared_ptr<FontCollection>& collection) {
-    android::AutoMutex _l(gMinikinLock);
-
-    LayoutContext ctx;
-    ctx.style = style;
-    ctx.paint = paint;
-
+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);
 
-    for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
-        doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize, runInfo.mIsRtl, &ctx,
-                start, collection, this, NULL);
+    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);
     }
-    ctx.clearHbFonts();
 }
 
-float Layout::measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
-        const std::shared_ptr<FontCollection>& collection, float* advances) {
-    android::AutoMutex _l(gMinikinLock);
+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);
 
-    LayoutContext ctx;
-    ctx.style = style;
-    ctx.paint = paint;
-
-    float advance = 0;
-    for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
-        float* advancesForRun = advances ? advances + (runInfo.mRunStart - start) : advances;
-        advance += doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize,
-                runInfo.mIsRtl, &ctx, 0, collection, NULL, advancesForRun);
+    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);
     }
+}
 
-    ctx.clearHbFonts();
+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) {
+    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);
+    }
     return advance;
 }
 
-float Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        bool isRtl, LayoutContext* ctx, size_t dstStart,
-        const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
-    const uint32_t originalHyphen = ctx->paint.hyphenEdit.getHyphen();
+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) {
+    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
-        size_t wordstart =
+        uint32_t wordstart =
                 start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
-        size_t wordend;
-        for (size_t iter = start; iter < start + count; iter = wordend) {
+        uint32_t wordend;
+        for (size_t iter = start; iter < end; iter = wordend) {
             wordend = getNextWordBreakForCache(buf, iter, bufSize);
-            // Only apply hyphen to the first or last word in the string.
-            uint32_t hyphen = originalHyphen;
-            if (iter != start) { // Not the first word
-                hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
-            }
-            if (wordend < start + count) { // Not the last word
-                hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
-            }
-            ctx->paint.hyphenEdit = hyphen;
-            size_t wordcount = std::min(start + count, wordend) - iter;
+            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, ctx, iter - dstStart, collection, layout,
-                    advances ? advances + (iter - start) : advances);
+                                    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
-        size_t wordstart;
-        size_t end = start + count;
-        size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
+        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);
-            // Only apply hyphen to the first (rightmost) or last (leftmost) word in the string.
-            uint32_t hyphen = originalHyphen;
-            if (wordstart > start) { // Not the first word
-                hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
-            }
-            if (iter != end) { // Not the last word
-                hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
-            }
-            ctx->paint.hyphenEdit = hyphen;
-            size_t bufStart = std::max(start, wordstart);
+            uint32_t bufStart = std::max(start, wordstart);
+            const uint32_t offset = bufStart - start;
             advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
-                    wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection, layout,
-                    advances ? advances + (bufStart - start) : advances);
+                                    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;
         }
     }
     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),
+              mAdvances(advances),
+              mExtents(extents),
+              mPieces(pieces),
+              mTotalAdvance(totalAdvance),
+              mBounds(bounds),
+              mOutOffset(outOffset),
+              mWordSpacing(wordSpacing) {}
+
+    void operator()(const Layout& layout) {
+        if (mLayout) {
+            mLayout->appendLayout(layout, mOutOffset, mWordSpacing);
+        }
+        if (mAdvances) {
+            layout.getAdvances(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);
+        }
+    }
+
+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, LayoutContext* ctx, size_t bufStart,
-        const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
-    LayoutCache& cache = LayoutEngine::getInstance().layoutCache;
-    LayoutCacheKey key(collection, ctx->paint, ctx->style, buf, start, count, bufSize, isRtl);
+                           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) {
+    float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
+    float totalAdvance;
 
-    float wordSpacing = count == 1 && isWordSpace(buf[start]) ? ctx->paint.wordSpacing : 0;
-
-    float advance;
-    if (ctx->paint.skipCache()) {
-        Layout layoutForWord;
-        key.doLayout(&layoutForWord, ctx, collection);
-        if (layout) {
-            layout->appendLayout(&layoutForWord, bufStart, wordSpacing);
-        }
-        if (advances) {
-            layoutForWord.getAdvances(advances);
-        }
-        advance = layoutForWord.getAdvance();
+    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 {
-        Layout* layoutForWord = cache.get(key, ctx, collection);
-        if (layout) {
-            layout->appendLayout(layoutForWord, bufStart, wordSpacing);
-        }
-        if (advances) {
-            layoutForWord->getAdvances(advances);
-        }
-        advance = layoutForWord->getAdvance();
+        LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
+                                               f);
     }
 
     if (wordSpacing != 0) {
-        advance += wordSpacing;
+        totalAdvance += wordSpacing;
         if (advances) {
             advances[0] += wordSpacing;
         }
     }
-    return advance;
+    return totalAdvance;
 }
 
-static void addFeatures(const string &str, vector<hb_feature_t>* features) {
-    if (!str.size())
-        return;
-
-    const char* start = str.c_str();
-    const char* end = start + str.size();
-
-    while (start < end) {
+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;
-        const char* p = strchr(start, ',');
-        if (!p)
-            p = end;
         /* We do not allow setting features on ranges.  As such, reject any
          * setting that has non-universal range. */
-        if (hb_feature_from_string (start, p - start, &feature)
-                && feature.start == 0 && feature.end == (unsigned int) -1)
+        if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) &&
+            feature.start == 0 && feature.end == (unsigned int)-1) {
             features->push_back(feature);
-        start = p + 1;
+        }
     }
 }
 
-static const hb_codepoint_t CHAR_HYPHEN = 0x2010; /* HYPHEN */
-
 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 (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 {
@@ -716,37 +459,36 @@
         // 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 0x002D;  // HYPHEN-MINUS
         }
     }
     return preferredHyphen;
 }
 
-static inline void addHyphenToHbBuffer(hb_buffer_t* buffer, hb_font_t* font, uint32_t hyphen,
-        uint32_t cluster) {
-    const uint32_t* hyphenStr = HyphenEdit::getHyphenString(hyphen);
-    while (*hyphenStr != 0) {
-        hb_codepoint_t hyphenChar = determineHyphenChar(*hyphenStr, font);
-        hb_buffer_add(buffer, hyphenChar, cluster);
-        hyphenStr++;
+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(hb_buffer_t* buffer,
-        const uint16_t* buf, size_t start, size_t count, size_t bufSize,
-        ssize_t scriptRunStart, ssize_t scriptRunEnd,
-        HyphenEdit hyphenEdit, hb_font_t* hbFont) {
-
+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 uint32_t startHyphen = (scriptRunStart == 0)
-            ? hyphenEdit.getStart()
-            : HyphenEdit::NO_EDIT;
+    const StartHyphenEdit startHyphen =
+            (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
     // Only hyphenate the very last script run for ending hyphens.
-    const uint32_t endHyphen = (static_cast<size_t>(scriptRunEnd) == count)
-            ? hyphenEdit.getEnd()
-            : HyphenEdit::NO_EDIT;
+    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
@@ -762,19 +504,19 @@
 
     // We don't have any start-of-line replacement edit yet, so we don't need to check for
     // those.
-    if (HyphenEdit::isInsertion(startHyphen)) {
+    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 value */);
+        addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
     }
 
     const uint16_t* hbText;
     int hbTextLength;
     unsigned int hbItemOffset;
-    unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
+    unsigned int hbItemLength = scriptRunEnd - scriptRunStart;  // This is >= 1.
 
-    const bool hasEndInsertion = HyphenEdit::isInsertion(endHyphen);
-    const bool hasEndReplacement = HyphenEdit::isReplacement(endHyphen);
+    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
@@ -782,7 +524,7 @@
         hbItemLength -= 1;
     }
 
-    if (startHyphen == HyphenEdit::NO_EDIT) {
+    if (startHyphen == StartHyphenEdit::NO_EDIT) {
         // No edit at the beginning. Use the whole pre-context.
         hbText = buf;
         hbItemOffset = start + scriptRunStart;
@@ -793,7 +535,7 @@
         hbItemOffset = 0;
     }
 
-    if (endHyphen == HyphenEdit::NO_EDIT) {
+    if (endHyphen == EndHyphenEdit::NO_EDIT) {
         // No edit at the end, use the whole post-context.
         hbTextLength = (buf + bufSize) - hbText;
     } else {
@@ -801,10 +543,10 @@
         hbTextLength = hbItemOffset + hbItemLength;
     }
 
-    hb_buffer_add_utf16(buffer, hbText, 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, &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) {
@@ -823,61 +565,67 @@
             // we have a replacement that is replacing a one-code unit script run.
             hyphenCluster = 0;
         } else {
-            hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t) hasEndReplacement;
+            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, nullptr /* we don't need the size */);
+        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, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection) {
-    hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;
-    vector<FontCollection::Run> items;
-    collection->itemize(buf + start, count, ctx->style, &items);
+                         bool isRtl, const MinikinPaint& paint, StartHyphenEdit startHyphen,
+                         EndHyphenEdit endHyphen) {
+    HbBufferUniquePtr buffer(hb_buffer_create());
+    hb_buffer_set_unicode_funcs(buffer.get(), getUnicodeFunctions());
+    std::vector<FontCollection::Run> items;
+    paint.font->itemize(buf + start, count, paint, &items);
 
-    vector<hb_feature_t> features;
+    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(ctx->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 };
+    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(ctx->paint.fontFeatureSettings, &features);
+    addFeatures(paint.fontFeatureSettings, &features);
 
-    double size = ctx->paint.size;
-    double scaleX = ctx->paint.scaleX;
+    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];
-        if (run.fakedFont.font == NULL) {
-            ALOGE("no font for run starting u+%04x length %d", buf[run.start], run.end - run.start);
-            continue;
+         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));
         }
-        int font_ix = findFace(run.fakedFont, ctx);
-        ctx->paint.font = mFaces[font_ix].font;
-        ctx->paint.fakery = mFaces[font_ix].fakery;
-        hb_font_t* hbFont = ctx->hbFonts[font_ix];
-#ifdef VERBOSE_DEBUG
-        ALOGD("Run %zu, font %d [%d:%d]", run_ix, font_ix, run.start, run.end);
-#endif
+        const HbFontUniquePtr& hbFont = hbFonts[font_ix];
 
-        hb_font_set_ppem(hbFont, size * scaleX, size);
-        hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
+        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);
 
@@ -888,9 +636,8 @@
         // 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) {
+        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
@@ -904,9 +651,9 @@
             double letterSpaceHalfLeft = 0.0;
             double letterSpaceHalfRight = 0.0;
 
-            if (ctx->paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
-                letterSpace = ctx->paint.letterSpacing * size * scaleX;
-                if ((ctx->paint.paintFlags & LinearTextFlag) == 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 {
@@ -915,32 +662,30 @@
                 letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
             }
 
-            hb_buffer_clear_contents(buffer);
-            hb_buffer_set_script(buffer, script);
-            hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
-            const FontLanguages& langList =
-                    FontLanguageListCache::getById(ctx->style.getLanguageListId());
-            if (langList.size() != 0) {
-                const FontLanguage* hbLanguage = &langList[0];
-                for (size_t i = 0; i < langList.size(); ++i) {
-                    if (langList[i].supportsHbScript(script)) {
-                        hbLanguage = &langList[i];
+            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, hbLanguage->getHbLanguage());
+                hb_buffer_set_language(buffer.get(), hbLanguage);
             }
 
-            const uint32_t clusterStart = addToHbBuffer(
-                buffer,
-                buf, start, count, bufSize,
-                scriptRunStart, scriptRunEnd,
-                ctx->paint.hyphenEdit, hbFont);
+            const uint32_t clusterStart =
+                    addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
+                                  startHyphen, endHyphen, hbFont);
 
-            hb_shape(hbFont, buffer, features.empty() ? NULL : &features[0], features.size());
+            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, &numGlyphs);
-            hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
+            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
@@ -950,39 +695,32 @@
             // mAdvances.
             const ssize_t clusterOffset = clusterStart - scriptRunStart;
 
-            if (numGlyphs)
-            {
+            if (numGlyphs) {
                 mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
                 x += letterSpaceHalfLeft;
             }
             for (unsigned int i = 0; i < numGlyphs; i++) {
-#ifdef VERBOSE_DEBUG
-                ALOGD("%d %d %d %d",
-                        positions[i].x_advance, positions[i].y_advance,
-                        positions[i].x_offset, positions[i].y_offset);
-                ALOGD("DoLayout %u: %f; %d, %d",
-                        info[i].codepoint, HBFixedToFloat(positions[i].x_advance),
-                        positions[i].x_offset, positions[i].y_offset);
-#endif
+                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[info[i].cluster - clusterOffset] += letterSpaceHalfLeft;
+                    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 * ctx->paint.skewX;
+                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 ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
+                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, glyph_ix, &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.
@@ -992,20 +730,22 @@
                     glyphBounds.mBottom =
                             roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
                 } else {
-                    ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
+                    fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
+                                                          fakedFont.fakery);
                 }
-                glyphBounds.offset(x + xoff, y + yoff);
-                mBounds.join(glyphBounds);
-                if (static_cast<size_t>(info[i].cluster - clusterOffset) < count) {
-                    mAdvances[info[i].cluster - clusterOffset] += xAdvance;
+                glyphBounds.offset(xoff, yoff);
+
+                if (clusterBaseIndex < count) {
+                    mAdvances[clusterBaseIndex] += xAdvance;
                 } else {
-                    ALOGE("cluster %zu (start %zu) out of bounds of count %zu",
-                        info[i].cluster - clusterOffset, start, count);
+                    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)
-            {
+            if (numGlyphs) {
                 mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
                 x += letterSpaceHalfRight;
             }
@@ -1014,21 +754,21 @@
     mAdvance = x;
 }
 
-void Layout::appendLayout(Layout* src, size_t start, float extraAdvance) {
+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])) {
+    if (src.mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
         fontMap = fontMapStack;
     } else {
-        fontMap = new int[src->mFaces.size()];
+        fontMap = new int[src.mFaces.size()];
     }
-    for (size_t i = 0; i < src->mFaces.size(); i++) {
-        int font_ix = findFace(src->mFaces[i], NULL);
+    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++) {
-        LayoutGlyph& srcGlyph = src->mGlyphs[i];
+    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;
@@ -1036,15 +776,17 @@
         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];
-        if (i == 0)
-          mAdvances[i + start] += extraAdvance;
+    for (size_t i = 0; i < src.mAdvances.size(); i++) {
+        mAdvances[i + start] = src.mAdvances[i];
+        if (i == 0) {
+            mAdvances[start] += extraAdvance;
+        }
+        mExtents[i + start] = src.mExtents[i];
     }
-    MinikinRect srcBounds(src->mBounds);
+    MinikinRect srcBounds(src.mBounds);
     srcBounds.offset(x0, 0);
     mBounds.join(srcBounds);
-    mAdvance += src->mAdvance + extraAdvance;
+    mAdvance += src.mAdvance + extraAdvance;
 
     if (fontMap != fontMapStack) {
         delete[] fontMap;
@@ -1057,7 +799,7 @@
 
 const MinikinFont* Layout::getFont(int i) const {
     const LayoutGlyph& glyph = mGlyphs[i];
-    return mFaces[glyph.font_ix].font;
+    return mFaces[glyph.font_ix].font->typeface().get();
 }
 
 FontFakery Layout::getFakery(int i) const {
@@ -1084,26 +826,24 @@
     return mAdvance;
 }
 
-void Layout::getAdvances(float* advances) {
+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);
 }
 
 void Layout::purgeCaches() {
-    android::AutoMutex _l(gMinikinLock);
-    LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
-    layoutCache.clear();
-    purgeHbFontCacheLocked();
+    LayoutCache::getInstance().clear();
+}
+
+void Layout::dumpMinikinStats(int fd) {
+    LayoutCache::getInstance().dumpStats(fd);
 }
 
 }  // namespace minikin
-
-// Unable to define the static data member outside of android.
-// TODO: introduce our own Singleton to drop android namespace.
-namespace android {
-ANDROID_SINGLETON_STATIC_INSTANCE(minikin::LayoutEngine);
-}  // namespace android
-
diff --git a/libs/minikin/LayoutUtils.cpp b/libs/minikin/LayoutUtils.cpp
index a3238d4..e79ea8c 100644
--- a/libs/minikin/LayoutUtils.cpp
+++ b/libs/minikin/LayoutUtils.cpp
@@ -14,19 +14,16 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Minikin"
-
 #include "LayoutUtils.h"
 
 namespace minikin {
 
-const uint16_t CHAR_NBSP = 0x00A0;
-
 /*
  * Determine whether the code unit is a word space for the purposes of justification.
+ * TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
  */
 bool isWordSpace(uint16_t code_unit) {
-    return code_unit == ' ' || code_unit == CHAR_NBSP;
+    return code_unit == ' ';
 }
 
 /**
@@ -35,7 +32,7 @@
  * heuristic, but should be accurate most of the time.
  */
 static bool isWordBreakAfter(uint16_t c) {
-    if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {
+    if (c == ' ' || (0x2000 <= c && c <= 0x200A) || c == 0x3000) {
         // spaces
         return true;
     }
@@ -45,14 +42,13 @@
 
 static bool isWordBreakBefore(uint16_t c) {
     // CJK ideographs (and yijing hexagram symbols)
-    return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff);
+    return isWordBreakAfter(c) || (0x3400 <= c && c <= 0x9FFF);
 }
 
 /**
  * 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) {
+size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
     if (offset == 0) return 0;
     if (offset > len) offset = len;
     if (isWordBreakBefore(chars[offset - 1])) {
@@ -69,8 +65,7 @@
 /**
  * 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) {
+size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len) {
     if (offset >= len) return len;
     if (isWordBreakAfter(chars[offset])) {
         return offset + 1;
diff --git a/libs/minikin/LayoutUtils.h b/libs/minikin/LayoutUtils.h
index b89004c..3128148 100644
--- a/libs/minikin/LayoutUtils.h
+++ b/libs/minikin/LayoutUtils.h
@@ -17,7 +17,7 @@
 #ifndef MINIKIN_LAYOUT_UTILS_H
 #define MINIKIN_LAYOUT_UTILS_H
 
-#include <stdint.h>
+#include <cstdint>
 
 namespace minikin {
 
@@ -33,8 +33,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);
+size_t getPrevWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
 
 /**
  * Return offset of next word break. It is either > offset or == len.
@@ -43,8 +42,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);
+size_t getNextWordBreakForCache(const uint16_t* chars, size_t offset, size_t len);
 
 }  // namespace minikin
 #endif  // MINIKIN_LAYOUT_UTILS_H
diff --git a/libs/minikin/LineBreaker.cpp b/libs/minikin/LineBreaker.cpp
index bccc299..12cf9bd 100644
--- a/libs/minikin/LineBreaker.cpp
+++ b/libs/minikin/LineBreaker.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,533 +14,24 @@
  * limitations under the License.
  */
 
-#define VERBOSE_DEBUG 0
+#include "minikin/LineBreaker.h"
 
-#define LOG_TAG "Minikin"
-
-#include <limits>
-
-#include <log/log.h>
-
-#include "LayoutUtils.h"
-#include <minikin/Layout.h>
-#include <minikin/LineBreaker.h>
-
-using std::vector;
+#include "GreedyLineBreaker.h"
+#include "OptimalLineBreaker.h"
 
 namespace minikin {
 
-const int CHAR_TAB = 0x0009;
-
-// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
-// constants are larger than any reasonable actual width score.
-const float SCORE_INFTY = std::numeric_limits<float>::max();
-const float SCORE_OVERFULL = 1e12f;
-const float SCORE_DESPERATE = 1e10f;
-
-// Multiplier for hyphen penalty on last line.
-const float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
-// Penalty assigned to each line break (to try to minimize number of lines)
-// TODO: when we implement full justification (so spaces can shrink and stretch), this is
-// probably not the most appropriate method.
-const float LINE_PENALTY_MULTIPLIER = 2.0f;
-
-// Penalty assigned to shrinking the whitepsace.
-const float SHRINK_PENALTY_MULTIPLIER = 4.0f;
-
-// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for
-// unreasonably long words. This is somewhat of a heuristic because extremely long words
-// are possible in some languages. This does mean that very long real words can get
-// broken by desperate breaks, with no hyphens.
-const size_t LONGEST_HYPHENATED_WORD = 45;
-
-// When the text buffer is within this limit, capacity of vectors is retained at finish(),
-// to avoid allocation.
-const size_t MAX_TEXT_BUF_RETAIN = 32678;
-
-// Maximum amount that spaces can shrink, in justified text.
-const float SHRINKABILITY = 1.0 / 3.0;
-
-void LineBreaker::setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators) {
-    bool goodLocaleFound = false;
-    const ssize_t numLocales = hyphenators.size();
-    // For now, we ignore all locales except the first valid one.
-    // TODO: Support selecting the locale based on the script of the text.
-    const char* localeStart = locales;
-    for (ssize_t i = 0; i < numLocales - 1; i++) { // Loop over all locales, except the last one.
-        const char* localeEnd = strchr(localeStart, ',');
-        const size_t localeNameLength = localeEnd - localeStart;
-        char localeName[localeNameLength + 1];
-        strncpy(localeName, localeStart, localeNameLength);
-        localeName[localeNameLength] = '\0';
-        mLocale = icu::Locale::createFromName(localeName);
-        goodLocaleFound = !mLocale.isBogus();
-        if (goodLocaleFound) {
-            mHyphenator = hyphenators[i];
-            break;
-        } else {
-            localeStart = localeEnd + 1;
-        }
-    }
-    if (!goodLocaleFound) { // Try the last locale.
-        mLocale = icu::Locale::createFromName(localeStart);
-        if (mLocale.isBogus()) {
-            // No good locale.
-            mLocale = icu::Locale::getRoot();
-            mHyphenator = nullptr;
-        } else {
-            mHyphenator = numLocales == 0 ? nullptr : hyphenators[numLocales - 1];
-        }
-    }
-    mWordBreaker.setLocale(mLocale);
-}
-
-void LineBreaker::setText() {
-    mWordBreaker.setText(mTextBuf.data(), mTextBuf.size());
-
-    // handle initial break here because addStyleRun may never be called
-    mWordBreaker.next();
-    mCandidates.clear();
-    Candidate cand = {0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, HyphenationType::DONT_BREAK};
-    mCandidates.push_back(cand);
-
-    // reset greedy breaker state
-    mBreaks.clear();
-    mWidths.clear();
-    mFlags.clear();
-    mLastBreak = 0;
-    mBestBreak = 0;
-    mBestScore = SCORE_INFTY;
-    mPreBreak = 0;
-    mLastHyphenation = HyphenEdit::NO_EDIT;
-    mFirstTabIndex = INT_MAX;
-    mSpaceCount = 0;
-}
-
-void LineBreaker::setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth) {
-    mLineWidths.setWidths(firstWidth, firstWidthLineCount, restWidth);
-}
-
-
-void LineBreaker::setIndents(const std::vector<float>& indents) {
-    mLineWidths.setIndents(indents);
-}
-
-// This function determines whether a character is a space that disappears at end of line.
-// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]],
-// plus '\n'.
-// Note: all such characters are in the BMP, so it's ok to use code units for this.
-static bool isLineEndSpace(uint16_t c) {
-    return c == '\n' || c == ' ' || c == 0x1680 || (0x2000 <= c && c <= 0x200A && c != 0x2007) ||
-            c == 0x205F || c == 0x3000;
-}
-
-// Ordinarily, this method measures the text in the range given. However, when paint
-// is nullptr, it assumes the widths have already been calculated and stored in the
-// width buffer.
-// This method finds the candidate word breaks (using the ICU break iterator) and sends them
-// to addCandidate.
-float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
-        FontStyle style, size_t start, size_t end, bool isRtl) {
-    float width = 0.0f;
-    int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
-
-    float hyphenPenalty = 0.0;
-    if (paint != nullptr) {
-        width = Layout::measureText(mTextBuf.data(), start, end - start, mTextBuf.size(), bidiFlags,
-                style, *paint, typeface, mCharWidths.data() + start);
-
-        // a heuristic that seems to perform well
-        hyphenPenalty = 0.5 * paint->size * paint->scaleX * mLineWidths.getLineWidth(0);
-        if (mHyphenationFrequency == kHyphenationFrequency_Normal) {
-            hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing
-        }
-
-        if (mJustified) {
-            // Make hyphenation more aggressive for fully justified text (so that "normal" in
-            // justified mode is the same as "full" in ragged-right).
-            hyphenPenalty *= 0.25;
-        } else {
-            // Line penalty is zero for justified text.
-            mLinePenalty = std::max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER);
-        }
-    }
-
-    size_t current = (size_t)mWordBreaker.current();
-    size_t afterWord = start;
-    size_t lastBreak = start;
-    ParaWidth lastBreakWidth = mWidth;
-    ParaWidth postBreak = mWidth;
-    size_t postSpaceCount = mSpaceCount;
-    for (size_t i = start; i < end; i++) {
-        uint16_t c = mTextBuf[i];
-        if (c == CHAR_TAB) {
-            mWidth = mPreBreak + mTabStops.nextTab(mWidth - mPreBreak);
-            if (mFirstTabIndex == INT_MAX) {
-                mFirstTabIndex = (int)i;
-            }
-            // fall back to greedy; other modes don't know how to deal with tabs
-            mStrategy = kBreakStrategy_Greedy;
-        } else {
-            if (isWordSpace(c)) mSpaceCount += 1;
-            mWidth += mCharWidths[i];
-            if (!isLineEndSpace(c)) {
-                postBreak = mWidth;
-                postSpaceCount = mSpaceCount;
-                afterWord = i + 1;
-            }
-        }
-        if (i + 1 == current) {
-            size_t wordStart = mWordBreaker.wordStart();
-            size_t wordEnd = mWordBreaker.wordEnd();
-            if (paint != nullptr && mHyphenator != nullptr &&
-                    mHyphenationFrequency != kHyphenationFrequency_None &&
-                    wordStart >= start && wordEnd > wordStart &&
-                    wordEnd - wordStart <= LONGEST_HYPHENATED_WORD) {
-                mHyphenator->hyphenate(&mHyphBuf,
-                        &mTextBuf[wordStart],
-                        wordEnd - wordStart,
-                        mLocale);
-#if VERBOSE_DEBUG
-                std::string hyphenatedString;
-                for (size_t j = wordStart; j < wordEnd; j++) {
-                    if (mHyphBuf[j - wordStart] == HyphenationType::BREAK_AND_INSERT_HYPHEN) {
-                        hyphenatedString.push_back('-');
-                    }
-                    // Note: only works with ASCII, should do UTF-8 conversion here
-                    hyphenatedString.push_back(buffer()[j]);
-                }
-                ALOGD("hyphenated string: %s", hyphenatedString.c_str());
-#endif
-
-                // measure hyphenated substrings
-                for (size_t j = wordStart; j < wordEnd; j++) {
-                    HyphenationType hyph = mHyphBuf[j - wordStart];
-                    if (hyph != HyphenationType::DONT_BREAK) {
-                        paint->hyphenEdit = HyphenEdit::editForThisLine(hyph);
-                        const float firstPartWidth = Layout::measureText(mTextBuf.data(),
-                                lastBreak, j - lastBreak, mTextBuf.size(), bidiFlags, style,
-                                *paint, typeface, nullptr);
-                        ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth;
-
-                        paint->hyphenEdit = HyphenEdit::editForNextLine(hyph);
-                        const float secondPartWidth = Layout::measureText(mTextBuf.data(), j,
-                                afterWord - j, mTextBuf.size(), bidiFlags, style, *paint,
-                                typeface, nullptr);
-                        ParaWidth hyphPreBreak = postBreak - secondPartWidth;
-
-                        addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount, postSpaceCount,
-                                hyphenPenalty, hyph);
-
-                        paint->hyphenEdit = HyphenEdit::NO_EDIT;
-                    }
-                }
-            }
-
-            // Skip break for zero-width characters inside replacement span
-            if (paint != nullptr || current == end || mCharWidths[current] > 0) {
-                float penalty = hyphenPenalty * mWordBreaker.breakBadness();
-                addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount, penalty,
-                        HyphenationType::DONT_BREAK);
-            }
-            lastBreak = current;
-            lastBreakWidth = mWidth;
-            current = (size_t)mWordBreaker.next();
-        }
-    }
-
-    return width;
-}
-
-// add a word break (possibly for a hyphenated fragment), and add desperate breaks if
-// needed (ie when word exceeds current line width)
-void LineBreaker::addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak,
-        size_t preSpaceCount, size_t postSpaceCount, float penalty, HyphenationType hyph) {
-    Candidate cand;
-    ParaWidth width = mCandidates.back().preBreak;
-    if (postBreak - width > currentLineWidth()) {
-        // Add desperate breaks.
-        // Note: these breaks are based on the shaping of the (non-broken) original text; they
-        // are imprecise especially in the presence of kerning, ligatures, and Arabic shaping.
-        size_t i = mCandidates.back().offset;
-        width += mCharWidths[i++];
-        for (; i < offset; i++) {
-            float w = mCharWidths[i];
-            if (w > 0) {
-                cand.offset = i;
-                cand.preBreak = width;
-                cand.postBreak = width;
-                // postSpaceCount doesn't include trailing spaces
-                cand.preSpaceCount = postSpaceCount;
-                cand.postSpaceCount = postSpaceCount;
-                cand.penalty = SCORE_DESPERATE;
-                cand.hyphenType = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;
-#if VERBOSE_DEBUG
-                ALOGD("desperate cand: %zd %g:%g",
-                        mCandidates.size(), cand.postBreak, cand.preBreak);
-#endif
-                addCandidate(cand);
-                width += w;
-            }
-        }
-    }
-
-    cand.offset = offset;
-    cand.preBreak = preBreak;
-    cand.postBreak = postBreak;
-    cand.penalty = penalty;
-    cand.preSpaceCount = preSpaceCount;
-    cand.postSpaceCount = postSpaceCount;
-    cand.hyphenType = hyph;
-#if VERBOSE_DEBUG
-    ALOGD("cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak);
-#endif
-    addCandidate(cand);
-}
-
-// Helper method for addCandidate()
-void LineBreaker::pushGreedyBreak() {
-    const Candidate& bestCandidate = mCandidates[mBestBreak];
-    pushBreak(bestCandidate.offset, bestCandidate.postBreak - mPreBreak,
-            mLastHyphenation | HyphenEdit::editForThisLine(bestCandidate.hyphenType));
-    mBestScore = SCORE_INFTY;
-#if VERBOSE_DEBUG
-    ALOGD("break: %d %g", mBreaks.back(), mWidths.back());
-#endif
-    mLastBreak = mBestBreak;
-    mPreBreak = bestCandidate.preBreak;
-    mLastHyphenation = HyphenEdit::editForNextLine(bestCandidate.hyphenType);
-}
-
-// TODO performance: could avoid populating mCandidates if greedy only
-void LineBreaker::addCandidate(Candidate cand) {
-    const size_t candIndex = mCandidates.size();
-    mCandidates.push_back(cand);
-
-    // mLastBreak is the index of the last line break we decided to do in mCandidates,
-    // and mPreBreak is its preBreak value. mBestBreak is the index of the best line breaking candidate
-    // we have found since then, and mBestScore is its penalty.
-    if (cand.postBreak - mPreBreak > currentLineWidth()) {
-        // This break would create an overfull line, pick the best break and break there (greedy)
-        if (mBestBreak == mLastBreak) {
-            // No good break has been found since last break. Break here.
-            mBestBreak = candIndex;
-        }
-        pushGreedyBreak();
-    }
-
-    while (mLastBreak != candIndex && cand.postBreak - mPreBreak > currentLineWidth()) {
-        // We should rarely come here. But if we are here, we have broken the line, but the
-        // remaining part still doesn't fit. We now need to break at the second best place after the
-        // last break, but we have not kept that information, so we need to go back and find it.
-        //
-        // In some really rare cases, postBreak - preBreak of a candidate itself may be over the
-        // current line width. We protect ourselves against an infinite loop in that case by
-        // checking that we have not broken the line at this candidate already.
-        for (size_t i = mLastBreak + 1; i < candIndex; i++) {
-            const float penalty = mCandidates[i].penalty;
-            if (penalty <= mBestScore) {
-                mBestBreak = i;
-                mBestScore = penalty;
-            }
-        }
-        if (mBestBreak == mLastBreak) {
-            // We didn't find anything good. Break here.
-            mBestBreak = candIndex;
-        }
-        pushGreedyBreak();
-    }
-
-    if (cand.penalty <= mBestScore) {
-        mBestBreak = candIndex;
-        mBestScore = cand.penalty;
-    }
-}
-
-void LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {
-    mBreaks.push_back(offset);
-    mWidths.push_back(width);
-    int flags = (mFirstTabIndex < mBreaks.back()) << kTab_Shift;
-    flags |= hyphenEdit;
-    mFlags.push_back(flags);
-    mFirstTabIndex = INT_MAX;
-}
-
-void LineBreaker::addReplacement(size_t start, size_t end, float width) {
-    mCharWidths[start] = width;
-    std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);
-    addStyleRun(nullptr, nullptr, FontStyle(), start, end, false);
-}
-
-// Get the width of a space. May return 0 if there are no spaces.
-// Note: if there are multiple different widths for spaces (for example, because of mixing of
-// fonts), it's only guaranteed to pick one.
-float LineBreaker::getSpaceWidth() const {
-    for (size_t i = 0; i < mTextBuf.size(); i++) {
-        if (isWordSpace(mTextBuf[i])) {
-            return mCharWidths[i];
-        }
-    }
-    return 0.0f;
-}
-
-float LineBreaker::currentLineWidth() const {
-    return mLineWidths.getLineWidth(mBreaks.size());
-}
-
-void LineBreaker::computeBreaksGreedy() {
-    // All breaks but the last have been added in addCandidate already.
-    size_t nCand = mCandidates.size();
-    if (nCand == 1 || mLastBreak != nCand - 1) {
-        pushBreak(mCandidates[nCand - 1].offset, mCandidates[nCand - 1].postBreak - mPreBreak,
-                mLastHyphenation);
-        // don't need to update mBestScore, because we're done
-#if VERBOSE_DEBUG
-        ALOGD("final break: %d %g", mBreaks.back(), mWidths.back());
-#endif
-    }
-}
-
-// Follow "prev" links in mCandidates array, and copy to result arrays.
-void LineBreaker::finishBreaksOptimal() {
-    // clear existing greedy break result
-    mBreaks.clear();
-    mWidths.clear();
-    mFlags.clear();
-    size_t nCand = mCandidates.size();
-    size_t prev;
-    for (size_t i = nCand - 1; i > 0; i = prev) {
-        prev = mCandidates[i].prev;
-        mBreaks.push_back(mCandidates[i].offset);
-        mWidths.push_back(mCandidates[i].postBreak - mCandidates[prev].preBreak);
-        int flags = HyphenEdit::editForThisLine(mCandidates[i].hyphenType);
-        if (prev > 0) {
-            flags |= HyphenEdit::editForNextLine(mCandidates[prev].hyphenType);
-        }
-        mFlags.push_back(flags);
-    }
-    std::reverse(mBreaks.begin(), mBreaks.end());
-    std::reverse(mWidths.begin(), mWidths.end());
-    std::reverse(mFlags.begin(), mFlags.end());
-}
-
-void LineBreaker::computeBreaksOptimal(bool isRectangle) {
-    size_t active = 0;
-    size_t nCand = mCandidates.size();
-    float width = mLineWidths.getLineWidth(0);
-    float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f;
-
-    // "i" iterates through candidates for the end of the line.
-    for (size_t i = 1; i < nCand; i++) {
-        bool atEnd = i == nCand - 1;
-        float best = SCORE_INFTY;
-        size_t bestPrev = 0;
-        size_t lineNumberLast = 0;
-
-        if (!isRectangle) {
-            size_t lineNumberLast = mCandidates[active].lineNumber;
-            width = mLineWidths.getLineWidth(lineNumberLast);
-        }
-        ParaWidth leftEdge = mCandidates[i].postBreak - width;
-        float bestHope = 0;
-
-        // "j" iterates through candidates for the beginning of the line.
-        for (size_t j = active; j < i; j++) {
-            if (!isRectangle) {
-                size_t lineNumber = mCandidates[j].lineNumber;
-                if (lineNumber != lineNumberLast) {
-                    float widthNew = mLineWidths.getLineWidth(lineNumber);
-                    if (widthNew != width) {
-                        leftEdge = mCandidates[i].postBreak - width;
-                        bestHope = 0;
-                        width = widthNew;
-                    }
-                    lineNumberLast = lineNumber;
-                }
-            }
-            float jScore = mCandidates[j].score;
-            if (jScore + bestHope >= best) continue;
-            float delta = mCandidates[j].preBreak - leftEdge;
-
-            // compute width score for line
-
-            // Note: the "bestHope" optimization makes the assumption that, when delta is
-            // non-negative, widthScore will increase monotonically as successive candidate
-            // breaks are considered.
-            float widthScore = 0.0f;
-            float additionalPenalty = 0.0f;
-            if ((atEnd || !mJustified) && delta < 0) {
-                widthScore = SCORE_OVERFULL;
-            } else if (atEnd && mStrategy != kBreakStrategy_Balanced) {
-                // increase penalty for hyphen on last line
-                additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty;
-            } else {
-                widthScore = delta * delta;
-                if (delta < 0) {
-                    if (-delta < maxShrink *
-                            (mCandidates[i].postSpaceCount - mCandidates[j].preSpaceCount)) {
-                        widthScore *= SHRINK_PENALTY_MULTIPLIER;
-                    } else {
-                        widthScore = SCORE_OVERFULL;
-                    }
-                }
-            }
-
-            if (delta < 0) {
-                active = j + 1;
-            } else {
-                bestHope = widthScore;
-            }
-
-            float score = jScore + widthScore + additionalPenalty;
-            if (score <= best) {
-                best = score;
-                bestPrev = j;
-            }
-        }
-        mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty;
-        mCandidates[i].prev = bestPrev;
-        mCandidates[i].lineNumber = mCandidates[bestPrev].lineNumber + 1;
-#if VERBOSE_DEBUG
-        ALOGD("break %zd: score=%g, prev=%zd", i, mCandidates[i].score, mCandidates[i].prev);
-#endif
-    }
-    finishBreaksOptimal();
-}
-
-size_t LineBreaker::computeBreaks() {
-    if (mStrategy == kBreakStrategy_Greedy) {
-        computeBreaksGreedy();
+LineBreakResult breakIntoLines(const U16StringPiece& textBuffer, BreakStrategy strategy,
+                               HyphenationFrequency frequency, bool justified,
+                               const MeasuredText& measuredText, const LineWidth& lineWidth,
+                               const TabStops& tabStops) {
+    if (strategy == BreakStrategy::Greedy || textBuffer.hasChar(CHAR_TAB)) {
+        return breakLineGreedy(textBuffer, measuredText, lineWidth, tabStops,
+                               frequency != HyphenationFrequency::None);
     } else {
-        computeBreaksOptimal(mLineWidths.isConstant());
+        return breakLineOptimal(textBuffer, measuredText, lineWidth, strategy, frequency,
+                                justified);
     }
-    return mBreaks.size();
-}
-
-void LineBreaker::finish() {
-    mWordBreaker.finish();
-    mWidth = 0;
-    mLineWidths.clear();
-    mCandidates.clear();
-    mBreaks.clear();
-    mWidths.clear();
-    mFlags.clear();
-    if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {
-        mTextBuf.clear();
-        mTextBuf.shrink_to_fit();
-        mCharWidths.clear();
-        mCharWidths.shrink_to_fit();
-        mHyphBuf.clear();
-        mHyphBuf.shrink_to_fit();
-        mCandidates.shrink_to_fit();
-        mBreaks.shrink_to_fit();
-        mWidths.shrink_to_fit();
-        mFlags.shrink_to_fit();
-    }
-    mStrategy = kBreakStrategy_Greedy;
-    mHyphenationFrequency = kHyphenationFrequency_Normal;
-    mLinePenalty = 0.0f;
-    mJustified = false;
 }
 
 }  // namespace minikin
diff --git a/libs/minikin/LineBreakerUtil.cpp b/libs/minikin/LineBreakerUtil.cpp
new file mode 100644
index 0000000..920a8d0
--- /dev/null
+++ b/libs/minikin/LineBreakerUtil.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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 "LineBreakerUtil.h"
+
+namespace minikin {
+
+// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for
+// unreasonably long words. This is somewhat of a heuristic because extremely long words are
+// possible in some languages. This does mean that very long real words can get broken by
+// desperate breaks, with no hyphens.
+constexpr size_t LONGEST_HYPHENATED_WORD = 45;
+
+// Hyphenates a string potentially containing non-breaking spaces.
+std::vector<HyphenationType> hyphenate(const U16StringPiece& str, const Hyphenator& hyphenator) {
+    std::vector<HyphenationType> out;
+    const size_t len = str.size();
+    out.resize(len);
+
+    // A word here is any consecutive string of non-NBSP characters.
+    bool inWord = false;
+    size_t wordStart = 0;  // The initial value will never be accessed, but just in case.
+    for (size_t i = 0; i <= len; i++) {
+        if (i == len || str[i] == CHAR_NBSP) {
+            if (inWord) {
+                // A word just ended. Hyphenate it.
+                const U16StringPiece word = str.substr(Range(wordStart, i));
+                if (word.size() <= LONGEST_HYPHENATED_WORD) {
+                    hyphenator.hyphenate(word, out.data() + wordStart);
+                } else {  // Word is too long. Inefficient to hyphenate.
+                    out.insert(out.end(), word.size(), HyphenationType::DONT_BREAK);
+                }
+                inWord = false;
+            }
+            if (i < len) {
+                // Insert one DONT_BREAK for the NBSP.
+                out.push_back(HyphenationType::DONT_BREAK);
+            }
+        } else if (!inWord) {
+            inWord = true;
+            wordStart = i;
+        }
+    }
+    return out;
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h
new file mode 100644
index 0000000..64f4371
--- /dev/null
+++ b/libs/minikin/LineBreakerUtil.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 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_LINE_BREAKER_UTIL_H
+#define MINIKIN_LINE_BREAKER_UTIL_H
+
+#include <vector>
+
+#include "minikin/Hyphenator.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LayoutUtils.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+// ParaWidth is used to hold cumulative width from beginning of paragraph. Note that for very large
+// paragraphs, accuracy could degrade using only 32-bit float. Note however that float is used
+// extensively on the Java side for this. This is a typedef so that we can easily change it based
+// on performance/accuracy tradeoff.
+typedef double ParaWidth;
+
+// Hyphenates a string potentially containing non-breaking spaces.
+std::vector<HyphenationType> hyphenate(const U16StringPiece& string, const Hyphenator& hypenator);
+
+// This function determines whether a character is a space that disappears at end of line.
+// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]], plus '\n'.
+// Note: all such characters are in the BMP, so it's ok to use code units for this.
+inline bool isLineEndSpace(uint16_t c) {
+    return c == '\n' || c == ' '                           // SPACE
+           || c == 0x1680                                  // OGHAM SPACE MARK
+           || (0x2000 <= c && c <= 0x200A && c != 0x2007)  // EN QUAD, EM QUAD, EN SPACE, EM SPACE,
+           // THREE-PER-EM SPACE, FOUR-PER-EM SPACE,
+           // SIX-PER-EM SPACE, PUNCTUATION SPACE,
+           // THIN SPACE, HAIR SPACE
+           || c == 0x205F  // MEDIUM MATHEMATICAL SPACE
+           || 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];
+}
+
+// Retrieves hyphenation break points from a word.
+inline void populateHyphenationPoints(
+        const U16StringPiece& textBuf,        // A text buffer.
+        const Run& run,                       // A run of this region.
+        const Hyphenator& hyphenator,         // A hyphenator to be used for hyphenation.
+        const Range& contextRange,            // A context range for measuring hyphenated piece.
+        const Range& hyphenationTargetRange,  // An actual range for the hyphenation target.
+        std::vector<HyphenBreak>* out,        // An output to be appended.
+        LayoutPieces* pieces) {               // An output of layout pieces. Maybe null.
+    if (!run.getRange().contains(contextRange) || !contextRange.contains(hyphenationTargetRange)) {
+        return;
+    }
+
+    const std::vector<HyphenationType> hyphenResult =
+            hyphenate(textBuf.substr(hyphenationTargetRange), hyphenator);
+    for (uint32_t i = hyphenationTargetRange.getStart(); i < hyphenationTargetRange.getEnd(); ++i) {
+        const HyphenationType hyph = hyphenResult[hyphenationTargetRange.toRangeOffset(i)];
+        if (hyph == HyphenationType::DONT_BREAK) {
+            continue;  // Not a hyphenation point.
+        }
+
+        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);
+
+        out->emplace_back(i, hyph, first, second);
+    }
+}
+
+// Processes and retrieve informations from characters in the paragraph.
+struct CharProcessor {
+    // The number of spaces.
+    uint32_t rawSpaceCount = 0;
+
+    // The number of spaces minus trailing spaces.
+    uint32_t effectiveSpaceCount = 0;
+
+    // The sum of character width from the paragraph start.
+    ParaWidth sumOfCharWidths = 0.0;
+
+    // The sum of character width from the paragraph start minus trailing line end spaces.
+    // This means that the line width from the paragraph start if we decided break now.
+    ParaWidth effectiveWidth = 0.0;
+
+    // The total amount of character widths at the previous word break point.
+    ParaWidth sumOfCharWidthsAtPrevWordBreak = 0.0;
+
+    // The next word break offset.
+    uint32_t nextWordBreak = 0;
+
+    // The previous word break offset.
+    uint32_t prevWordBreak = 0;
+
+    // The width of a space. May be 0 if there are no spaces.
+    // Note: if there are multiple different widths for spaces (for example, because of mixing of
+    // fonts), it's only guaranteed to pick one.
+    float spaceWidth = 0.0f;
+
+    // The current hyphenator.
+    const Hyphenator* hyphenator = nullptr;
+
+    // Retrieve the current word range.
+    inline Range wordRange() const { return breaker.wordRange(); }
+
+    // Retrieve the current context range.
+    inline Range contextRange() const { return Range(prevWordBreak, nextWordBreak); }
+
+    // Returns the width from the last word break point.
+    inline ParaWidth widthFromLastWordBreak() const {
+        return effectiveWidth - sumOfCharWidthsAtPrevWordBreak;
+    }
+
+    // Returns the break penalty for the current word break point.
+    inline int wordBreakPenalty() const { return breaker.breakBadness(); }
+
+    CharProcessor(const U16StringPiece& text) { breaker.setText(text.data(), text.size()); }
+
+    // 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);
+            nextWordBreak = breaker.followingWithLocale(locale, run.getRange().getStart());
+            hyphenator = HyphenatorMap::lookup(locale);
+            localeListId = newLocaleListId;
+        }
+    }
+
+    // Process one character.
+    void feedChar(uint32_t idx, uint16_t c, float w) {
+        if (idx == nextWordBreak) {
+            prevWordBreak = nextWordBreak;
+            nextWordBreak = breaker.next();
+            sumOfCharWidthsAtPrevWordBreak = sumOfCharWidths;
+        }
+        if (isWordSpace(c)) {
+            rawSpaceCount += 1;
+            spaceWidth = w;
+        }
+        sumOfCharWidths += w;
+        if (isLineEndSpace(c)) {
+            // If we break a line on a line-ending space, that space goes away. So postBreak
+            // and postSpaceCount, which keep the width and number of spaces if we decide to
+            // break at this point, don't need to get adjusted.
+        } else {
+            effectiveSpaceCount = rawSpaceCount;
+            effectiveWidth = sumOfCharWidths;
+        }
+    }
+
+private:
+    // The current locale list id.
+    uint32_t localeListId = LocaleListCache::kInvalidListId;
+
+    WordBreaker breaker;
+};
+}  // namespace minikin
+
+#endif  // MINIKIN_LINE_BREAKER_UTIL_H
diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp
new file mode 100644
index 0000000..4209413
--- /dev/null
+++ b/libs/minikin/Locale.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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 "Locale.h"
+
+#include <algorithm>
+
+#include <hb.h>
+
+#include "minikin/LocaleList.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "StringPiece.h"
+
+namespace minikin {
+
+constexpr uint32_t FIVE_BITS = 0x1f;
+
+uint32_t registerLocaleList(const std::string& locales) {
+    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) {
+    if (bufLen < subtagLen) {
+        return false;
+    }
+    if (strncmp(buf, subtag, subtagLen) != 0) {
+        return false;  // no match between two strings
+    }
+    return (bufLen == subtagLen || buf[subtagLen] == '\0' || buf[subtagLen] == '-' ||
+            buf[subtagLen] == '_');
+}
+
+// Pack the three letter code into 15 bits and stored to 16 bit integer. The highest bit is 0.
+// For the region code, the letters must be all digits in three letter case, so the number of
+// possible values are 10. For the language code, the letters must be all small alphabets, so the
+// number of possible values are 26. Thus, 5 bits are sufficient for each case and we can pack the
+// three letter language code or region code to 15 bits.
+//
+// In case of two letter code, use fullbit(0x1f) for the first letter instead.
+static uint16_t packLanguageOrRegion(const StringPiece& in, uint8_t twoLetterBase,
+                                     uint8_t threeLetterBase) {
+    if (in.length() == 2) {
+        return 0x7c00u |  // 0x1fu << 10
+               (uint16_t)(in[0] - twoLetterBase) << 5 | (uint16_t)(in[1] - twoLetterBase);
+    } else {
+        return ((uint16_t)(in[0] - threeLetterBase) << 10) |
+               (uint16_t)(in[1] - threeLetterBase) << 5 | (uint16_t)(in[2] - threeLetterBase);
+    }
+}
+
+static size_t unpackLanguageOrRegion(uint16_t in, char* out, uint8_t twoLetterBase,
+                                     uint8_t threeLetterBase) {
+    uint8_t first = (in >> 10) & FIVE_BITS;
+    uint8_t second = (in >> 5) & FIVE_BITS;
+    uint8_t third = in & FIVE_BITS;
+
+    if (first == 0x1f) {
+        out[0] = second + twoLetterBase;
+        out[1] = third + twoLetterBase;
+        return 2;
+    } else {
+        out[0] = first + threeLetterBase;
+        out[1] = second + threeLetterBase;
+        out[2] = third + threeLetterBase;
+        return 3;
+    }
+}
+
+static uint16_t packLanguage(const StringPiece& in) {
+    return packLanguageOrRegion(in, 'a', 'a');
+}
+
+static size_t unpackLanguage(uint16_t in, char* out) {
+    return unpackLanguageOrRegion(in, out, 'a', 'a');
+}
+
+constexpr uint32_t packScript(char c1, char c2, char c3, char c4) {
+    constexpr char FIRST_LETTER_BASE = 'A';
+    constexpr char REST_LETTER_BASE = 'a';
+    return ((uint32_t)(c1 - FIRST_LETTER_BASE) << 15) | (uint32_t)(c2 - REST_LETTER_BASE) << 10 |
+           ((uint32_t)(c3 - REST_LETTER_BASE) << 5) | (uint32_t)(c4 - REST_LETTER_BASE);
+}
+
+constexpr uint32_t packScript(uint32_t script) {
+    return packScript(script >> 24, (script >> 16) & 0xff, (script >> 8) & 0xff, script & 0xff);
+}
+
+constexpr uint32_t unpackScript(uint32_t packedScript) {
+    constexpr char FIRST_LETTER_BASE = 'A';
+    constexpr char REST_LETTER_BASE = 'a';
+    const uint32_t first = (packedScript >> 15) + FIRST_LETTER_BASE;
+    const uint32_t second = ((packedScript >> 10) & FIVE_BITS) + REST_LETTER_BASE;
+    const uint32_t third = ((packedScript >> 5) & FIVE_BITS) + REST_LETTER_BASE;
+    const uint32_t fourth = (packedScript & FIVE_BITS) + REST_LETTER_BASE;
+
+    return first << 24 | second << 16 | third << 8 | fourth;
+}
+
+static uint16_t packRegion(const StringPiece& in) {
+    return packLanguageOrRegion(in, 'A', '0');
+}
+
+static size_t unpackRegion(uint16_t in, char* out) {
+    return unpackLanguageOrRegion(in, out, 'A', '0');
+}
+
+static inline bool isLowercase(char c) {
+    return 'a' <= c && c <= 'z';
+}
+
+static inline bool isUppercase(char c) {
+    return 'A' <= c && c <= 'Z';
+}
+
+static inline bool isDigit(char c) {
+    return '0' <= c && c <= '9';
+}
+
+// Returns true if the buffer is valid for language code.
+static inline bool isValidLanguageCode(const StringPiece& buffer) {
+    if (buffer.length() != 2 && buffer.length() != 3) return false;
+    if (!isLowercase(buffer[0])) return false;
+    if (!isLowercase(buffer[1])) return false;
+    if (buffer.length() == 3 && !isLowercase(buffer[2])) return false;
+    return true;
+}
+
+// Returns true if buffer is valid for script code. The length of buffer must be 4.
+static inline bool isValidScriptCode(const StringPiece& buffer) {
+    return buffer.size() == 4 && isUppercase(buffer[0]) && isLowercase(buffer[1]) &&
+           isLowercase(buffer[2]) && isLowercase(buffer[3]);
+}
+
+// Returns true if the buffer is valid for region code.
+static inline bool isValidRegionCode(const StringPiece& buffer) {
+    return (buffer.size() == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||
+           (buffer.size() == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) && isDigit(buffer[2]));
+}
+
+// Parse BCP 47 language identifier into internal structure
+Locale::Locale(const StringPiece& input) : Locale() {
+    SplitIterator it(input, '-');
+
+    StringPiece language = it.next();
+    if (isValidLanguageCode(language)) {
+        mLanguage = packLanguage(language);
+    } else {
+        // We don't understand anything other than two-letter or three-letter
+        // language codes, so we skip parsing the rest of the string.
+        return;
+    }
+
+    if (!it.hasNext()) {
+        return;  // Language code only.
+    }
+    StringPiece token = it.next();
+
+    if (isValidScriptCode(token)) {
+        mScript = packScript(token[0], token[1], token[2], token[3]);
+        mSubScriptBits = scriptToSubScriptBits(mScript);
+
+        if (!it.hasNext()) {
+            goto finalize;  // No variant, emoji subtag and region code.
+        }
+        token = it.next();
+    }
+
+    if (isValidRegionCode(token)) {
+        mRegion = packRegion(token);
+
+        if (!it.hasNext()) {
+            goto finalize;  // No variant or emoji subtag.
+        }
+        token = it.next();
+    }
+
+    if (language == "de") {  // We are only interested in German variants.
+        if (token == "1901") {
+            mVariant = Variant::GERMAN_1901_ORTHOGRAPHY;
+        } else if (token == "1996") {
+            mVariant = Variant::GERMAN_1996_ORTHOGRAPHY;
+        }
+
+        if (mVariant != Variant::NO_VARIANT) {
+            if (!it.hasNext()) {
+                goto finalize;  // No emoji subtag.
+            }
+
+            token = it.next();
+        }
+    }
+
+    mEmojiStyle = resolveEmojiStyle(input.data(), input.length());
+
+finalize:
+    if (mEmojiStyle == EmojiStyle::EMPTY) {
+        mEmojiStyle = scriptToEmojiStyle(mScript);
+    }
+}
+
+// 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;
+    if (length >= kMinSubtagLength) {
+        static const char kPrefix[] = "-u-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 (isEmojiSubtag(pos, remainingLength, "emoji", 5)) {
+                return EmojiStyle::EMOJI;
+            } else if (isEmojiSubtag(pos, remainingLength, "text", 4)) {
+                return EmojiStyle::TEXT;
+            } else if (isEmojiSubtag(pos, remainingLength, "default", 7)) {
+                return EmojiStyle::DEFAULT;
+            }
+        }
+    }
+    return EmojiStyle::EMPTY;
+}
+
+EmojiStyle Locale::scriptToEmojiStyle(uint32_t script) {
+    // If no emoji subtag was provided, resolve the emoji style from script code.
+    if (script == packScript('Z', 's', 'y', 'e')) {
+        return EmojiStyle::EMOJI;
+    } else if (script == packScript('Z', 's', 'y', 'm')) {
+        return EmojiStyle::TEXT;
+    }
+    return EmojiStyle::EMPTY;
+}
+
+// static
+uint8_t Locale::scriptToSubScriptBits(uint32_t script) {
+    uint8_t subScriptBits = 0u;
+    switch (script) {
+        case packScript('B', 'o', 'p', 'o'):
+            subScriptBits = kBopomofoFlag;
+            break;
+        case packScript('H', 'a', 'n', 'g'):
+            subScriptBits = kHangulFlag;
+            break;
+        case packScript('H', 'a', 'n', 'b'):
+            // Bopomofo is almost exclusively used in Taiwan.
+            subScriptBits = kHanFlag | kBopomofoFlag;
+            break;
+        case packScript('H', 'a', 'n', 'i'):
+            subScriptBits = kHanFlag;
+            break;
+        case packScript('H', 'a', 'n', 's'):
+            subScriptBits = kHanFlag | kSimplifiedChineseFlag;
+            break;
+        case packScript('H', 'a', 'n', 't'):
+            subScriptBits = kHanFlag | kTraditionalChineseFlag;
+            break;
+        case packScript('H', 'i', 'r', 'a'):
+            subScriptBits = kHiraganaFlag;
+            break;
+        case packScript('H', 'r', 'k', 't'):
+            subScriptBits = kKatakanaFlag | kHiraganaFlag;
+            break;
+        case packScript('J', 'p', 'a', 'n'):
+            subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;
+            break;
+        case packScript('K', 'a', 'n', 'a'):
+            subScriptBits = kKatakanaFlag;
+            break;
+        case packScript('K', 'o', 'r', 'e'):
+            subScriptBits = kHanFlag | kHangulFlag;
+            break;
+    }
+    return subScriptBits;
+}
+
+std::string Locale::getString() const {
+    char buf[24];
+    size_t i;
+    if (mLanguage == NO_LANGUAGE) {
+        buf[0] = 'u';
+        buf[1] = 'n';
+        buf[2] = 'd';
+        i = 3;
+    } else {
+        i = unpackLanguage(mLanguage, buf);
+    }
+    if (mScript != NO_SCRIPT) {
+        uint32_t rawScript = unpackScript(mScript);
+        buf[i++] = '-';
+        buf[i++] = (rawScript >> 24) & 0xFFu;
+        buf[i++] = (rawScript >> 16) & 0xFFu;
+        buf[i++] = (rawScript >> 8) & 0xFFu;
+        buf[i++] = rawScript & 0xFFu;
+    }
+    if (mRegion != NO_REGION) {
+        buf[i++] = '-';
+        i += unpackRegion(mRegion, buf + i);
+    }
+    if (mVariant != Variant::NO_VARIANT) {
+        buf[i++] = '-';
+        buf[i++] = '1';
+        buf[i++] = '9';
+        switch (mVariant) {
+            case Variant::GERMAN_1901_ORTHOGRAPHY:
+                buf[i++] = '0';
+                buf[i++] = '1';
+                break;
+            case Variant::GERMAN_1996_ORTHOGRAPHY:
+                buf[i++] = '9';
+                buf[i++] = '6';
+                break;
+            default:
+                MINIKIN_ASSERT(false, "Must not reached.");
+        }
+    }
+    return std::string(buf, i);
+}
+
+Locale Locale::getPartialLocale(SubtagBits bits) const {
+    Locale subLocale;
+    if ((bits & SubtagBits::LANGUAGE) != SubtagBits::EMPTY) {
+        subLocale.mLanguage = mLanguage;
+    } else {
+        subLocale.mLanguage = packLanguage("und");
+    }
+    if ((bits & SubtagBits::SCRIPT) != SubtagBits::EMPTY) {
+        subLocale.mScript = mScript;
+        subLocale.mSubScriptBits = mSubScriptBits;
+    }
+    if ((bits & SubtagBits::REGION) != SubtagBits::EMPTY) {
+        subLocale.mRegion = mRegion;
+    }
+    if ((bits & SubtagBits::VARIANT) != SubtagBits::EMPTY) {
+        subLocale.mVariant = mVariant;
+    }
+    if ((bits & SubtagBits::EMOJI) != SubtagBits::EMPTY) {
+        subLocale.mEmojiStyle = mEmojiStyle;
+    }
+    return subLocale;
+}
+
+bool Locale::isEqualScript(const Locale& other) const {
+    return other.mScript == mScript;
+}
+
+// static
+bool Locale::supportsScript(uint8_t providedBits, uint8_t requestedBits) {
+    return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;
+}
+
+bool Locale::supportsHbScript(hb_script_t script) const {
+    static_assert(unpackScript(packScript('J', 'p', 'a', 'n')) == HB_TAG('J', 'p', 'a', 'n'),
+                  "The Minikin script and HarfBuzz hb_script_t have different encodings.");
+    uint32_t packedScript = packScript(script);
+    if (packedScript == mScript) return true;
+    return supportsScript(mSubScriptBits, scriptToSubScriptBits(packedScript));
+}
+
+int Locale::calcScoreFor(const LocaleList& supported) const {
+    bool languageScriptMatch = false;
+    bool subtagMatch = false;
+    bool scriptMatch = false;
+
+    for (size_t i = 0; i < supported.size(); ++i) {
+        if (mEmojiStyle != EmojiStyle::EMPTY && mEmojiStyle == supported[i].mEmojiStyle) {
+            subtagMatch = true;
+            if (mLanguage == supported[i].mLanguage) {
+                return 4;
+            }
+        }
+        if (isEqualScript(supported[i]) ||
+            supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {
+            scriptMatch = true;
+            if (mLanguage == supported[i].mLanguage) {
+                languageScriptMatch = true;
+            }
+        }
+    }
+
+    if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {
+        scriptMatch = true;
+        if (mLanguage == supported[0].mLanguage && supported.isAllTheSameLocale()) {
+            return 3;
+        }
+    }
+
+    if (languageScriptMatch) {
+        return 3;
+    } else if (subtagMatch) {
+        return 2;
+    } else if (scriptMatch) {
+        return 1;
+    }
+    return 0;
+}
+
+static hb_language_t buildHbLanguage(const Locale& locale) {
+    return locale.isSupported() ? hb_language_from_string(locale.getString().c_str(), -1)
+                                : HB_LANGUAGE_INVALID;
+}
+
+LocaleList::LocaleList(std::vector<Locale>&& locales) : mLocales(std::move(locales)) {
+    mIsAllTheSameLocale = true;
+    mUnionOfSubScriptBits = 0u;
+    mHbLangs.reserve(mLocales.size());
+    mEmojiStyle = EmojiStyle::EMPTY;
+    const auto firstLanguage = mLocales.empty() ? NO_LANGUAGE : mLocales[0].mLanguage;
+    for (const Locale& locale : mLocales) {
+        mUnionOfSubScriptBits |= locale.mSubScriptBits;
+        if (mIsAllTheSameLocale && firstLanguage != locale.mLanguage) {
+            mIsAllTheSameLocale = false;
+        }
+        mHbLangs.push_back(buildHbLanguage(locale));
+        if (mEmojiStyle == EmojiStyle::EMPTY) {
+            mEmojiStyle = locale.getEmojiStyle();
+        }
+    }
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h
new file mode 100644
index 0000000..f030f92
--- /dev/null
+++ b/libs/minikin/Locale.h
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+
+#ifndef MINIKIN_LOCALE_LIST_H
+#define MINIKIN_LOCALE_LIST_H
+
+#include <string>
+#include <vector>
+
+#include <hb.h>
+
+#include "StringPiece.h"
+
+namespace minikin {
+
+// Due to the limits in font fallback score calculation, we can't use anything more than 12 locales.
+const size_t FONT_LOCALE_LIMIT = 12;
+
+// The language or region code is encoded to 15 bits.
+constexpr uint16_t NO_LANGUAGE = 0x7fff;
+constexpr uint16_t NO_REGION = 0x7fff;
+// The script code is encoded to 20 bits.
+constexpr uint32_t NO_SCRIPT = 0xfffff;
+
+class LocaleList;
+
+// Enum for making sub-locale from FontLangauge.
+enum class SubtagBits : uint8_t {
+    EMPTY = 0b00000000,
+    LANGUAGE = 0b00000001,
+    SCRIPT = 0b00000010,
+    REGION = 0b00000100,
+    VARIANT = 0b00001000,
+    EMOJI = 0b00010000,
+    ALL = 0b00011111,
+};
+
+inline constexpr SubtagBits operator&(SubtagBits l, SubtagBits r) {
+    return static_cast<SubtagBits>(static_cast<uint8_t>(l) & static_cast<uint8_t>(r));
+}
+inline constexpr SubtagBits operator|(SubtagBits l, SubtagBits r) {
+    return static_cast<SubtagBits>(static_cast<uint8_t>(l) | static_cast<uint8_t>(r));
+}
+
+// Enum for emoji style.
+enum class EmojiStyle : uint8_t {
+    EMPTY = 0,    // No emoji style is specified.
+    DEFAULT = 1,  // Default emoji style is specified.
+    EMOJI = 2,    // Emoji (color) emoji style is specified.
+    TEXT = 3,     // Text (black/white) emoji style is specified.
+};
+
+// 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
+        NO_VARIANT = 0x0000,
+        GERMAN_1901_ORTHOGRAPHY = 0x0001,
+        GERMAN_1996_ORTHOGRAPHY = 0x0002,
+    };
+
+    // Default constructor creates the unsupported locale.
+    Locale()
+            : mScript(NO_SCRIPT),
+              mLanguage(NO_LANGUAGE),
+              mRegion(NO_REGION),
+              mSubScriptBits(0ul),
+              mVariant(Variant::NO_VARIANT),
+              mEmojiStyle(EmojiStyle::EMPTY) {}
+
+    // Parse from string
+    Locale(const StringPiece& buf);
+
+    bool operator==(const Locale other) const {
+        return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage &&
+               mRegion == other.mRegion && mVariant == other.mVariant &&
+               mEmojiStyle == other.mEmojiStyle;
+    }
+
+    bool operator!=(const Locale other) const { return !(*this == other); }
+
+    inline bool hasLanguage() const { return mLanguage != NO_LANGUAGE; }
+    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 hasEmojiStyle() const { return mEmojiStyle != EmojiStyle::EMPTY; }
+
+    inline bool isSupported() const {
+        return hasLanguage() || hasScript() || hasRegion() || hasVariant() || hasEmojiStyle();
+    }
+
+    inline bool isUnsupported() const { return !isSupported(); }
+
+    EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
+
+    bool isEqualScript(const Locale& other) const;
+
+    // Returns true if this script supports the given script. For example, ja-Jpan supports Hira,
+    // ja-Hira doesn't support Jpan.
+    bool supportsHbScript(hb_script_t script) const;
+
+    std::string getString() const;
+
+    // Calculates a matching score. This score represents how well the input locales cover this
+    // locale. The maximum score in the locale list is returned.
+    // 0 = no match, 1 = script match, 2 = script and primary language match.
+    int calcScoreFor(const LocaleList& supported) const;
+
+    uint64_t getIdentifier() const {
+        return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) |
+               ((uint64_t)mEmojiStyle << 12) | (uint64_t)mVariant;
+    }
+
+    Locale getPartialLocale(SubtagBits bits) const;
+
+private:
+    friend class LocaleList;  // for LocaleList constructor
+
+    // ISO 15924 compliant script code. The 4 chars script code are packed into a 20 bit integer.
+    // If not specified, this is kInvalidScript.
+    uint32_t mScript;
+
+    // ISO 639-1 or ISO 639-2 compliant language code.
+    // The two- or three-letter language code is packed into a 15 bit integer.
+    // mLanguage = 0 means the Locale is unsupported.
+    uint16_t mLanguage;
+
+    // ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit region code is
+    // packed into a 15 bit integer.
+    uint16_t mRegion;
+
+    // For faster comparing, use 7 bits for specific scripts.
+    static const uint8_t kBopomofoFlag = 1u;
+    static const uint8_t kHanFlag = 1u << 1;
+    static const uint8_t kHangulFlag = 1u << 2;
+    static const uint8_t kHiraganaFlag = 1u << 3;
+    static const uint8_t kKatakanaFlag = 1u << 4;
+    static const uint8_t kSimplifiedChineseFlag = 1u << 5;
+    static const uint8_t kTraditionalChineseFlag = 1u << 6;
+    uint8_t mSubScriptBits;
+
+    Variant mVariant;
+
+    EmojiStyle mEmojiStyle;
+
+    static uint8_t scriptToSubScriptBits(uint32_t rawScript);
+
+    static EmojiStyle resolveEmojiStyle(const char* buf, size_t length);
+    static EmojiStyle scriptToEmojiStyle(uint32_t script);
+
+    // Returns true if the provide subscript bits has the requested subscript bits.
+    // Note that this function returns false if the requested subscript bits are empty.
+    static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);
+};
+
+// An immutable list of locale.
+class LocaleList {
+public:
+    explicit LocaleList(std::vector<Locale>&& locales);
+    LocaleList()
+            : mUnionOfSubScriptBits(0),
+              mIsAllTheSameLocale(false),
+              mEmojiStyle(EmojiStyle::EMPTY) {}
+    LocaleList(LocaleList&&) = default;
+
+    size_t size() const { return mLocales.size(); }
+    bool empty() const { return mLocales.empty(); }
+    const Locale& operator[](size_t n) const { return mLocales[n]; }
+
+    hb_language_t getHbLanguage(size_t n) const { return mHbLangs[n]; }
+
+    // Returns an effective emoji style of this locale list.
+    // The effective means the first non empty emoji style in the list.
+    EmojiStyle getEmojiStyle() const { return mEmojiStyle; }
+
+private:
+    friend struct Locale;  // for calcScoreFor
+
+    std::vector<Locale> mLocales;
+
+    // The languages to be passed to HarfBuzz shaper.
+    std::vector<hb_language_t> mHbLangs;
+    uint8_t mUnionOfSubScriptBits;
+    bool mIsAllTheSameLocale;
+    EmojiStyle mEmojiStyle;
+
+    uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }
+    bool isAllTheSameLocale() const { return mIsAllTheSameLocale; }
+
+    // Do not copy and assign.
+    LocaleList(const LocaleList&) = delete;
+    void operator=(const LocaleList&) = delete;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LOCALE_LIST_H
diff --git a/libs/minikin/LocaleListCache.cpp b/libs/minikin/LocaleListCache.cpp
new file mode 100644
index 0000000..c191ea6
--- /dev/null
+++ b/libs/minikin/LocaleListCache.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "LocaleListCache.h"
+
+#include <unordered_set>
+
+#include <log/log.h>
+#include <unicode/uloc.h>
+
+#include "Locale.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+
+const uint32_t LocaleListCache::kEmptyListId;
+
+// Returns the text length of output.
+static size_t toLanguageTag(char* output, size_t outSize, const StringPiece& locale) {
+    output[0] = '\0';
+    if (locale.empty()) {
+        return 0;
+    }
+
+    std::string localeString = locale.toString();  // ICU only understands C-style string.
+
+    size_t outLength = 0;
+    UErrorCode uErr = U_ZERO_ERROR;
+    outLength = uloc_canonicalize(localeString.c_str(), output, outSize, &uErr);
+    if (U_FAILURE(uErr)) {
+        // unable to build a proper locale identifier
+        ALOGD("uloc_canonicalize(\"%s\") failed: %s", localeString.c_str(), u_errorName(uErr));
+        output[0] = '\0';
+        return 0;
+    }
+
+    // Preserve "und" and "und-****" since uloc_addLikelySubtags changes "und" to "en-Latn-US".
+    if (strncmp(output, "und", 3) == 0 &&
+        (outLength == 3 || (outLength == 8 && output[3] == '_'))) {
+        output[3] = '-';  // to be language tag.
+        return outLength;
+    }
+
+    char likelyChars[ULOC_FULLNAME_CAPACITY];
+    uErr = U_ZERO_ERROR;
+    uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);
+    if (U_FAILURE(uErr)) {
+        // unable to build a proper locale identifier
+        ALOGD("uloc_addLikelySubtags(\"%s\") failed: %s", output, u_errorName(uErr));
+        output[0] = '\0';
+        return 0;
+    }
+
+    uErr = U_ZERO_ERROR;
+    outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr);
+    if (U_FAILURE(uErr)) {
+        // unable to build a proper locale identifier
+        ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr));
+        output[0] = '\0';
+        return 0;
+    }
+    return outLength;
+}
+
+static std::vector<Locale> parseLocaleList(const std::string& input) {
+    std::vector<Locale> result;
+    char langTag[ULOC_FULLNAME_CAPACITY];
+    std::unordered_set<uint64_t> seen;
+
+    SplitIterator it(input, ',');
+    while (it.hasNext()) {
+        StringPiece localeStr = it.next();
+        size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, localeStr);
+        Locale locale(StringPiece(langTag, length));
+        if (locale.isUnsupported()) {
+            continue;
+        }
+        const bool isNewLocale = seen.insert(locale.getIdentifier()).second;
+        if (!isNewLocale) {
+            continue;
+        }
+
+        result.push_back(locale);
+        if (result.size() >= FONT_LOCALE_LIMIT) {
+            break;
+        }
+    }
+    return result;
+}
+
+LocaleListCache::LocaleListCache() {
+    // Insert an empty locale list for mapping default locale list to kEmptyListId.
+    // The default locale list has only one Locale and it is the unsupported locale.
+    mLocaleLists.emplace_back();
+    mLocaleListLookupTable.insert(std::make_pair("", kEmptyListId));
+}
+
+uint32_t LocaleListCache::getIdInternal(const std::string& locales) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    const auto& it = mLocaleListLookupTable.find(locales);
+    if (it != mLocaleListLookupTable.end()) {
+        return it->second;
+    }
+
+    // Given locale list is not in cache. Insert it and return newly assigned ID.
+    const uint32_t nextId = mLocaleLists.size();
+    LocaleList fontLocales(parseLocaleList(locales));
+    if (fontLocales.empty()) {
+        return kEmptyListId;
+    }
+    mLocaleLists.push_back(std::move(fontLocales));
+    mLocaleListLookupTable.insert(std::make_pair(locales, nextId));
+    return nextId;
+}
+
+const LocaleList& LocaleListCache::getByIdInternal(uint32_t id) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    MINIKIN_ASSERT(id < mLocaleLists.size(), "Lookup by unknown locale list ID.");
+    return mLocaleLists[id];
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/LocaleListCache.h b/libs/minikin/LocaleListCache.h
new file mode 100644
index 0000000..61e3f81
--- /dev/null
+++ b/libs/minikin/LocaleListCache.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#ifndef MINIKIN_LOCALE_LIST_CACHE_H
+#define MINIKIN_LOCALE_LIST_CACHE_H
+
+#include <mutex>
+#include <unordered_map>
+
+#include "minikin/Macros.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+class LocaleListCache {
+public:
+    // A special ID for the empty locale list.
+    // This value must be 0 since the empty locale list is inserted into mLocaleLists by
+    // default.
+    const static uint32_t kEmptyListId = 0;
+
+    // A special ID for the invalid locale list.
+    const static uint32_t kInvalidListId = (uint32_t)(-1);
+
+    // Returns the locale list ID for the given string representation of LocaleList.
+    // Caller should acquire a lock before calling the method.
+    static inline uint32_t getId(const std::string& locales) {
+        return getInstance().getIdInternal(locales);
+    }
+
+    // Caller should acquire a lock before calling the method.
+    static inline const LocaleList& getById(uint32_t id) {
+        return getInstance().getByIdInternal(id);
+    }
+
+private:
+    LocaleListCache();  // Singleton
+    ~LocaleListCache() {}
+
+    uint32_t getIdInternal(const std::string& locales);
+    const LocaleList& getByIdInternal(uint32_t id);
+
+    // Caller should acquire a lock before calling the method.
+    static LocaleListCache& getInstance() {
+        static LocaleListCache instance;
+        return instance;
+    }
+
+    std::vector<LocaleList> mLocaleLists GUARDED_BY(mMutex);
+
+    // A map from the string representation of the font locale list to the ID.
+    std::unordered_map<std::string, uint32_t> mLocaleListLookupTable GUARDED_BY(mMutex);
+
+    std::mutex mMutex;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_LOCALE_LIST_CACHE_H
diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp
new file mode 100644
index 0000000..bbc6091
--- /dev/null
+++ b/libs/minikin/MeasuredText.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 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/MeasuredText.h"
+
+#include "minikin/Layout.h"
+
+#include "LayoutUtils.h"
+#include "LineBreakerUtil.h"
+
+namespace minikin {
+
+void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
+                           bool computeLayout) {
+    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);
+
+        if (!computeHyphenation || !run->canHyphenate()) {
+            continue;
+        }
+
+        proc.updateLocaleIfNecessary(*run);
+        for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+            proc.feedChar(i, textBuf[i], widths[i]);
+
+            const uint32_t nextCharOffset = i + 1;
+            if (nextCharOffset != proc.nextWordBreak) {
+                continue;  // Wait until word break point.
+            }
+
+            populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(),
+                                      proc.wordRange(), &hyphenBreaks, piecesOut);
+        }
+    }
+}
+
+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);
+}
+
+MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) {
+    MinikinRect rect;
+    float advance = 0.0f;
+    for (const auto& run : runs) {
+        const Range& runRange = run->getRange();
+        if (!Range::intersects(range, runRange)) {
+            continue;
+        }
+        std::pair<float, MinikinRect> next =
+                run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
+        MinikinRect nextRect = next.second;
+        nextRect.offset(advance, 0);
+        rect.join(nextRect);
+        advance += next.first;
+    }
+    return rect;
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp
index f0d15f2..5d110c4 100644
--- a/libs/minikin/Measurement.cpp
+++ b/libs/minikin/Measurement.cpp
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Minikin"
+#include "minikin/Measurement.h"
 
+#include <cfloat>
 #include <cmath>
-#include <unicode/uchar.h>
 
-#include <android/log.h>
-
-#include <minikin/GraphemeBreak.h>
-#include <minikin/Measurement.h>
+#include "minikin/GraphemeBreak.h"
 
 namespace minikin {
 
@@ -30,7 +27,7 @@
 // are separate.
 
 static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart,
-        size_t start, size_t count, size_t offset) {
+                           size_t start, size_t count, size_t offset) {
     float advance = 0.0f;
     size_t lastCluster = start;
     float clusterWidth = 0.0f;
@@ -54,8 +51,8 @@
         int numGraphemeClustersAfter = 0;
         for (size_t i = lastCluster; i < nextCluster; i++) {
             bool isAfter = i >= offset;
-            if (GraphemeBreak::isGraphemeBreak(
-                    advances + (start - layoutStart), buf, start, count, i)) {
+            if (GraphemeBreak::isGraphemeBreak(advances + (start - layoutStart), buf, start, count,
+                                               i)) {
                 numGraphemeClusters++;
                 if (isAfter) {
                     numGraphemeClustersAfter++;
@@ -70,7 +67,7 @@
 }
 
 float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
-        size_t offset) {
+                    size_t offset) {
     return getRunAdvance(advances, buf, start, start, count, offset);
 }
 
@@ -83,7 +80,7 @@
  * search within the cluster and grapheme breaks.
  */
 size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
-        float advance) {
+                           float advance) {
     float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
     size_t lastClusterStart = start, searchStart = start;
     for (size_t i = start; i < start + count; i++) {
@@ -108,7 +105,7 @@
             // "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
             float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i)
 
-                    + xSearchStart - advance;
+                          + xSearchStart - advance;
             if (std::abs(delta) < bestDist) {
                 bestDist = std::abs(delta);
                 best = i;
diff --git a/libs/minikin/MinikinInternal.cpp b/libs/minikin/MinikinInternal.cpp
index cfa43bc..d02f71f 100644
--- a/libs/minikin/MinikinInternal.cpp
+++ b/libs/minikin/MinikinInternal.cpp
@@ -13,34 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 // Definitions internal to Minikin
-#define LOG_TAG "Minikin"
 
-#include "MinikinInternal.h"
-#include "HbFontCache.h"
+#define LOG_TAG "Minikin"
 
 #include <log/log.h>
 
+#include "MinikinInternal.h"
+
 namespace minikin {
 
-android::Mutex gMinikinLock;
-
-void assertMinikinLocked() {
-#ifdef ENABLE_RACE_DETECTION
-    LOG_ALWAYS_FATAL_IF(gMinikinLock.tryLock() == 0);
-#endif
-}
-
-hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag) {
-    assertMinikinLocked();
-    hb_font_t* font = getHbFontLocked(minikinFont);
-    hb_face_t* face = hb_font_get_face(font);
-    hb_blob_t* blob = hb_face_reference_table(face, tag);
-    hb_font_destroy(font);
-    return blob;
-}
-
 inline static bool isBMPVariationSelector(uint32_t codePoint) {
     return VS1 <= codePoint && codePoint <= VS16;
 }
diff --git a/libs/minikin/MinikinInternal.h b/libs/minikin/MinikinInternal.h
index a59e55d..d90f099 100644
--- a/libs/minikin/MinikinInternal.h
+++ b/libs/minikin/MinikinInternal.h
@@ -20,23 +20,21 @@
 #define MINIKIN_INTERNAL_H
 
 #include <hb.h>
-
+#include <utils/Log.h>
 #include <utils/Mutex.h>
 
-#include <minikin/MinikinFont.h>
+#include "minikin/HbUtils.h"
+#include "minikin/MinikinFont.h"
 
 namespace minikin {
 
-// All external Minikin interfaces are designed to be thread-safe.
-// Presently, that's implemented by through a global lock, and having
-// all external interfaces take that lock.
+#ifdef ENABLE_ASSERTION
+#define MINIKIN_ASSERT(cond, ...) LOG_ALWAYS_FATAL_IF(!(cond), __VA_ARGS__)
+#else
+#define MINIKIN_ASSERT(cond, ...) ((void)0)
+#endif
 
-extern android::Mutex gMinikinLock;
-
-// Aborts if gMinikinLock is not acquired. Do nothing on the release build.
-void assertMinikinLocked();
-
-hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag);
+#define MINIKIN_NOT_REACHED(...) MINIKIN_ASSERT(false, __VA_ARGS__);
 
 constexpr uint32_t MAX_UNICODE_CODE_POINT = 0x10FFFF;
 
@@ -57,29 +55,24 @@
 // Note that this function returns false for Mongolian free variation selectors.
 bool isVariationSelector(uint32_t codePoint);
 
-// An RAII wrapper for hb_blob_t
+// An RAII accessor for hb_blob_t
 class HbBlob {
 public:
-    // Takes ownership of hb_blob_t object, caller is no longer
-    // responsible for calling hb_blob_destroy().
-    explicit HbBlob(hb_blob_t* blob) : mBlob(blob) {
+    HbBlob(const HbFaceUniquePtr& face, uint32_t tag)
+            : mBlob(hb_face_reference_table(face.get(), tag)) {}
+    HbBlob(const HbFontUniquePtr& font, uint32_t tag)
+            : mBlob(hb_face_reference_table(hb_font_get_face(font.get()), tag)) {}
+
+    inline const uint8_t* get() const {
+        return reinterpret_cast<const uint8_t*>(hb_blob_get_data(mBlob.get(), nullptr));
     }
 
-    ~HbBlob() {
-        hb_blob_destroy(mBlob);
-    }
+    inline size_t size() const { return (size_t)hb_blob_get_length(mBlob.get()); }
 
-    const uint8_t* get() const {
-        const char* data = hb_blob_get_data(mBlob, nullptr);
-        return reinterpret_cast<const uint8_t*>(data);
-    }
-
-    size_t size() const {
-        return (size_t)hb_blob_get_length(mBlob);
-    }
+    inline operator bool() const { return size() > 0; }
 
 private:
-    hb_blob_t* mBlob;
+    HbBlobUniquePtr mBlob;
 };
 
 }  // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.cpp b/libs/minikin/OptimalLineBreaker.cpp
new file mode 100644
index 0000000..5b3a6fc
--- /dev/null
+++ b/libs/minikin/OptimalLineBreaker.cpp
@@ -0,0 +1,432 @@
+/*
+ * 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 "OptimalLineBreaker.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "minikin/Characters.h"
+#include "minikin/Layout.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
+
+#include "HyphenatorMap.h"
+#include "LayoutUtils.h"
+#include "LineBreakerUtil.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+
+namespace {
+
+// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
+// constants are larger than any reasonable actual width score.
+constexpr float SCORE_INFTY = std::numeric_limits<float>::max();
+constexpr float SCORE_OVERFULL = 1e12f;
+constexpr float SCORE_DESPERATE = 1e10f;
+
+// Multiplier for hyphen penalty on last line.
+constexpr float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
+// Penalty assigned to each line break (to try to minimize number of lines)
+// TODO: when we implement full justification (so spaces can shrink and stretch), this is
+// probably not the most appropriate method.
+constexpr float LINE_PENALTY_MULTIPLIER = 2.0f;
+
+// Penalty assigned to shrinking the whitepsace.
+constexpr float SHRINK_PENALTY_MULTIPLIER = 4.0f;
+
+// Maximum amount that spaces can shrink, in justified text.
+constexpr float SHRINKABILITY = 1.0 / 3.0;
+
+// A single candidate break
+struct Candidate {
+    uint32_t offset;  // offset to text buffer, in code units
+
+    ParaWidth preBreak;       // width of text until this point, if we decide to not break here:
+                              // preBreak is used as an optimized way to calculate the width
+                              // between two candidates. The line width between two line break
+                              // candidates i and j is calculated as postBreak(j) - preBreak(i).
+    ParaWidth postBreak;      // width of text until this point, if we decide to break here
+    float penalty;            // penalty of this break (for example, hyphen penalty)
+    uint32_t preSpaceCount;   // preceding space count before breaking
+    uint32_t postSpaceCount;  // preceding space count after breaking
+    HyphenationType hyphenType;
+    bool isRtl;  // The direction of the bidi run containing or ending in this candidate
+
+    Candidate(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak, float penalty,
+              uint32_t preSpaceCount, uint32_t postSpaceCount, HyphenationType hyphenType,
+              bool isRtl)
+            : offset(offset),
+              preBreak(preBreak),
+              postBreak(postBreak),
+              penalty(penalty),
+              preSpaceCount(preSpaceCount),
+              postSpaceCount(postSpaceCount),
+              hyphenType(hyphenType),
+              isRtl(isRtl) {}
+};
+
+// A context of line break optimization.
+struct OptimizeContext {
+    // The break candidates.
+    std::vector<Candidate> candidates;
+
+    // The penalty for the number of lines.
+    float linePenalty = 0.0f;
+
+    // The width of a space. May be 0 if there are no spaces.
+    // Note: if there are multiple different widths for spaces (for example, because of mixing of
+    // fonts), it's only guaranteed to pick one.
+    float spaceWidth = 0.0f;
+
+    // Append desperate break point to the candidates.
+    inline void pushDesperate(uint32_t offset, ParaWidth sumOfCharWidths, uint32_t spaceCount,
+                              bool isRtl) {
+        candidates.emplace_back(offset, sumOfCharWidths, sumOfCharWidths, SCORE_DESPERATE,
+                                spaceCount, spaceCount,
+                                HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, isRtl);
+    }
+
+    // Append hyphenation break point to the candidates.
+    inline void pushHyphenation(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
+                                float penalty, uint32_t spaceCount, HyphenationType type,
+                                bool isRtl) {
+        candidates.emplace_back(offset, preBreak, postBreak, penalty, spaceCount, spaceCount, type,
+                                isRtl);
+    }
+
+    // Append word break point to the candidates.
+    inline void pushWordBreak(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
+                              float penalty, uint32_t preSpaceCount, uint32_t postSpaceCount,
+                              bool isRtl) {
+        candidates.emplace_back(offset, preBreak, postBreak, penalty, preSpaceCount, postSpaceCount,
+                                HyphenationType::DONT_BREAK, isRtl);
+    }
+
+    OptimizeContext() {
+        candidates.emplace_back(0, 0.0f, 0.0f, 0.0f, 0, 0, HyphenationType::DONT_BREAK, false);
+    }
+};
+
+// Compute the penalty for the run and returns penalty for hyphenation and number of lines.
+std::pair<float, float> computePenalties(const Run& run, const LineWidth& lineWidth,
+                                         HyphenationFrequency frequency, bool justified) {
+    float linePenalty = 0.0;
+    const MinikinPaint* paint = run.getPaint();
+    // a heuristic that seems to perform well
+    float hyphenPenalty = 0.5 * paint->size * paint->scaleX * lineWidth.getAt(0);
+    if (frequency == HyphenationFrequency::Normal) {
+        hyphenPenalty *= 4.0;  // TODO: Replace with a better value after some testing
+    }
+
+    if (justified) {
+        // Make hyphenation more aggressive for fully justified text (so that "normal" in
+        // justified mode is the same as "full" in ragged-right).
+        hyphenPenalty *= 0.25;
+    } else {
+        // Line penalty is zero for justified text.
+        linePenalty = hyphenPenalty * LINE_PENALTY_MULTIPLIER;
+    }
+
+    return std::make_pair(hyphenPenalty, linePenalty);
+}
+
+// Represents a desperate break point.
+struct DesperateBreak {
+    // The break offset.
+    uint32_t offset;
+
+    // The sum of the character width from the beginning of the word.
+    ParaWidth sumOfChars;
+
+    DesperateBreak(uint32_t offset, ParaWidth sumOfChars)
+            : offset(offset), sumOfChars(sumOfChars){};
+};
+
+// Retrieves desperate break points from a word.
+std::vector<DesperateBreak> populateDesperatePoints(const MeasuredText& measured,
+                                                    const Range& range) {
+    std::vector<DesperateBreak> out;
+    ParaWidth width = measured.widths[range.getStart()];
+    for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
+        const float w = measured.widths[i];
+        if (w == 0) {
+            continue;  // w == 0 means here is not a grapheme bounds. Don't break here.
+        }
+        out.emplace_back(i, width);
+        width += w;
+    }
+    return out;
+}
+
+// Append hyphenation break points and desperate break points.
+// If an offset is a both candidate for hyphenation and desperate break points, place desperate
+// break candidate first and hyphenation break points second since the result width of the desperate
+// break is shorter than hyphenation break.
+// This is important since DP in computeBreaksOptimal assumes that the result line width is
+// increased by break offset.
+void appendWithMerging(std::vector<HyphenBreak>::const_iterator hyIter,
+                       std::vector<HyphenBreak>::const_iterator endHyIter,
+                       const std::vector<DesperateBreak>& desperates, const CharProcessor& proc,
+                       float hyphenPenalty, bool isRtl, OptimizeContext* out) {
+    auto d = desperates.begin();
+    while (hyIter != endHyIter || d != desperates.end()) {
+        // If both hyphen breaks and desperate breaks point to the same offset, push desperate
+        // breaks first.
+        if (d != desperates.end() && (hyIter == endHyIter || d->offset <= hyIter->offset)) {
+            out->pushDesperate(d->offset, proc.sumOfCharWidthsAtPrevWordBreak + d->sumOfChars,
+                               proc.effectiveSpaceCount, isRtl);
+            d++;
+        } else {
+            out->pushHyphenation(hyIter->offset, proc.sumOfCharWidths - hyIter->second,
+                                 proc.sumOfCharWidthsAtPrevWordBreak + hyIter->first, hyphenPenalty,
+                                 proc.effectiveSpaceCount, hyIter->type, isRtl);
+            hyIter++;
+        }
+    }
+}
+
+// Enumerate all line break candidates.
+OptimizeContext populateCandidates(const U16StringPiece& textBuf, const MeasuredText& measured,
+                                   const LineWidth& lineWidth, HyphenationFrequency frequency,
+                                   bool isJustified) {
+    const ParaWidth minLineWidth = lineWidth.getMin();
+    CharProcessor proc(textBuf);
+
+    OptimizeContext result;
+
+    const bool doHyphenation = frequency != HyphenationFrequency::None;
+    auto hyIter = std::begin(measured.hyphenBreaks);
+
+    for (const auto& run : measured.runs) {
+        const bool isRtl = run->isRtl();
+        const Range& range = run->getRange();
+
+        // Compute penalty parameters.
+        float hyphenPenalty = 0.0f;
+        if (run->canHyphenate()) {
+            auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
+            hyphenPenalty = penalties.first;
+            result.linePenalty = std::max(penalties.second, result.linePenalty);
+        }
+
+        proc.updateLocaleIfNecessary(*run);
+
+        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]);
+
+            const uint32_t nextCharOffset = i + 1;
+            if (nextCharOffset != proc.nextWordBreak) {
+                continue;  // Wait until word break point.
+            }
+
+            // Add hyphenation and desperate break points.
+            std::vector<HyphenBreak> hyphenedBreaks;
+            std::vector<DesperateBreak> desperateBreaks;
+            const Range contextRange = proc.contextRange();
+
+            auto beginHyIter = hyIter;
+            while (hyIter != std::end(measured.hyphenBreaks) &&
+                   hyIter->offset < contextRange.getEnd()) {
+                hyIter++;
+            }
+            if (proc.widthFromLastWordBreak() > minLineWidth) {
+                desperateBreaks = populateDesperatePoints(measured, contextRange);
+            }
+            appendWithMerging(beginHyIter, doHyphenation ? hyIter : beginHyIter, desperateBreaks,
+                              proc, hyphenPenalty, isRtl, &result);
+
+            // We skip breaks for zero-width characters inside replacement spans.
+            if (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);
+            }
+        }
+    }
+    result.spaceWidth = proc.spaceWidth;
+    return result;
+}
+
+class LineBreakOptimizer {
+public:
+    LineBreakOptimizer() {}
+
+    LineBreakResult computeBreaks(const OptimizeContext& context, const U16StringPiece& textBuf,
+                                  const MeasuredText& measuredText, const LineWidth& lineWidth,
+                                  BreakStrategy strategy, bool justified);
+
+private:
+    // Data used to compute optimal line breaks
+    struct OptimalBreaksData {
+        float score;          // best score found for this break
+        uint32_t prev;        // index to previous break
+        uint32_t lineNumber;  // the computed line number of the candidate
+    };
+    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,
+        const std::vector<OptimalBreaksData>& breaksData,
+        const std::vector<Candidate>& candidates) {
+    LineBreakResult result;
+    const uint32_t nCand = candidates.size();
+    uint32_t prevIndex;
+    for (uint32_t i = nCand - 1; i > 0; i = prevIndex) {
+        prevIndex = breaksData[i].prev;
+        const Candidate& cand = candidates[i];
+        const Candidate& prev = candidates[prevIndex];
+
+        result.breakPoints.push_back(cand.offset);
+        result.widths.push_back(cand.postBreak - prev.preBreak);
+        MinikinExtent extent = computeMaxExtent(textBuf, measured, prev.offset, cand.offset);
+        result.ascents.push_back(extent.ascent);
+        result.descents.push_back(extent.descent);
+
+        const HyphenEdit edit =
+                packHyphenEdit(editForNextLine(prev.hyphenType), editForThisLine(cand.hyphenType));
+        result.flags.push_back(static_cast<int>(edit));
+    }
+    result.reverse();
+    return result;
+}
+
+LineBreakResult LineBreakOptimizer::computeBreaks(const OptimizeContext& context,
+                                                  const U16StringPiece& textBuf,
+                                                  const MeasuredText& measured,
+                                                  const LineWidth& lineWidth,
+                                                  BreakStrategy strategy, bool justified) {
+    const std::vector<Candidate>& candidates = context.candidates;
+    uint32_t active = 0;
+    const uint32_t nCand = candidates.size();
+    const float maxShrink = justified ? SHRINKABILITY * context.spaceWidth : 0.0f;
+
+    std::vector<OptimalBreaksData> breaksData;
+    breaksData.reserve(nCand);
+    breaksData.push_back({0.0, 0, 0});  // The first candidate is always at the first line.
+
+    // "i" iterates through candidates for the end of the line.
+    for (uint32_t i = 1; i < nCand; i++) {
+        const bool atEnd = i == nCand - 1;
+        float best = SCORE_INFTY;
+        uint32_t bestPrev = 0;
+
+        uint32_t lineNumberLast = breaksData[active].lineNumber;
+        float width = lineWidth.getAt(lineNumberLast);
+
+        ParaWidth leftEdge = candidates[i].postBreak - width;
+        float bestHope = 0;
+
+        // "j" iterates through candidates for the beginning of the line.
+        for (uint32_t j = active; j < i; j++) {
+            const uint32_t lineNumber = breaksData[j].lineNumber;
+            if (lineNumber != lineNumberLast) {
+                const float widthNew = lineWidth.getAt(lineNumber);
+                if (widthNew != width) {
+                    leftEdge = candidates[i].postBreak - width;
+                    bestHope = 0;
+                    width = widthNew;
+                }
+                lineNumberLast = lineNumber;
+            }
+            const float jScore = breaksData[j].score;
+            if (jScore + bestHope >= best) continue;
+            const float delta = candidates[j].preBreak - leftEdge;
+
+            // compute width score for line
+
+            // Note: the "bestHope" optimization makes the assumption that, when delta is
+            // non-negative, widthScore will increase monotonically as successive candidate
+            // breaks are considered.
+            float widthScore = 0.0f;
+            float additionalPenalty = 0.0f;
+            if ((atEnd || !justified) && delta < 0) {
+                widthScore = SCORE_OVERFULL;
+            } else if (atEnd && strategy != BreakStrategy::Balanced) {
+                // increase penalty for hyphen on last line
+                additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * candidates[j].penalty;
+            } else {
+                widthScore = delta * delta;
+                if (delta < 0) {
+                    if (-delta <
+                        maxShrink * (candidates[i].postSpaceCount - candidates[j].preSpaceCount)) {
+                        widthScore *= SHRINK_PENALTY_MULTIPLIER;
+                    } else {
+                        widthScore = SCORE_OVERFULL;
+                    }
+                }
+            }
+
+            if (delta < 0) {
+                active = j + 1;
+            } else {
+                bestHope = widthScore;
+            }
+
+            const float score = jScore + widthScore + additionalPenalty;
+            if (score <= best) {
+                best = score;
+                bestPrev = j;
+            }
+        }
+        breaksData.push_back({best + candidates[i].penalty + context.linePenalty,  // score
+                              bestPrev,                                            // prev
+                              breaksData[bestPrev].lineNumber + 1});               // lineNumber
+    }
+    return finishBreaksOptimal(textBuf, measured, breaksData, candidates);
+}
+
+}  // namespace
+
+LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
+                                 const LineWidth& lineWidth, BreakStrategy strategy,
+                                 HyphenationFrequency frequency, bool justified) {
+    if (textBuf.size() == 0) {
+        return LineBreakResult();
+    }
+    const OptimizeContext context =
+            populateCandidates(textBuf, measured, lineWidth, frequency, justified);
+    LineBreakOptimizer optimizer;
+    return optimizer.computeBreaks(context, textBuf, measured, lineWidth, strategy, justified);
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/OptimalLineBreaker.h b/libs/minikin/OptimalLineBreaker.h
new file mode 100644
index 0000000..f13e2a8
--- /dev/null
+++ b/libs/minikin/OptimalLineBreaker.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#ifndef MINIKIN_OPTIMAL_LINE_BREAKER_H
+#define MINIKIN_OPTIMAL_LINE_BREAKER_H
+
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/U16StringPiece.h"
+
+namespace minikin {
+
+LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
+                                 const LineWidth& lineWidthLimits, BreakStrategy strategy,
+                                 HyphenationFrequency frequency, bool justified);
+
+}  // namespace minikin
+
+#endif  // MINIKIN_OPTIMAL_LINE_BREAKER_H
diff --git a/libs/minikin/SparseBitSet.cpp b/libs/minikin/SparseBitSet.cpp
index 9fad6a0..66d6c02 100644
--- a/libs/minikin/SparseBitSet.cpp
+++ b/libs/minikin/SparseBitSet.cpp
@@ -14,14 +14,9 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "SparseBitSet"
+#include "minikin/SparseBitSet.h"
 
-#include <stddef.h>
-#include <string.h>
-
-#include <log/log.h>
-
-#include <minikin/SparseBitSet.h>
+#include "MinikinInternal.h"
 
 namespace minikin {
 
@@ -69,7 +64,7 @@
     for (size_t i = 0; i < nRanges; i++) {
         uint32_t start = ranges[i * 2];
         uint32_t end = ranges[i * 2 + 1];
-        LOG_ALWAYS_FATAL_IF(end < start);  // make sure range size is nonnegative
+        MINIKIN_ASSERT(start <= end, "Range size must be nonnegative");
         uint32_t startPage = start >> kLogValuesPerPage;
         uint32_t endPage = (end - 1) >> kLogValuesPerPage;
         if (startPage >= nonzeroPageEnd) {
@@ -85,11 +80,11 @@
         }
 
         size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
-            ((start & kPageMask) >> kLogBitsPerEl);
+                       ((start & kPageMask) >> kLogBitsPerEl);
         size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;
         if (nElements == 1) {
-            mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
-                (kElAllOnes << ((~end + 1) & kElMask));
+            mBitmaps[index] |=
+                    (kElAllOnes >> (start & kElMask)) & (kElAllOnes << ((~end + 1) & kElMask));
         } else {
             mBitmaps[index] |= kElAllOnes >> (start & kElMask);
             for (size_t j = 1; j < nElements - 1; j++) {
diff --git a/libs/minikin/StringPiece.h b/libs/minikin/StringPiece.h
new file mode 100644
index 0000000..befb312
--- /dev/null
+++ b/libs/minikin/StringPiece.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 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_STRING_PIECE_H
+#define MINIKIN_STRING_PIECE_H
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace minikin {
+
+class StringPiece {
+public:
+    StringPiece() : mData(nullptr), mLength(0) {}
+    StringPiece(const char* data) : mData(data), mLength(data == nullptr ? 0 : strlen(data)) {}
+    StringPiece(const char* data, size_t length) : mData(data), mLength(length) {}
+    StringPiece(const std::string& str) : mData(str.data()), mLength(str.size()) {}
+
+    inline const char* data() const { return mData; }
+    inline size_t length() const { return mLength; }
+    inline size_t size() const { return mLength; }
+    inline bool empty() const { return mLength == 0; }
+
+    inline char operator[](size_t i) const { return mData[i]; }
+
+    inline StringPiece substr(size_t from, size_t length) const {
+        return StringPiece(mData + from, length);
+    }
+
+    inline size_t find(size_t from, char c) const {
+        if (from >= mLength) {
+            return mLength;
+        }
+        const char* p = static_cast<const char*>(memchr(mData + from, c, mLength - from));
+        return p == nullptr ? mLength : p - mData;
+    }
+
+    std::string toString() const { return std::string(mData, mData + mLength); }
+
+private:
+    const char* mData;
+    size_t mLength;
+};
+
+inline bool operator==(const StringPiece& l, const StringPiece& r) {
+    const size_t len = l.size();
+    if (len != r.size()) {
+        return false;
+    }
+    const char* lData = l.data();
+    const char* rData = r.data();
+    if (lData == rData) {
+        return true;
+    }
+    return memcmp(lData, rData, len) == 0;
+}
+
+inline bool operator==(const StringPiece& l, const char* s) {
+    const size_t len = l.size();
+    if (len != strlen(s)) {
+        return false;
+    }
+    return memcmp(l.data(), s, len) == 0;
+}
+
+inline bool operator!=(const StringPiece& l, const StringPiece& r) {
+    return !(l == r);
+}
+
+inline bool operator!=(const StringPiece& l, const char* s) {
+    return !(l == s);
+}
+
+class SplitIterator {
+public:
+    SplitIterator(const StringPiece& string, char delimiter)
+            : mStarted(false), mCurrent(0), mString(string), mDelimiter(delimiter) {}
+
+    inline StringPiece next() {
+        if (!hasNext()) {
+            return StringPiece();
+        }
+        const size_t searchFrom = mStarted ? mCurrent + 1 : 0;
+        mStarted = true;
+        mCurrent = mString.find(searchFrom, mDelimiter);
+        return mString.substr(searchFrom, mCurrent - searchFrom);
+    }
+    inline bool hasNext() const { return mCurrent < mString.size(); }
+
+private:
+    bool mStarted;
+    size_t mCurrent;
+    StringPiece mString;
+    char mDelimiter;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_STRING_PIECE_H
diff --git a/libs/minikin/WordBreaker.cpp b/libs/minikin/WordBreaker.cpp
index 74c1138..dae5cab 100644
--- a/libs/minikin/WordBreaker.cpp
+++ b/libs/minikin/WordBreaker.cpp
@@ -14,45 +14,95 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Minikin"
+#include "WordBreaker.h"
 
-#include <android/log.h>
-
-#include <minikin/Emoji.h>
-#include <minikin/Hyphenator.h>
-#include <minikin/WordBreaker.h>
-#include "MinikinInternal.h"
+#include <list>
+#include <map>
 
 #include <unicode/uchar.h>
 #include <unicode/utf16.h>
 
+#include "minikin/Emoji.h"
+#include "minikin/Hyphenator.h"
+
+#include "Locale.h"
+#include "MinikinInternal.h"
+
 namespace minikin {
 
-const uint32_t CHAR_SOFT_HYPHEN = 0x00AD;
-const uint32_t CHAR_ZWJ = 0x200D;
-
-void WordBreaker::setLocale(const icu::Locale& locale) {
-    UErrorCode status = U_ZERO_ERROR;
-    mBreakIterator.reset(icu::BreakIterator::createLineInstance(locale, status));
+namespace {
+static icu::BreakIterator* createNewIterator(const Locale& locale) {
     // TODO: handle failure status
-    if (mText != nullptr) {
-        mBreakIterator->setText(&mUText, status);
+    UErrorCode status = U_ZERO_ERROR;
+    return icu::BreakIterator::createLineInstance(
+            locale.isUnsupported() ? icu::Locale::getRoot()
+                                   : icu::Locale::createFromName(locale.getString().c_str()),
+            status);
+}
+}  // namespace
+
+ICULineBreakerPool::Slot ICULineBreakerPoolImpl::acquire(const Locale& locale) {
+    const uint64_t id = locale.getIdentifier();
+    std::lock_guard<std::mutex> lock(mMutex);
+    for (auto i = mPool.begin(); i != mPool.end(); i++) {
+        if (i->localeId == id) {
+            Slot slot = std::move(*i);
+            mPool.erase(i);
+            return slot;
+        }
     }
-    mIteratorWasReset = true;
+
+    // Not found in pool. Create new one.
+    return {id, std::unique_ptr<icu::BreakIterator>(createNewIterator(locale))};
+}
+
+void ICULineBreakerPoolImpl::release(ICULineBreakerPool::Slot&& slot) {
+    if (slot.breaker.get() == nullptr) {
+        return;  // Already released slot. Do nothing.
+    }
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mPool.size() >= MAX_POOL_SIZE) {
+        // Pool is full. Move to local variable, so that the given slot will be released when the
+        // variable leaves the scope.
+        Slot localSlot = std::move(slot);
+        return;
+    }
+    mPool.push_front(std::move(slot));
+}
+
+WordBreaker::WordBreaker() : mPool(&ICULineBreakerPoolImpl::getInstance()) {}
+
+WordBreaker::WordBreaker(ICULineBreakerPool* pool) : mPool(pool) {}
+
+ssize_t WordBreaker::followingWithLocale(const Locale& locale, size_t from) {
+    mIcuBreaker = mPool->acquire(locale);
+    UErrorCode status = U_ZERO_ERROR;
+    MINIKIN_ASSERT(mText != nullptr, "setText must be called first");
+    // TODO: handle failure status
+    mIcuBreaker.breaker->setText(&mUText, status);
+    if (mInEmailOrUrl) {
+        // Note:
+        // Don't reset mCurrent, mLast, or mScanOffset for keeping email/URL context.
+        // The email/URL detection doesn't support following() functionality, so that we can't
+        // restart from the specific position. This means following() can not be supported in
+        // general, but keeping old email/URL context works for LineBreaker since it just wants to
+        // re-calculate the next break point with the new locale.
+    } else {
+        mCurrent = mLast = mScanOffset = from;
+        next();
+    }
+    return mCurrent;
 }
 
 void WordBreaker::setText(const uint16_t* data, size_t size) {
     mText = data;
     mTextSize = size;
-    mIteratorWasReset = false;
     mLast = 0;
     mCurrent = 0;
     mScanOffset = 0;
     mInEmailOrUrl = false;
     UErrorCode status = U_ZERO_ERROR;
     utext_openUChars(&mUText, reinterpret_cast<const UChar*>(data), size, &status);
-    mBreakIterator->setText(&mUText, status);
-    mBreakIterator->first();
 }
 
 ssize_t WordBreaker::current() const {
@@ -64,9 +114,14 @@
  * represents customization beyond the ICU behavior, because plain ICU provides some
  * line break opportunities that we don't want.
  **/
-static bool isBreakValid(const uint16_t* buf, size_t bufEnd, size_t i) {
+static bool isValidBreak(const uint16_t* buf, size_t bufEnd, int32_t i) {
+    const size_t position = static_cast<size_t>(i);
+    if (i == icu::BreakIterator::DONE || position == bufEnd) {
+        // If the iterator reaches the end, treat as break.
+        return true;
+    }
     uint32_t codePoint;
-    size_t prev_offset = i;
+    size_t prev_offset = position;
     U16_PREV(buf, 0, prev_offset, codePoint);
     // Do not break on hard or soft hyphens. These are handled by automatic hyphenation.
     if (Hyphenator::isLineBreakingHyphen(codePoint) || codePoint == CHAR_SOFT_HYPHEN) {
@@ -81,7 +136,7 @@
     }
 
     uint32_t next_codepoint;
-    size_t next_offset = i;
+    size_t next_offset = position;
     U16_NEXT(buf, next_offset, bufEnd, next_codepoint);
 
     // Rule LB8 for Emoji ZWJ sequences. We need to do this ourselves since we may have fresher
@@ -106,16 +161,10 @@
 // Customized iteratorNext that takes care of both resets and our modifications
 // to ICU's behavior.
 int32_t WordBreaker::iteratorNext() {
-    int32_t result;
-    do {
-        if (mIteratorWasReset) {
-            result = mBreakIterator->following(mCurrent);
-            mIteratorWasReset = false;
-        } else {
-            result = mBreakIterator->next();
-        }
-    } while (!(result == icu::BreakIterator::DONE || (size_t)result == mTextSize
-            || isBreakValid(mText, mTextSize, result)));
+    int32_t result = mIcuBreaker.breaker->following(mCurrent);
+    while (!isValidBreak(mText, mTextSize, result)) {
+        result = mIcuBreaker.breaker->next();
+    }
     return result;
 }
 
@@ -126,8 +175,8 @@
 
 // Chicago Manual of Style recommends breaking before these characters in URLs and email addresses
 static bool breakBefore(uint16_t c) {
-    return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' || c == '#'
-            || c == '%' || c == '=' || c == '&';
+    return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' || c == '#' ||
+           c == '%' || c == '=' || c == '&';
 }
 
 enum ScanState {
@@ -162,14 +211,13 @@
             }
         }
         if (state == SAW_AT || state == SAW_COLON_SLASH_SLASH) {
-            if (!mBreakIterator->isBoundary(i)) {
+            if (!mIcuBreaker.breaker->isBoundary(i)) {
                 // If there are combining marks or such at the end of the URL or the email address,
                 // consider them a part of the URL or the email, and skip to the next actual
                 // boundary.
-                i = mBreakIterator->following(i);
+                i = mIcuBreaker.breaker->following(i);
             }
             mInEmailOrUrl = true;
-            mIteratorWasReset = true;
         } else {
             mInEmailOrUrl = false;
         }
@@ -197,7 +245,7 @@
             }
             // break before single slash
             if (thisChar == '/' && lastChar != '/' &&
-                        !(i + 1 < mScanOffset && mText[i + 1] == '/')) {
+                !(i + 1 < mScanOffset && mText[i + 1] == '/')) {
                 break;
             }
         }
@@ -213,7 +261,7 @@
     if (mInEmailOrUrl) {
         mCurrent = findNextBreakInEmailOrUrl();
     } else {  // Business as usual
-        mCurrent = (ssize_t) iteratorNext();
+        mCurrent = (ssize_t)iteratorNext();
     }
     return mCurrent;
 }
@@ -248,8 +296,8 @@
         ssize_t ix = result;
         U16_PREV(mText, mLast, ix, c);
         const int32_t gc_mask = U_GET_GC_MASK(c);
-        // strip trailing space and punctuation
-        if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK)) == 0) {
+        // strip trailing spaces, punctuation and control characters
+        if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK | U_GC_CC_MASK)) == 0) {
             break;
         }
         result = ix;
@@ -265,6 +313,7 @@
     mText = nullptr;
     // Note: calling utext_close multiply is safe
     utext_close(&mUText);
+    mPool->release(std::move(mIcuBreaker));
 }
 
 }  // namespace minikin
diff --git a/libs/minikin/WordBreaker.h b/libs/minikin/WordBreaker.h
new file mode 100644
index 0000000..487c1fa
--- /dev/null
+++ b/libs/minikin/WordBreaker.h
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+/**
+ * A wrapper around ICU's line break iterator, that gives customized line
+ * break opportunities, as well as identifying words for the purpose of
+ * hyphenation.
+ */
+
+#ifndef MINIKIN_WORD_BREAKER_H
+#define MINIKIN_WORD_BREAKER_H
+
+#include <list>
+#include <mutex>
+
+#include <unicode/brkiter.h>
+
+#include "minikin/Macros.h"
+#include "minikin/Range.h"
+
+#include "Locale.h"
+
+namespace minikin {
+
+// A class interface for providing pooling implementation of ICU's line breaker.
+// The implementation can be customized for testing purposes.
+class ICULineBreakerPool {
+public:
+    struct Slot {
+        Slot() : localeId(0), breaker(nullptr) {}
+        Slot(uint64_t localeId, std::unique_ptr<icu::BreakIterator>&& breaker)
+                : localeId(localeId), breaker(std::move(breaker)) {}
+
+        Slot(Slot&& other) = default;
+        Slot& operator=(Slot&& other) = default;
+
+        // Forbid copy and assignment.
+        Slot(const Slot&) = delete;
+        Slot& operator=(const Slot&) = delete;
+
+        uint64_t localeId;
+        std::unique_ptr<icu::BreakIterator> breaker;
+    };
+    virtual ~ICULineBreakerPool() {}
+    virtual Slot acquire(const Locale& locale) = 0;
+    virtual void release(Slot&& slot) = 0;
+};
+
+// An singleton implementation of the ICU line breaker pool.
+// Since creating ICU line breaker instance takes some time. Pool it for later use.
+class ICULineBreakerPoolImpl : public ICULineBreakerPool {
+public:
+    Slot acquire(const Locale& locale) override;
+    void release(Slot&& slot) override;
+
+    static ICULineBreakerPoolImpl& getInstance() {
+        static ICULineBreakerPoolImpl pool;
+        return pool;
+    }
+
+protected:
+    // protected for testing purposes.
+    static constexpr size_t MAX_POOL_SIZE = 4;
+    ICULineBreakerPoolImpl(){};  // singleton.
+    size_t getPoolSize() const {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mPool.size();
+    }
+
+private:
+    std::list<Slot> mPool GUARDED_BY(mMutex);
+    mutable std::mutex mMutex;
+};
+
+class WordBreaker {
+public:
+    virtual ~WordBreaker() { finish(); }
+
+    WordBreaker();
+
+    void setText(const uint16_t* data, size_t size);
+
+    // Advance iterator to next word break with current locale. Return offset, or -1 if EOT
+    ssize_t next();
+
+    // Advance iterator to the break just after "from" with using the new provided locale.
+    // Return offset, or -1 if EOT
+    ssize_t followingWithLocale(const Locale& locale, size_t from);
+
+    // Current offset of iterator, equal to 0 at BOT or last return from next()
+    ssize_t current() const;
+
+    // After calling next(), wordStart() and wordEnd() are offsets defining the previous
+    // word. If wordEnd <= wordStart, it's not a word for the purpose of hyphenation.
+    ssize_t wordStart() const;
+
+    ssize_t wordEnd() const;
+
+    // Returns the range from wordStart() to wordEnd().
+    // If wordEnd() <= wordStart(), returns empty range.
+    inline Range wordRange() const {
+        const uint32_t start = wordStart();
+        const uint32_t end = wordEnd();
+        return start < end ? Range(start, end) : Range(end, end);
+    }
+
+    int breakBadness() const;
+
+    void finish();
+
+protected:
+    // protected virtual for testing purpose.
+    // Caller must release the pool.
+    WordBreaker(ICULineBreakerPool* pool);
+
+private:
+    int32_t iteratorNext();
+    void detectEmailOrUrl();
+    ssize_t findNextBreakInEmailOrUrl();
+
+    // Doesn't take ownership. Must not be nullptr. Must be set in constructor.
+    ICULineBreakerPool* mPool;
+
+    ICULineBreakerPool::Slot mIcuBreaker;
+
+    UText mUText = UTEXT_INITIALIZER;
+    const uint16_t* mText = nullptr;
+    size_t mTextSize;
+    ssize_t mLast;
+    ssize_t mCurrent;
+
+    // state for the email address / url detector
+    ssize_t mScanOffset;
+    bool mInEmailOrUrl;
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_WORD_BREAKER_H
diff --git a/tests/Android.bp b/tests/Android.bp
index 887f057..9f6d534 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -2,6 +2,7 @@
     name: "minikin-test-data",
     srcs: [
         "data/Arabic.ttf",
+        "data/Ascii.ttf",
         "data/Bold.ttf",
         "data/BoldItalic.ttf",
         "data/Cherokee.ttf",
@@ -11,6 +12,7 @@
         "data/Italic.ttf",
         "data/Ja.ttf",
         "data/Ko.ttf",
+        "data/LayoutTestFont.ttf",
         "data/MultiAxis.ttf",
         "data/NoCmapFormat14.ttf",
         "data/NoGlyphFont.ttf",
diff --git a/tests/data/Arabic.ttf b/tests/data/Arabic.ttf
index faa1f3d..0c13030 100644
--- a/tests/data/Arabic.ttf
+++ b/tests/data/Arabic.ttf
Binary files differ
diff --git a/tests/data/Arabic.ttx b/tests/data/Arabic.ttx
index 778af33..1b8e966 100644
--- a/tests/data/Arabic.ttx
+++ b/tests/data/Arabic.ttx
@@ -18,8 +18,10 @@
   <GlyphOrder>
     <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
     <GlyphID id="0" name=".notdef"/>
-    <GlyphID id="1" name="uni061C"/>
-    <GlyphID id="2" name="uni200D"/>
+    <GlyphID id="1" name="uni000A"/>
+    <GlyphID id="2" name="uni000D"/>
+    <GlyphID id="3" name="uni061C"/>
+    <GlyphID id="4" name="uni200D"/>
   </GlyphOrder>
 
   <head>
@@ -66,7 +68,7 @@
   <maxp>
     <!-- Most of this table will be recalculated by the compiler -->
     <tableVersion value="0x10000"/>
-    <numGlyphs value="3"/>
+    <numGlyphs value="5"/>
     <maxPoints value="0"/>
     <maxContours value="0"/>
     <maxCompositePoints value="0"/>
@@ -137,6 +139,8 @@
 
   <hmtx>
     <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="uni000A" width="0" lsb="0"/>
+    <mtx name="uni000D" width="0" lsb="0"/>
     <mtx name="uni061C" width="0" lsb="0"/>
     <mtx name="uni200D" width="0" lsb="0"/>
   </hmtx>
@@ -144,8 +148,10 @@
   <cmap>
     <tableVersion version="0"/>
     <cmap_format_4 platformID="3" platEncID="10" language="0">
-      <map code="0x61c" name="uni061C"/><!-- ARABIC LETTER MARK -->
-      <map code="0x200d" name="uni200D"/><!-- ZERO WIDTH JOINER -->
+      <map code="0x000A" name="uni000A"/><!-- LINE FEED -->
+      <map code="0x000D" name="uni000D"/><!-- CARRIAGE RETURN -->
+      <map code="0x061C" name="uni061C"/><!-- ARABIC LETTER MARK -->
+      <map code="0x200D" name="uni200D"/><!-- ZERO WIDTH JOINER -->
     </cmap_format_4>
   </cmap>
 
@@ -160,6 +166,10 @@
 
     <TTGlyph name=".notdef"/><!-- contains no outline data -->
 
+    <TTGlyph name="uni000A"/><!-- contains no outline data -->
+
+    <TTGlyph name="uni000D"/><!-- contains no outline data -->
+
     <TTGlyph name="uni061C"/><!-- contains no outline data -->
 
     <TTGlyph name="uni200D"/><!-- contains no outline data -->
diff --git a/tests/data/Ascii.ttf b/tests/data/Ascii.ttf
new file mode 100644
index 0000000..be6d5fe
--- /dev/null
+++ b/tests/data/Ascii.ttf
Binary files differ
diff --git a/tests/data/Ascii.ttx b/tests/data/Ascii.ttx
new file mode 100644
index 0000000..74eba96
--- /dev/null
+++ b/tests/data/Ascii.ttx
@@ -0,0 +1,279 @@
+<?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="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"/>
+  </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="1em" /> <!-- 'f' -->
+      <map code="0x0067" name="1em" /> <!-- 'g' -->
+      <map code="0x0068" name="1em" /> <!-- 'h' -->
+      <map code="0x0069" name="1em" /> <!-- '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>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for ASCII
+    </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>
+
+</ttFont>
diff --git a/tests/data/LayoutTestFont.ttf b/tests/data/LayoutTestFont.ttf
new file mode 100644
index 0000000..7076023
--- /dev/null
+++ b/tests/data/LayoutTestFont.ttf
Binary files differ
diff --git a/tests/data/LayoutTestFont.ttx b/tests/data/LayoutTestFont.ttx
new file mode 100644
index 0000000..7971a03
--- /dev/null
+++ b/tests/data/LayoutTestFont.ttx
@@ -0,0 +1,207 @@
+<?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="0em"/>
+    <GlyphID id="2" name="1em"/>
+    <GlyphID id="3" name="3em"/>
+    <GlyphID id="4" name="5em"/>
+    <GlyphID id="5" name="7em"/>
+    <GlyphID id="6" name="10em"/>
+    <GlyphID id="7" name="50em"/>
+    <GlyphID id="8" name="100em"/>
+  </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="1000"/>
+    <descent value="-200"/>
+    <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="0em" width="0" lsb="0"/>
+    <mtx name="1em" width="100" lsb="0"/>
+    <mtx name="3em" width="300" lsb="0"/>
+    <mtx name="5em" width="500" lsb="0"/>
+    <mtx name="7em" width="700" lsb="0"/>
+    <mtx name="10em" width="1000" lsb="0"/>
+    <mtx name="50em" width="5000" lsb="0"/>
+    <mtx name="100em" width="10000" 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="10em" />
+      <map code="0x002e" name="10em" />  <!-- . -->
+      <map code="0x0043" name="100em" />  <!-- C -->
+      <map code="0x0049" name="1em" />  <!-- I -->
+      <map code="0x004c" name="50em" />  <!-- L -->
+      <map code="0x0056" name="5em" />  <!-- V -->
+      <map code="0x0058" name="10em" />  <!-- X -->
+      <map code="0x005f" name="0em" /> <!-- _ -->
+      <map code="0xfffd" name="7em" /> <!-- REPLACEMENT CHAR -->
+      <map code="0x10331" name="10em" />
+    </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="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" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for LayoutTestFont
+    </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 LayoutTestFont
+    </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/perftests/Android.bp b/tests/perftests/Android.bp
index f8847ac..617560d 100644
--- a/tests/perftests/Android.bp
+++ b/tests/perftests/Android.bp
@@ -24,7 +24,6 @@
     ],
     srcs: [
         "FontCollection.cpp",
-        "FontFamily.cpp",
         "FontLanguage.cpp",
         "GraphemeBreak.cpp",
         "Hyphenator.cpp",
@@ -41,9 +40,10 @@
     ],
 
     shared_libs: [
+        "libft2",
         "libharfbuzz_ng",
         "libicuuc",
         "liblog",
-        "libskia",
+
     ],
 }
diff --git a/tests/perftests/FontCollection.cpp b/tests/perftests/FontCollection.cpp
index 79f2563..819f125 100644
--- a/tests/perftests/FontCollection.cpp
+++ b/tests/perftests/FontCollection.cpp
@@ -13,14 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <benchmark/benchmark.h>
+
+#include "minikin/FontCollection.h"
 
 #include <memory>
 
-#include <minikin/FontCollection.h>
-#include <FontTestUtils.h>
-#include <UnicodeUtils.h>
-#include <MinikinInternal.h>
+#include <benchmark/benchmark.h>
+
+#include "minikin/LocaleList.h"
+
+#include "FontTestUtils.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
 
 namespace minikin {
 
@@ -38,8 +42,8 @@
 BENCHMARK(BM_FontCollection_construct);
 
 static void BM_FontCollection_hasVariationSelector(benchmark::State& state) {
-    std::shared_ptr<FontCollection> collection(
-            getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+    auto collection =
+            std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
 
     uint32_t baseCp = state.range(0);
     uint32_t vsCp = state.range(1);
@@ -55,47 +59,47 @@
 
 // TODO: Rewrite with BENCHMARK_CAPTURE for better test name.
 BENCHMARK(BM_FontCollection_hasVariationSelector)
-      ->ArgPair(0x2708, 0xFE0F)
-      ->ArgPair(0x2708, 0xFE0E)
-      ->ArgPair(0x3402, 0xE0100);
+        ->ArgPair(0x2708, 0xFE0F)
+        ->ArgPair(0x2708, 0xFE0E)
+        ->ArgPair(0x3402, 0xE0100);
 
 struct ItemizeTestCases {
     std::string itemizeText;
     std::string languageTag;
     std::string labelText;
 } ITEMIZE_TEST_CASES[] = {
-    { "'A' 'n' 'd' 'r' 'o' 'i' 'd'", "en", "English" },
-    { "U+4E16", "zh-Hans", "CJK Ideograph" },
-    { "U+4E16", "zh-Hans,zh-Hant,ja,en,es,pt,fr,de", "CJK Ideograph with many language fallback" },
-    { "U+3402 U+E0100", "ja", "CJK Ideograph with variation selector" },
-    { "'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648", "en", "Mixture of English, Thai and Arabic" },
-    { "U+2708 U+FE0E", "en", "Emoji with variation selector" },
-    { "U+0031 U+FE0F U+20E3", "en", "KEYCAP" },
+        {"'A' 'n' 'd' 'r' 'o' 'i' 'd'", "en", "English"},
+        {"U+4E16", "zh-Hans", "CJK Ideograph"},
+        {"U+4E16", "zh-Hans,zh-Hant,ja,en,es,pt,fr,de",
+         "CJK Ideograph with many language fallback"},
+        {"U+3402 U+E0100", "ja", "CJK Ideograph with variation selector"},
+        {"'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648", "en", "Mixture of English, Thai and Arabic"},
+        {"U+2708 U+FE0E", "en", "Emoji with variation selector"},
+        {"U+0031 U+FE0F U+20E3", "en", "KEYCAP"},
 };
 
 static void BM_FontCollection_itemize(benchmark::State& state) {
-    std::shared_ptr<FontCollection> collection(
-            getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+    auto collection =
+            std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
 
     size_t testIndex = state.range(0);
     state.SetLabel("Itemize: " + ITEMIZE_TEST_CASES[testIndex].labelText);
 
     uint16_t buffer[64];
     size_t utf16_length = 0;
-    ParseUnicode(
-            buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(), &utf16_length, nullptr);
+    ParseUnicode(buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(), &utf16_length,
+                 nullptr);
     std::vector<FontCollection::Run> result;
-    FontStyle style(FontStyle::registerLanguageList(ITEMIZE_TEST_CASES[testIndex].languageTag));
+    MinikinPaint paint(collection);
+    paint.localeListId = registerLocaleList(ITEMIZE_TEST_CASES[testIndex].languageTag);
 
-    android::AutoMutex _l(gMinikinLock);
     while (state.KeepRunning()) {
         result.clear();
-        collection->itemize(buffer, utf16_length, style, &result);
+        collection->itemize(buffer, utf16_length, paint, &result);
     }
 }
 
 // TODO: Rewrite with BENCHMARK_CAPTURE once it is available in Android.
-BENCHMARK(BM_FontCollection_itemize)
-    ->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(5)->Arg(6);
+BENCHMARK(BM_FontCollection_itemize)->Arg(0)->Arg(1)->Arg(2)->Arg(3)->Arg(4)->Arg(5)->Arg(6);
 
 }  // namespace minikin
diff --git a/tests/perftests/FontFamily.cpp b/tests/perftests/FontFamily.cpp
deleted file mode 100644
index 9ab61e1..0000000
--- a/tests/perftests/FontFamily.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2017 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 <benchmark/benchmark.h>
-
-#include <minikin/FontFamily.h>
-#include "../util/MinikinFontForTest.h"
-
-namespace minikin {
-
-static void BM_FontFamily_create(benchmark::State& state) {
-    std::shared_ptr<MinikinFontForTest> minikinFont =
-            std::make_shared<MinikinFontForTest>("/system/fonts/NotoSansCJK-Regular.ttc", 0);
-
-    while (state.KeepRunning()) {
-        std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(
-                std::vector<Font>({Font(minikinFont, FontStyle())}));
-    }
-}
-
-BENCHMARK(BM_FontFamily_create);
-
-}  // namespace minikin
diff --git a/tests/perftests/FontLanguage.cpp b/tests/perftests/FontLanguage.cpp
index 6c9c84d..20d8698 100644
--- a/tests/perftests/FontLanguage.cpp
+++ b/tests/perftests/FontLanguage.cpp
@@ -13,31 +13,31 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <benchmark/benchmark.h>
+#include "Locale.h"
 
-#include "FontLanguage.h"
+#include <benchmark/benchmark.h>
 
 namespace minikin {
 
-static void BM_FontLanguage_en_US(benchmark::State& state) {
+static void BM_Locale_en_US(benchmark::State& state) {
     while (state.KeepRunning()) {
-        FontLanguage language("en-US", 5);
+        Locale language(StringPiece("en-US", 5));
     }
 }
-BENCHMARK(BM_FontLanguage_en_US);
+BENCHMARK(BM_Locale_en_US);
 
-static void BM_FontLanguage_en_Latn_US(benchmark::State& state) {
+static void BM_Locale_en_Latn_US(benchmark::State& state) {
     while (state.KeepRunning()) {
-        FontLanguage language("en-Latn-US", 10);
+        Locale language(StringPiece("en-Latn-US", 10));
     }
 }
-BENCHMARK(BM_FontLanguage_en_Latn_US);
+BENCHMARK(BM_Locale_en_Latn_US);
 
-static void BM_FontLanguage_en_Latn_US_u_em_emoji(benchmark::State& state) {
+static void BM_Locale_en_Latn_US_u_em_emoji(benchmark::State& state) {
     while (state.KeepRunning()) {
-        FontLanguage language("en-Latn-US-u-em-emoji", 21);
+        Locale language(StringPiece("en-Latn-US-u-em-emoji", 21));
     }
 }
-BENCHMARK(BM_FontLanguage_en_Latn_US_u_em_emoji);
+BENCHMARK(BM_Locale_en_Latn_US_u_em_emoji);
 
 }  // namespace minikin
diff --git a/tests/perftests/GraphemeBreak.cpp b/tests/perftests/GraphemeBreak.cpp
index 830586f..af156c8 100644
--- a/tests/perftests/GraphemeBreak.cpp
+++ b/tests/perftests/GraphemeBreak.cpp
@@ -13,11 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <benchmark/benchmark.h>
-
-#include <cutils/log.h>
 
 #include "minikin/GraphemeBreak.h"
+
+#include <benchmark/benchmark.h>
+#include <cutils/log.h>
+
 #include "UnicodeUtils.h"
 
 namespace minikin {
@@ -42,9 +43,9 @@
     }
 }
 BENCHMARK(BM_GraphemeBreak_Ascii)
-    ->Arg(0)  // Begining of the text.
-    ->Arg(1)  // Middle of the text.
-    ->Arg(12);  // End of the text.
+        ->Arg(0)    // Begining of the text.
+        ->Arg(1)    // Middle of the text.
+        ->Arg(12);  // End of the text.
 
 static void BM_GraphemeBreak_Emoji(benchmark::State& state) {
     size_t result_size;
@@ -57,9 +58,9 @@
     }
 }
 BENCHMARK(BM_GraphemeBreak_Emoji)
-    ->Arg(1)  // Middle of emoji modifier sequence.
-    ->Arg(2)  // Middle of the surrogate pairs.
-    ->Arg(3);  // After emoji modifier sequence. Here is boundary of grapheme cluster.
+        ->Arg(1)   // Middle of emoji modifier sequence.
+        ->Arg(2)   // Middle of the surrogate pairs.
+        ->Arg(3);  // After emoji modifier sequence. Here is boundary of grapheme cluster.
 
 static void BM_GraphemeBreak_Emoji_Flags(benchmark::State& state) {
     size_t result_size;
@@ -72,8 +73,8 @@
     }
 }
 BENCHMARK(BM_GraphemeBreak_Emoji_Flags)
-    ->Arg(2)  // Middle of flag sequence.
-    ->Arg(4)  // After flag sequence. Here is boundary of grapheme cluster.
-    ->Arg(10); // Middle of 3rd flag sequence.
+        ->Arg(2)    // Middle of flag sequence.
+        ->Arg(4)    // After flag sequence. Here is boundary of grapheme cluster.
+        ->Arg(10);  // Middle of 3rd flag sequence.
 
 }  // namespace minikin
diff --git a/tests/perftests/Hyphenator.cpp b/tests/perftests/Hyphenator.cpp
index ae62498..ae95644 100644
--- a/tests/perftests/Hyphenator.cpp
+++ b/tests/perftests/Hyphenator.cpp
@@ -13,43 +13,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include "minikin/Hyphenator.h"
+
 #include <benchmark/benchmark.h>
 
-#include <minikin/Hyphenator.h>
-#include <FileUtils.h>
-#include <UnicodeUtils.h>
+#include "FileUtils.h"
+#include "UnicodeUtils.h"
 
 namespace minikin {
 
 const char* enUsHyph = "/system/usr/hyphen-data/hyph-en-us.hyb";
 const int enUsMinPrefix = 2;
 const int enUsMinSuffix = 3;
-const icu::Locale& usLocale = icu::Locale::getUS();
 
 static void BM_Hyphenator_short_word(benchmark::State& state) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(
-            readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
+    Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(enUsHyph).data(), enUsMinPrefix,
+                                                    enUsMinSuffix, "en");
     std::vector<uint16_t> word = utf8ToUtf16("hyphen");
     std::vector<HyphenationType> result;
     while (state.KeepRunning()) {
-        hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
+        hyphenator->hyphenate(word, &result);
     }
-    Hyphenator::loadBinary(nullptr, 2, 2);
 }
 
 // TODO: Use BENCHMARK_CAPTURE for parametrise.
 BENCHMARK(BM_Hyphenator_short_word);
 
 static void BM_Hyphenator_long_word(benchmark::State& state) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(
-            readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);
-    std::vector<uint16_t> word = utf8ToUtf16(
-            "Pneumonoultramicroscopicsilicovolcanoconiosis");
+    Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(enUsHyph).data(), enUsMinPrefix,
+                                                    enUsMinSuffix, "en");
+    std::vector<uint16_t> word = utf8ToUtf16("Pneumonoultramicroscopicsilicovolcanoconiosis");
     std::vector<HyphenationType> result;
     while (state.KeepRunning()) {
-        hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);
+        hyphenator->hyphenate(word, &result);
     }
-    Hyphenator::loadBinary(nullptr, 2, 2);
 }
 
 // TODO: Use BENCHMARK_CAPTURE for parametrise.
diff --git a/tests/perftests/WordBreaker.cpp b/tests/perftests/WordBreaker.cpp
index f9ef214..6a8b1c4 100644
--- a/tests/perftests/WordBreaker.cpp
+++ b/tests/perftests/WordBreaker.cpp
@@ -13,23 +13,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include "WordBreaker.h"
+
 #include <benchmark/benchmark.h>
 
-#include "minikin/WordBreaker.h"
+#include "Locale.h"
 #include "UnicodeUtils.h"
 
 namespace minikin {
 
 static void BM_WordBreaker_English(benchmark::State& state) {
-    const char* kLoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
-        "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
+    const char* kLoremIpsum =
+            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+            "eiusmod tempor incididunt ut labore et dolore magna aliqua.";
 
     WordBreaker wb;
-    wb.setLocale(icu::Locale::getEnglish());
+    wb.followingWithLocale(Locale("en-US"), 0);
     std::vector<uint16_t> text = utf8ToUtf16(kLoremIpsum);
     while (state.KeepRunning()) {
         wb.setText(text.data(), text.size());
-        while (wb.next() != -1) {}
+        while (wb.next() != -1) {
+        }
     }
 }
 BENCHMARK(BM_WordBreaker_English);
diff --git a/tests/perftests/main.cpp b/tests/perftests/main.cpp
index e6f9d14..142b396 100644
--- a/tests/perftests/main.cpp
+++ b/tests/perftests/main.cpp
@@ -13,17 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
 #include <benchmark/benchmark.h>
-
 #include <cutils/log.h>
-
 #include <unicode/uclean.h>
 #include <unicode/udata.h>
 
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-
 int main(int argc, char** argv) {
     const char* fn = "/system/usr/icu/" U_ICUDATA_NAME ".dat";
     int fd = open(fn, O_RDONLY);
diff --git a/tests/stresstest/Android.bp b/tests/stresstest/Android.bp
index d91007a..3b132ed 100644
--- a/tests/stresstest/Android.bp
+++ b/tests/stresstest/Android.bp
@@ -18,6 +18,7 @@
     name: "minikin_stress_tests",
 
     header_libs: ["libminikin-headers-for-tests"],
+    data: [":minikin-test-data"],
 
     static_libs: [
         "libminikin-tests-util",
@@ -28,7 +29,7 @@
     // Shared libraries which are dependencies of minikin; these are not automatically
     // pulled in by the build system (and thus sadly must be repeated).
     shared_libs: [
-        "libskia",
+
         "libft2",
         "libharfbuzz_ng",
         "libicuuc",
@@ -42,7 +43,7 @@
         "MultithreadTest.cpp",
     ],
 
-    cppflags: [
+    cflags: [
         "-Werror",
         "-Wall",
         "-Wextra",
diff --git a/tests/stresstest/FontFamilyTest.cpp b/tests/stresstest/FontFamilyTest.cpp
index 9d289e5..7554314 100644
--- a/tests/stresstest/FontFamilyTest.cpp
+++ b/tests/stresstest/FontFamilyTest.cpp
@@ -14,14 +14,16 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#include "minikin/FontFamily.h"
 
-#include "../util/FontTestUtils.h"
-#include "../util/MinikinFontForTest.h"
-#include "HbFontCache.h"
-#include "MinikinInternal.h"
+#include <gtest/gtest.h>
+#include <hb.h>
+
 #include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "MinikinInternal.h"
 
 namespace minikin {
 
@@ -33,18 +35,18 @@
     const std::string& fontPath = GetParam().first;
     int ttcIndex = GetParam().second;
 
-    std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath, ttcIndex));
-    std::shared_ptr<FontFamily> family =
-            std::make_shared<FontFamily>(std::vector<Font>({Font(font, FontStyle())}));
+    auto font = std::make_shared<FreeTypeMinikinFontForTest>(fontPath);
+    std::vector<Font> fonts;
+    fonts.push_back(Font::Builder(font).build());
+    std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(std::move(fonts));
 
-    android::AutoMutex _l(gMinikinLock);
-    hb_font_t* hbFont = getHbFontLocked(font.get());
+    hb_font_t* hbFont = family->getFont(0)->baseFont().get();
 
     for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) {
         uint32_t unusedGlyph;
         EXPECT_EQ(family->hasGlyph(codePoint, 0 /* variation selector */),
-                static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, 0 /* variation selector */,
-                        &unusedGlyph)));
+                  static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, 0 /* variation selector */,
+                                                      &unusedGlyph)));
     }
 
     for (uint32_t vs = VS1; vs < VS256; ++vs) {
@@ -55,21 +57,17 @@
         for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) {
             uint32_t unusedGlyph;
             ASSERT_EQ(family->hasGlyph(codePoint, vs),
-                    static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph)))
-                << "Inconsistent Result: " << fontPath << "#" << ttcIndex
-                << ": U+" << std::hex << codePoint << " U+" << std::hex << vs
-                << " Minikin: " << family->hasGlyph(codePoint, vs)
-                << " HarfBuzz: "
-                << static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph));
-
+                      static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph)))
+                    << "Inconsistent Result: " << fontPath << "#" << ttcIndex << ": U+" << std::hex
+                    << codePoint << " U+" << std::hex << vs
+                    << " Minikin: " << family->hasGlyph(codePoint, vs) << " HarfBuzz: "
+                    << static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph));
         }
     }
     hb_font_destroy(hbFont);
 }
 
-INSTANTIATE_TEST_CASE_P(FontFamilyTest,
-        FontFamilyHarfBuzzCompatibilityTest,
-        ::testing::Values(
-                TestParam("/system/fonts/NotoSansCJK-Regular.ttc", 0),
-                TestParam("/system/fonts/NotoColorEmoji.ttf", 0)));
+INSTANTIATE_TEST_CASE_P(FontFamilyTest, FontFamilyHarfBuzzCompatibilityTest,
+                        ::testing::Values(TestParam("/system/fonts/NotoSansCJK-Regular.ttc", 0),
+                                          TestParam("/system/fonts/NotoColorEmoji.ttf", 0)));
 }  // namespace minikin
diff --git a/tests/stresstest/MultithreadTest.cpp b/tests/stresstest/MultithreadTest.cpp
index 08c94b9..5d5ae03 100644
--- a/tests/stresstest/MultithreadTest.cpp
+++ b/tests/stresstest/MultithreadTest.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#include "minikin/Layout.h"
 
 #include <condition_variable>
 #include <mutex>
@@ -22,27 +22,27 @@
 #include <thread>
 
 #include <cutils/log.h>
+#include <gtest/gtest.h>
 
-#include "MinikinInternal.h"
 #include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
-#include "../util/FontTestUtils.h"
+#include "minikin/Macros.h"
+
+#include "FontTestUtils.h"
+#include "MinikinInternal.h"
+#include "PathUtils.h"
 
 namespace minikin {
 
-const char* SYSTEM_FONT_PATH = "/system/fonts/";
-const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
-
 constexpr int LAYOUT_COUNT_PER_COLLECTION = 500;
 constexpr int COLLECTION_COUNT_PER_THREAD = 15;
 constexpr int NUM_THREADS = 10;
 
 std::mutex gMutex;
 std::condition_variable gCv;
-bool gReady = false;
+bool gReady GUARDED_BY(gMutex) = false;
 
-static std::vector<uint16_t> generateTestText(
-        std::mt19937* mt, int lettersInWord, int wordsInText) {
+static std::vector<uint16_t> generateTestText(std::mt19937* mt, int lettersInWord,
+                                              int wordsInText) {
     std::uniform_int_distribution<uint16_t> dist('A', 'Z');
 
     std::vector<uint16_t> text;
@@ -66,22 +66,21 @@
     }
 
     std::mt19937 mt(tid);
-    MinikinPaint paint;
 
     for (int i = 0; i < COLLECTION_COUNT_PER_THREAD; ++i) {
-        std::shared_ptr<FontCollection> collection(
-                getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
+        MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+        paint.size = 10.0f;  // Make 1em = 10px
 
         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.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(),
-                    paint, collection);
+            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) {
-                // MinikinFontForTest always returns 10.0f for horizontal advance.
+                // All characters in Ascii.ttf has 1.0em horizontal advance.
                 LOG_ALWAYS_FATAL_IF(advances[k] != 10.0f, "Memory corruption detected.");
             }
         }
diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp
index 770d7be..02bec1e 100644
--- a/tests/unittest/Android.bp
+++ b/tests/unittest/Android.bp
@@ -30,7 +30,6 @@
     // Shared libraries which are dependencies of minikin; these are not automatically
     // pulled in by the build system (and thus sadly must be repeated).
     shared_libs: [
-        "libskia",
         "libft2",
         "libharfbuzz_ng",
         "libicuuc",
@@ -40,6 +39,8 @@
     ],
 
     srcs: [
+        "AndroidLineBreakerHelperTest.cpp",
+        "BidiUtilsTest.cpp",
         "CmapCoverageTest.cpp",
         "EmojiTest.cpp",
         "FontCollectionTest.cpp",
@@ -47,30 +48,27 @@
         "FontFamilyTest.cpp",
         "FontLanguageListCacheTest.cpp",
         "FontUtilsTest.cpp",
-        "HbFontCacheTest.cpp",
+        "HyphenatorMapTest.cpp",
         "HyphenatorTest.cpp",
         "GraphemeBreakTests.cpp",
+        "GreedyLineBreakerTest.cpp",
+        "LayoutCacheTest.cpp",
         "LayoutTest.cpp",
         "LayoutUtilsTest.cpp",
-        "LineBreakerTest.cpp",
+        "LocaleListTest.cpp",
+        "MeasuredTextTest.cpp",
         "MeasurementTests.cpp",
+        "OptimalLineBreakerTest.cpp",
         "SparseBitSetTest.cpp",
+        "StringPieceTest.cpp",
+        "TestMain.cpp",
         "UnicodeUtilsTest.cpp",
         "WordBreakerTests.cpp",
     ],
 
-    cppflags: [
+    cflags: [
         "-Werror",
         "-Wall",
         "-Wextra",
     ],
-
-    multilib: {
-        lib32: {
-            cppflags: ["-DkTestFontDir=\"/data/nativetest/minikin_tests/data/\""],
-        },
-        lib64: {
-            cppflags: ["-DkTestFontDir=\"/data/nativetest64/minikin_tests/data/\""],
-        },
-    },
 }
diff --git a/tests/unittest/AndroidLineBreakerHelperTest.cpp b/tests/unittest/AndroidLineBreakerHelperTest.cpp
new file mode 100644
index 0000000..ea977c0
--- /dev/null
+++ b/tests/unittest/AndroidLineBreakerHelperTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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/AndroidLineBreakerHelper.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+namespace android {
+
+TEST(AndroidLineWidth, negativeWidthTest) {
+    const int LINE_COUNT = 10;
+    const std::vector<float> EMPTY;
+    {
+        AndroidLineWidth lineWidth(-10 /* first width */, 1 /* first count */, 0 /* rest width */,
+                                   EMPTY, EMPTY, EMPTY, 0);
+
+        EXPECT_LE(0.0f, lineWidth.getMin());
+        for (int i = 0; i < LINE_COUNT; ++i) {
+            EXPECT_LE(0.0f, lineWidth.getAt(i));
+        }
+    }
+    {
+        AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, -10 /* rest width */,
+                                   EMPTY, EMPTY, EMPTY, 0);
+
+        EXPECT_LE(0.0f, lineWidth.getMin());
+        for (int i = 0; i < LINE_COUNT; ++i) {
+            EXPECT_LE(0.0f, lineWidth.getAt(i));
+        }
+    }
+    {
+        std::vector<float> indents = {10};
+        AndroidLineWidth lineWidth(0 /* first width */, 0 /* first count */, 0 /* rest width */,
+                                   indents, EMPTY, EMPTY, 0);
+
+        EXPECT_LE(0.0f, lineWidth.getMin());
+        for (int i = 0; i < LINE_COUNT; ++i) {
+            EXPECT_LE(0.0f, lineWidth.getAt(i));
+        }
+    }
+}
+
+}  // namespace android
+}  // namespace minikin
diff --git a/tests/unittest/BidiUtilsTest.cpp b/tests/unittest/BidiUtilsTest.cpp
new file mode 100644
index 0000000..f743a66
--- /dev/null
+++ b/tests/unittest/BidiUtilsTest.cpp
@@ -0,0 +1,536 @@
+/*
+ * 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 "BidiUtils.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/Range.h"
+
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+const char LTR_1[] = "Hello, World";
+const char RTL_1[] = "\u0627\u0644\u0633\u0644\u0627\u0645\u0020\u0639\u0644\u064A\u0643\u0645";
+const char LTR_2[] = "Hello, Android";
+const char RTL_2[] = "\u0639\u0644\u064A\u0643\u0645\u0020\u0627\u0644\u0633\u0644\u0627\u0645";
+
+TEST(BidiUtilsTest, AllLTRCharText) {
+    auto text = utf8ToUtf16(LTR_1);
+    uint32_t ltrLength = text.size();
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+TEST(BidiUtilsTest, AllRTLCharText) {
+    auto text = utf8ToUtf16(RTL_1);
+    uint32_t rtlLength = text.size();
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+TEST(BidiUtilsTest, LTR_RTL_CharText) {
+    auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1);
+    uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+    uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : R2 R1 L1 L2
+        BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : L1 L2 R1 R2
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2
+        // Visual Run : R2 R1 L2 L1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+TEST(BidiUtilsTest, RTL_LTR_CharText) {
+    auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1);
+    uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+    uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : R2 R1 L1 L2
+        BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : R1 R2 L1 L2
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2
+        // Visual Run : L2 L1 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+TEST(BidiUtilsTest, LTR_RTL_LTR_CharText) {
+    auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1 + LTR_2);
+    uint32_t ltr1Length = utf8ToUtf16(LTR_1).size();
+    uint32_t ltr2Length = utf8ToUtf16(LTR_2).size();
+    uint32_t rtlLength = utf8ToUtf16(RTL_1).size();
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L1 L2 R2 R1 L3 L4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L3 L4 R2 R1 L1 2L
+        BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length + rtlLength, text.size()), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L1 L2 R2 R1 L3 L4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L1 L2 R2 R1 L3 L4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L1 L2 R2 R1 L3 L4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: L1 L2 R1 R2 L3 L4
+        // Visual Run : L1 L2 R2 R1 L3 L4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+TEST(BidiUtilsTest, RTL_LTR_RTL_CharText) {
+    auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1 + RTL_2);
+    uint32_t ltrLength = utf8ToUtf16(LTR_1).size();
+    uint32_t rtl1Length = utf8ToUtf16(RTL_1).size();
+    uint32_t rtl2Length = utf8ToUtf16(RTL_2).size();
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R2 R1 L1 L2 R4 R3
+        BidiText bidiText(text, Range(0, text.size()), Bidi::LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R4 R3 L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R4 R3 L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R4 R3 L1 L2 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R1 R2 L1 L2 R3 R4
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range);
+        EXPECT_FALSE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+    {
+        // Logical Run: R1 R2 L1 L2 R3 R4
+        // Visual Run : R4 R3 L2 L1 R2 R1
+        BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL);
+        auto it = bidiText.begin();
+        EXPECT_NE(bidiText.end(), it);
+        EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range);
+        EXPECT_TRUE((*it).isRtl);
+        ++it;
+        EXPECT_EQ(bidiText.end(), it);
+    }
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/CmapCoverageTest.cpp b/tests/unittest/CmapCoverageTest.cpp
index 590a363..9dba583 100644
--- a/tests/unittest/CmapCoverageTest.cpp
+++ b/tests/unittest/CmapCoverageTest.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include "minikin/CmapCoverage.h"
+
 #include <random>
 
-#include <log/log.h>
 #include <gtest/gtest.h>
-#include <minikin/CmapCoverage.h>
-#include <minikin/SparseBitSet.h>
+#include <log/log.h>
+
+#include "minikin/SparseBitSet.h"
 
 #include "MinikinInternal.h"
 
@@ -64,22 +66,23 @@
 static std::vector<uint8_t> buildCmapFormat4Table(const std::vector<uint16_t>& ranges) {
     uint16_t segmentCount = ranges.size() / 2 + 1 /* +1 for end marker */;
 
-    const size_t numOfUint16 =
-        8 /* format, length, languages, segCountX2, searchRange, entrySelector, rangeShift, pad */ +
-        segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
+    const size_t numOfUint16 = 8 /* format, length, languages, segCountX2, searchRange,
+                                    entrySelector, rangeShift, pad */
+                               +
+                               segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
     const size_t finalLength = sizeof(uint16_t) * numOfUint16;
 
     std::vector<uint8_t> out(finalLength);
     size_t head = 0;
-    head = writeU16(4, out.data(), head);  // format
+    head = writeU16(4, out.data(), head);            // format
     head = writeU16(finalLength, out.data(), head);  // length
-    head = writeU16(0, out.data(), head);  // langauge
+    head = writeU16(0, out.data(), head);            // langauge
 
     const uint16_t searchRange = 2 * (1 << static_cast<int>(floor(log2(segmentCount))));
 
-    head = writeU16(segmentCount * 2, out.data(), head);  // segCountX2
-    head = writeU16(searchRange, out.data(), head);  // searchRange
-    head = writeU16(__builtin_ctz(searchRange) - 1, out.data(), head); // entrySelector
+    head = writeU16(segmentCount * 2, out.data(), head);                // segCountX2
+    head = writeU16(searchRange, out.data(), head);                     // searchRange
+    head = writeU16(__builtin_ctz(searchRange) - 1, out.data(), head);  // entrySelector
     head = writeU16(segmentCount * 2 - searchRange, out.data(), head);  // rangeShift
 
     size_t endCountHead = head;
@@ -113,18 +116,19 @@
 // 'a' (U+0061) is mapped to Glyph ID = 0x0061).
 // 'range' should be specified with inclusive-inclusive values.
 static std::vector<uint8_t> buildCmapFormat12Table(const std::vector<uint32_t>& ranges) {
-    uint32_t numGroups  = ranges.size() / 2;
+    uint32_t numGroups = ranges.size() / 2;
 
     const size_t finalLength = 2 /* format */ + 2 /* reserved */ + 4 /* length */ +
-        4 /* languages */ + 4 /* numGroups */ + 12 /* size of a group */ * numGroups;
+                               4 /* languages */ + 4 /* numGroups */ +
+                               12 /* size of a group */ * numGroups;
 
     std::vector<uint8_t> out(finalLength);
     size_t head = 0;
-    head = writeU16(12, out.data(), head);  // format
-    head = writeU16(0, out.data(), head);  // reserved
+    head = writeU16(12, out.data(), head);           // format
+    head = writeU16(0, out.data(), head);            // reserved
     head = writeU32(finalLength, out.data(), head);  // length
-    head = writeU32(0, out.data(), head);  // langauge
-    head = writeU32(numGroups, out.data(), head);  // numGroups
+    head = writeU32(0, out.data(), head);            // langauge
+    head = writeU32(numGroups, out.data(), head);    // numGroups
 
     for (uint32_t i = 0; i < numGroups; ++i) {
         const uint32_t start = ranges[2 * i];
@@ -153,7 +157,7 @@
         }
         const size_t numOfRanges = defaultUVSRanges.size() / 2;
         const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ +
-            numOfRanges * 4 /* size of Unicode Range Table */;
+                              numOfRanges * 4 /* size of Unicode Range Table */;
 
         std::vector<uint8_t> out(length);
         size_t head = 0;
@@ -173,7 +177,7 @@
             return std::vector<uint8_t>();
         }
         const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ +
-            nonDefaultUVS.size() * 5 /* size of UVS Mapping Record */;
+                              nonDefaultUVS.size() * 5 /* size of UVS Mapping Record */;
 
         std::vector<uint8_t> out(length);
         size_t head = 0;
@@ -189,15 +193,14 @@
 
 static std::vector<uint8_t> buildCmapFormat14Table(
         const std::vector<VariationSelectorRecord>& vsRecords) {
-
     const size_t headerLength = sizeof(uint16_t) /* format */ + sizeof(uint32_t) /* length */ +
-            sizeof(uint32_t) /* numVarSelectorRecords */ +
-            11 /* size of variation selector record */ * vsRecords.size();
+                                sizeof(uint32_t) /* numVarSelectorRecords */ +
+                                11 /* size of variation selector record */ * vsRecords.size();
 
     std::vector<uint8_t> out(headerLength);
     size_t head = 0;
-    head = writeU16(14, out.data(), head);  // format
-    head += sizeof(uint32_t);  // length will be filled later
+    head = writeU16(14, out.data(), head);                // format
+    head += sizeof(uint32_t);                             // length will be filled later
     head = writeU32(vsRecords.size(), out.data(), head);  // numVarSelectorRecords;
 
     for (const auto& record : vsRecords) {
@@ -232,14 +235,13 @@
 
     CmapBuilder(int numTables) : mNumTables(numTables), mCurrentTableIndex(0) {
         const size_t headerSize =
-            2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
+                2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
         out.resize(headerSize);
         writeU16(0, out.data(), 0);
         writeU16(numTables, out.data(), 2);
     }
 
-    void appendTable(uint16_t platformId, uint16_t encodingId,
-            const std::vector<uint8_t>& table) {
+    void appendTable(uint16_t platformId, uint16_t encodingId, const std::vector<uint8_t>& table) {
         appendEncodingTable(platformId, encodingId, out.size());
         out.insert(out.end(), table.begin(), table.end());
     }
@@ -251,14 +253,14 @@
 
     // Helper functions.
     static std::vector<uint8_t> buildSingleFormat4Cmap(uint16_t platformId, uint16_t encodingId,
-            const std::vector<uint16_t>& ranges) {
+                                                       const std::vector<uint16_t>& ranges) {
         CmapBuilder builder(1);
         builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges));
         return builder.build();
     }
 
     static std::vector<uint8_t> buildSingleFormat12Cmap(uint16_t platformId, uint16_t encodingId,
-            const std::vector<uint32_t>& ranges) {
+                                                        const std::vector<uint32_t>& ranges) {
         CmapBuilder builder(1);
         builder.appendTable(platformId, encodingId, buildCmapFormat12Table(ranges));
         return builder.build();
@@ -316,8 +318,8 @@
     }
     {
         SCOPED_TRACE("Reversed range");
-        std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>(
-                {'b', 'b', 'a', 'a'}));
+        std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
+                0, 0, std::vector<uint16_t>({'b', 'b', 'a', 'a'}));
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
         EXPECT_EQ(0U, coverage.length());
@@ -325,8 +327,8 @@
     }
     {
         SCOPED_TRACE("Reversed range - partially readable");
-        std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>(
-                { 'a', 'a', 'c', 'c', 'b', 'b'}));
+        std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
+                0, 0, std::vector<uint16_t>({'a', 'a', 'c', 'c', 'b', 'b'}));
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
         EXPECT_EQ(0U, coverage.length());
@@ -341,11 +343,9 @@
         uint16_t platformId;
         uint16_t encodingId;
     } TEST_CASES[] = {
-        { "Platform 0, Encoding 0", 0, 0 },
-        { "Platform 0, Encoding 1", 0, 1 },
-        { "Platform 0, Encoding 2", 0, 2 },
-        { "Platform 0, Encoding 3", 0, 3 },
-        { "Platform 3, Encoding 1", 3, 1 },
+            {"Platform 0, Encoding 0", 0, 0}, {"Platform 0, Encoding 1", 0, 1},
+            {"Platform 0, Encoding 2", 0, 2}, {"Platform 0, Encoding 3", 0, 3},
+            {"Platform 3, Encoding 1", 3, 1},
     };
 
     for (const auto& testCase : TEST_CASES) {
@@ -367,9 +367,9 @@
         uint16_t platformId;
         uint16_t encodingId;
     } TEST_CASES[] = {
-        { "Platform 0, Encoding 4", 0, 4 },
-        { "Platform 0, Encoding 6", 0, 6 },
-        { "Platform 3, Encoding 10", 3, 10 },
+            {"Platform 0, Encoding 4", 0, 4},
+            {"Platform 0, Encoding 6", 0, 6},
+            {"Platform 3, Encoding 10", 3, 10},
     };
 
     for (const auto& testCase : TEST_CASES) {
@@ -417,26 +417,26 @@
         uint16_t platformId;
         uint16_t encodingId;
     } TEST_CASES[] = {
-        // Any encodings with platform 2 is not supported.
-        { "Platform 2, Encoding 0", 2, 0 },
-        { "Platform 2, Encoding 1", 2, 1 },
-        { "Platform 2, Encoding 2", 2, 2 },
-        { "Platform 2, Encoding 3", 2, 3 },
-        // UCS-2 or UCS-4 are supported on Platform == 3. Others are not supported.
-        { "Platform 3, Encoding 0", 3, 0 },  // Symbol
-        { "Platform 3, Encoding 2", 3, 2 },  // ShiftJIS
-        { "Platform 3, Encoding 3", 3, 3 },  // RPC
-        { "Platform 3, Encoding 4", 3, 4 },  // Big5
-        { "Platform 3, Encoding 5", 3, 5 },  // Wansung
-        { "Platform 3, Encoding 6", 3, 6 },  // Johab
-        { "Platform 3, Encoding 7", 3, 7 },  // Reserved
-        { "Platform 3, Encoding 8", 3, 8 },  // Reserved
-        { "Platform 3, Encoding 9", 3, 9 },  // Reserved
-        // Uknown platforms
-        { "Platform 4, Encoding 0", 4, 0 },
-        { "Platform 5, Encoding 1", 5, 1 },
-        { "Platform 6, Encoding 0", 6, 0 },
-        { "Platform 7, Encoding 1", 7, 1 },
+            // Any encodings with platform 2 is not supported.
+            {"Platform 2, Encoding 0", 2, 0},
+            {"Platform 2, Encoding 1", 2, 1},
+            {"Platform 2, Encoding 2", 2, 2},
+            {"Platform 2, Encoding 3", 2, 3},
+            // UCS-2 or UCS-4 are supported on Platform == 3. Others are not supported.
+            {"Platform 3, Encoding 0", 3, 0},  // Symbol
+            {"Platform 3, Encoding 2", 3, 2},  // ShiftJIS
+            {"Platform 3, Encoding 3", 3, 3},  // RPC
+            {"Platform 3, Encoding 4", 3, 4},  // Big5
+            {"Platform 3, Encoding 5", 3, 5},  // Wansung
+            {"Platform 3, Encoding 6", 3, 6},  // Johab
+            {"Platform 3, Encoding 7", 3, 7},  // Reserved
+            {"Platform 3, Encoding 8", 3, 8},  // Reserved
+            {"Platform 3, Encoding 9", 3, 9},  // Reserved
+            // Uknown platforms
+            {"Platform 4, Encoding 0", 4, 0},
+            {"Platform 5, Encoding 1", 5, 1},
+            {"Platform 6, Encoding 0", 6, 0},
+            {"Platform 7, Encoding 1", 7, 1},
     };
 
     for (const auto& testCase : TEST_CASES) {
@@ -490,6 +490,31 @@
         EXPECT_EQ(0U, coverage.length());
         EXPECT_TRUE(vsTables.empty());
     }
+    {
+        SCOPED_TRACE("Reversed end code points");
+        std::vector<uint8_t> table =
+                buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b', 'a', 'a'}));
+        CmapBuilder builder(1);
+        builder.appendTable(0, 0, table);
+        std::vector<uint8_t> cmap = builder.build();
+
+        SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+        EXPECT_EQ(0U, coverage.length());
+        EXPECT_TRUE(vsTables.empty());
+    }
+}
+
+TEST(CmapCoverageTest, duplicatedCmap4EntryTest) {
+    std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+    std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'b', 'b', 'b'}));
+    CmapBuilder builder(1);
+    builder.appendTable(0, 0, table);
+    std::vector<uint8_t> cmap = builder.build();
+
+    SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+    EXPECT_TRUE(coverage.get('a'));
+    EXPECT_TRUE(coverage.get('b'));
+    EXPECT_TRUE(vsTables.empty());
 }
 
 TEST(CmapCoverageTest, brokenFormat12Table) {
@@ -578,13 +603,8 @@
             uint16_t encodingId;
             const std::vector<uint8_t>& table;
         } LOWER_PRIORITY_TABLES[] = {
-            { 0, 0, format4 },
-            { 0, 1, format4 },
-            { 0, 2, format4 },
-            { 0, 3, format4 },
-            { 0, 4, format12 },
-            { 0, 6, format12 },
-            { 3, 1, format4 },
+                {0, 0, format4},  {0, 1, format4},  {0, 2, format4}, {0, 3, format4},
+                {0, 4, format12}, {0, 6, format12}, {3, 1, format4},
         };
 
         for (const auto& table : LOWER_PRIORITY_TABLES) {
@@ -594,7 +614,7 @@
             std::vector<uint8_t> cmap = builder.build();
 
             SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-            EXPECT_TRUE(coverage.get('a'));  // comes from highest table
+            EXPECT_TRUE(coverage.get('a'));   // comes from highest table
             EXPECT_FALSE(coverage.get('b'));  // should not use other table.
             EXPECT_TRUE(vsTables.empty());
         }
@@ -607,10 +627,7 @@
             uint16_t encodingId;
             const std::vector<uint8_t>& table;
         } LOWER_PRIORITY_TABLES[] = {
-            { 0, 0, format4 },
-            { 0, 1, format4 },
-            { 0, 2, format4 },
-            { 0, 3, format4 },
+                {0, 0, format4}, {0, 1, format4}, {0, 2, format4}, {0, 3, format4},
         };
 
         for (const auto& table : LOWER_PRIORITY_TABLES) {
@@ -620,7 +637,7 @@
             std::vector<uint8_t> cmap = builder.build();
 
             SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-            EXPECT_TRUE(coverage.get('a'));  // comes from highest table
+            EXPECT_TRUE(coverage.get('a'));   // comes from highest table
             EXPECT_FALSE(coverage.get('b'));  // should not use other table.
             EXPECT_TRUE(vsTables.empty());
         }
@@ -640,7 +657,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -654,7 +671,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -668,7 +685,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -676,8 +693,7 @@
 
 TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) {
     std::vector<std::unique_ptr<SparseBitSet>> vsTables;
-    std::vector<uint8_t> validTable =
-            buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
+    std::vector<uint8_t> validTable = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
     {
         SCOPED_TRACE("Unsupported format");
         CmapBuilder builder(2);
@@ -688,7 +704,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -702,7 +718,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -716,7 +732,7 @@
         std::vector<uint8_t> cmap = builder.build();
 
         SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
-        EXPECT_TRUE(coverage.get('a'));  // comes from valid table
+        EXPECT_TRUE(coverage.get('a'));   // comes from valid table
         EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.
         EXPECT_TRUE(vsTables.empty());
     }
@@ -726,9 +742,9 @@
     std::vector<uint8_t> smallLetterTable =
             buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
     std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-        { 0xFE0E, { 'a', 'b' }, {} /* no non-default UVS table */ },
-        { 0xFE0F, {} /* no default UVS table */, { 'a', 'b'} },
-        { 0xE0100, { 'a', 'a' }, { 'b'} },
+            {0xFE0E, {'a', 'b'}, {} /* no non-default UVS table */},
+            {0xFE0F, {} /* no default UVS table */, {'a', 'b'}},
+            {0xE0100, {'a', 'a'}, {'b'}},
     }));
     CmapBuilder builder(2);
     builder.appendTable(3, 1, smallLetterTable);
@@ -763,12 +779,20 @@
     std::vector<uint8_t> smallLetterTable =
             buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
     std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-        { 0xFE0E, { 'a', 'e' }, { 'c', 'd', } },
-        { 0xFE0F, { 'c', 'e'} , { 'a', 'b', 'c', 'd', 'e'} },
-        { 0xE0100, { 'a', 'c' }, { 'b', 'c', 'd' } },
-        { 0xE0101, { 'b', 'd'} , { 'a', 'b', 'c', 'd'} },
-        { 0xE0102, { 'a', 'c', 'd', 'g'} , { 'b', 'c', 'd', 'e', 'f', 'g', 'h'} },
-        { 0xE0103, { 'a', 'f'} , { 'b', 'd', } },
+            {0xFE0E,
+             {'a', 'e'},
+             {
+                     'c', 'd',
+             }},
+            {0xFE0F, {'c', 'e'}, {'a', 'b', 'c', 'd', 'e'}},
+            {0xE0100, {'a', 'c'}, {'b', 'c', 'd'}},
+            {0xE0101, {'b', 'd'}, {'a', 'b', 'c', 'd'}},
+            {0xE0102, {'a', 'c', 'd', 'g'}, {'b', 'c', 'd', 'e', 'f', 'g', 'h'}},
+            {0xE0103,
+             {'a', 'f'},
+             {
+                     'b', 'd',
+             }},
     }));
     CmapBuilder builder(2);
     builder.appendTable(3, 1, smallLetterTable);
@@ -841,25 +865,23 @@
     std::vector<uint8_t> cmap12Table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'}));
     {
         SCOPED_TRACE("Too small cmap size");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0E, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0E, {'a', 'a'}, {'b'}}}));
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
         builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
         std::vector<uint8_t> cmap = builder.build();
 
         std::vector<std::unique_ptr<SparseBitSet>> vsTables;
-        SparseBitSet coverage = CmapCoverage::getCoverage(
-                cmap.data(), 3 /* too small size */, &vsTables);
+        SparseBitSet coverage =
+                CmapCoverage::getCoverage(cmap.data(), 3 /* too small size */, &vsTables);
         EXPECT_FALSE(coverage.get('a'));
         ASSERT_TRUE(vsTables.empty());
     }
     {
         SCOPED_TRACE("Too many variation records");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
         writeU32(5000, vsTable.data(), 6 /* numVarSelectorRecord offset */);
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
@@ -872,9 +894,8 @@
     }
     {
         SCOPED_TRACE("Invalid default UVS offset in variation records");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
         writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the first record */);
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
@@ -887,9 +908,8 @@
     }
     {
         SCOPED_TRACE("Invalid non default UVS offset in variation records");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
         writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */);
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
@@ -902,9 +922,8 @@
     }
     {
         SCOPED_TRACE("Too many ranges entry in default UVS table");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
         // 21 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
         writeU32(5000, vsTable.data(), 21);
         CmapBuilder builder(2);
@@ -918,9 +937,8 @@
     }
     {
         SCOPED_TRACE("Too many ranges entry in non default UVS table");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
         // 29 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
         writeU32(5000, vsTable.data(), 29);
         CmapBuilder builder(2);
@@ -934,9 +952,8 @@
     }
     {
         SCOPED_TRACE("Reversed range in default UVS table");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'b', 'b', 'a', 'a' }, { } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'b', 'b', 'a', 'a'}, {}}}));
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
         builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -948,9 +965,8 @@
     }
     {
         SCOPED_TRACE("Reversed range in default UVS table - partially readable");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a', 'c', 'c', 'b', 'b' }, { } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>(
+                {{0xFE0F, {'a', 'a', 'c', 'c', 'b', 'b'}, {}}}));
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
         builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -962,9 +978,8 @@
     }
     {
         SCOPED_TRACE("Reversed mapping entries in non default UVS table");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { }, { 'b', 'a' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'b', 'a'}}}));
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
         builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -976,9 +991,56 @@
     }
     {
         SCOPED_TRACE("Reversed mapping entries in non default UVS table");
-        std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { }, { 'a', 'c', 'b' } }
-        }));
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'a', 'c', 'b'}}}));
+        CmapBuilder builder(2);
+        builder.appendTable(3, 1, cmap12Table);
+        builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+        std::vector<uint8_t> cmap = builder.build();
+
+        std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+        SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+        ASSERT_TRUE(vsTables.empty());
+    }
+    {
+        // http://b/70808908
+        SCOPED_TRACE("OOB access due to integer overflow in non default UVS table");
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+        // 6 is the offset of the numRecords in the Cmap format14 subtable header.
+        writeU32(0x1745d174 /* 2^32 / kRecordSize(=11) */, vsTable.data(), 6);
+        CmapBuilder builder(2);
+        builder.appendTable(3, 1, cmap12Table);
+        builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+        std::vector<uint8_t> cmap = builder.build();
+
+        std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+        SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+        ASSERT_TRUE(vsTables.empty());
+    }
+    {
+        // http://b/70808908
+        SCOPED_TRACE("OOB access due to integer overflow in non default UVS table");
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+        // 29 is the offset of the numUVSMappings in the fist non defulat UVS table.
+        writeU32(0x33333333 /* 2^32 / kUVSMappingRecordSize(=5) */, vsTable.data(), 29);
+        CmapBuilder builder(2);
+        builder.appendTable(3, 1, cmap12Table);
+        builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
+        std::vector<uint8_t> cmap = builder.build();
+
+        std::vector<std::unique_ptr<SparseBitSet>> vsTables;
+        SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables);
+        ASSERT_TRUE(vsTables.empty());
+    }
+    {
+        // http://b/70808908
+        SCOPED_TRACE("OOB access due to integer overflow in default UVS table");
+        std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+                std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'a'}, {'b'}}}));
+        // 21 is the offset of the numUnicodeValueRanges in the fist defulat UVS table.
+        writeU32(0x40000000 /* 2^32 / kUnicodeRangeRecordSize(=4) */, vsTable.data(), 21);
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
         builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
@@ -1043,8 +1105,7 @@
     {
         SCOPED_TRACE("Invalid default UVS offset in variation records");
         std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0E, { 'a', 'a' }, { 'b' } },
-            { 0xFE0F, { 'a', 'a' }, { 'b' } },
+                {0xFE0E, {'a', 'a'}, {'b'}}, {0xFE0F, {'a', 'a'}, {'b'}},
         }));
         writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the record for 0xFE0E */);
         CmapBuilder builder(2);
@@ -1067,8 +1128,7 @@
     {
         SCOPED_TRACE("Invalid non default UVS offset in variation records");
         std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0E, { 'a', 'a' }, { 'b' } },
-            { 0xFE0F, { 'a', 'a' }, { 'b' } },
+                {0xFE0E, {'a', 'a'}, {'b'}}, {0xFE0F, {'a', 'a'}, {'b'}},
         }));
         writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */);
         CmapBuilder builder(2);
@@ -1091,8 +1151,7 @@
     {
         SCOPED_TRACE("Unknown variation selectors.");
         std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-            { 0xFE0F, { 'a', 'a' }, { 'b' } },
-            { 0xEFFFF, { 'a', 'a' }, { 'b' } },
+                {0xFE0F, {'a', 'a'}, {'b'}}, {0xEFFFF, {'a', 'a'}, {'b'}},
         }));
         CmapBuilder builder(2);
         builder.appendTable(3, 1, cmap12Table);
@@ -1116,9 +1175,8 @@
 TEST(CmapCoverageTest, TableSelection_defaultUVSPointMissingGlyph) {
     std::vector<uint8_t> baseTable = buildCmapFormat12Table(std::vector<uint32_t>(
             {RANGE('a', 'e'), RANGE('g', 'h'), RANGE('j', 'j'), RANGE('m', 'z')}));
-    std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-        { 0xFE0F, { 'a', 'z' }, { } }
-    }));
+    std::vector<uint8_t> vsTable = buildCmapFormat14Table(
+            std::vector<VariationSelectorRecord>({{0xFE0F, {'a', 'z'}, {}}}));
 
     CmapBuilder builder(2);
     builder.appendTable(3, 1, baseTable);
@@ -1131,7 +1189,7 @@
     ASSERT_LT(vsIndex, vsTables.size());
     ASSERT_TRUE(vsTables[vsIndex]);
 
-    for (char c = 'a'; c <= 'z'; ++c)  {
+    for (char c = 'a'; c <= 'z'; ++c) {
         // Default UVS table points the variation sequence to the glyph of the base code point.
         // Thus, if the base code point is not supported, we should exclude them.
         EXPECT_EQ(coverage.get(c), vsTables[vsIndex]->get(c)) << c;
@@ -1141,9 +1199,8 @@
 #undef RANGE
 
 TEST(CmapCoverageTest, TableSelection_vsTableOnly) {
-    std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({
-        { 0xFE0F, { }, { 'a' } }
-    }));
+    std::vector<uint8_t> vsTable =
+            buildCmapFormat14Table(std::vector<VariationSelectorRecord>({{0xFE0F, {}, {'a'}}}));
 
     CmapBuilder builder(1);
     builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable);
diff --git a/tests/unittest/EmojiTest.cpp b/tests/unittest/EmojiTest.cpp
index e7d0f56..32ffde7 100644
--- a/tests/unittest/EmojiTest.cpp
+++ b/tests/unittest/EmojiTest.cpp
@@ -14,27 +14,26 @@
  * limitations under the License.
  */
 
+#include "minikin/Emoji.h"
+
 #include <gtest/gtest.h>
-
 #include <unicode/uchar.h>
 
-#include <minikin/Emoji.h>
-
 namespace minikin {
 
 TEST(EmojiTest, isEmojiTest) {
-    EXPECT_TRUE(isEmoji(0x0023));  // NUMBER SIGN
-    EXPECT_TRUE(isEmoji(0x0035));  // DIGIT FIVE
-    EXPECT_TRUE(isEmoji(0x2640));  // FEMALE SIGN
-    EXPECT_TRUE(isEmoji(0x2642));  // MALE SIGN
-    EXPECT_TRUE(isEmoji(0x2695));  // STAFF OF AESCULAPIUS
+    EXPECT_TRUE(isEmoji(0x0023));   // NUMBER SIGN
+    EXPECT_TRUE(isEmoji(0x0035));   // DIGIT FIVE
+    EXPECT_TRUE(isEmoji(0x2640));   // FEMALE SIGN
+    EXPECT_TRUE(isEmoji(0x2642));   // MALE SIGN
+    EXPECT_TRUE(isEmoji(0x2695));   // STAFF OF AESCULAPIUS
     EXPECT_TRUE(isEmoji(0x1F0CF));  // PLAYING CARD BLACK JOKER
     EXPECT_TRUE(isEmoji(0x1F1E9));  // REGIONAL INDICATOR SYMBOL LETTER D
     EXPECT_TRUE(isEmoji(0x1F6F7));  // SLED
     EXPECT_TRUE(isEmoji(0x1F9E6));  // SOCKS
 
-    EXPECT_FALSE(isEmoji(0x0000));  // <control>
-    EXPECT_FALSE(isEmoji(0x0061));  // LATIN SMALL LETTER A
+    EXPECT_FALSE(isEmoji(0x0000));   // <control>
+    EXPECT_FALSE(isEmoji(0x0061));   // LATIN SMALL LETTER A
     EXPECT_FALSE(isEmoji(0x1F93B));  // MODERN PENTATHLON
     EXPECT_FALSE(isEmoji(0x1F946));  // RIFLE
     EXPECT_FALSE(isEmoji(0x29E3D));  // A han character.
@@ -47,15 +46,15 @@
     EXPECT_TRUE(isEmojiModifier(0x1F3FE));  // EMOJI MODIFIER FITZPATRICK TYPE-5
     EXPECT_TRUE(isEmojiModifier(0x1F3FF));  // EMOJI MODIFIER FITZPATRICK TYPE-6
 
-    EXPECT_FALSE(isEmojiModifier(0x0000));  // <control>
+    EXPECT_FALSE(isEmojiModifier(0x0000));   // <control>
     EXPECT_FALSE(isEmojiModifier(0x1F3FA));  // AMPHORA
     EXPECT_FALSE(isEmojiModifier(0x1F400));  // RAT
     EXPECT_FALSE(isEmojiModifier(0x29E3D));  // A han character.
 }
 
 TEST(EmojiTest, isEmojiBaseTest) {
-    EXPECT_TRUE(isEmojiBase(0x261D));  // WHITE UP POINTING INDEX
-    EXPECT_TRUE(isEmojiBase(0x270D));  // WRITING HAND
+    EXPECT_TRUE(isEmojiBase(0x261D));   // WHITE UP POINTING INDEX
+    EXPECT_TRUE(isEmojiBase(0x270D));   // WRITING HAND
     EXPECT_TRUE(isEmojiBase(0x1F385));  // FATHER CHRISTMAS
     EXPECT_TRUE(isEmojiBase(0x1F3C2));  // SNOWBOARDER
     EXPECT_TRUE(isEmojiBase(0x1F3C7));  // HORSE RACING
@@ -70,8 +69,8 @@
     EXPECT_TRUE(isEmojiBase(0x1F9D1));  // ADULT
     EXPECT_TRUE(isEmojiBase(0x1F9DD));  // ELF
 
-    EXPECT_FALSE(isEmojiBase(0x0000));  // <control>
-    EXPECT_FALSE(isEmojiBase(0x261C));  // WHITE LEFT POINTING INDEX
+    EXPECT_FALSE(isEmojiBase(0x0000));   // <control>
+    EXPECT_FALSE(isEmojiBase(0x261C));   // WHITE LEFT POINTING INDEX
     EXPECT_FALSE(isEmojiBase(0x1F384));  // CHRISTMAS TREE
     EXPECT_FALSE(isEmojiBase(0x1F9DE));  // GENIE
     EXPECT_FALSE(isEmojiBase(0x29E3D));  // A han character.
@@ -80,7 +79,7 @@
 TEST(EmojiTest, emojiBidiOverrideTest) {
     EXPECT_EQ(U_RIGHT_TO_LEFT, emojiBidiOverride(nullptr, 0x05D0));  // HEBREW LETTER ALEF
     EXPECT_EQ(U_LEFT_TO_RIGHT,
-            emojiBidiOverride(nullptr, 0x1F170));  // NEGATIVE SQUARED LATIN CAPITAL LETTER A
+              emojiBidiOverride(nullptr, 0x1F170));  // NEGATIVE SQUARED LATIN CAPITAL LETTER A
     EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F6F7));  // SLED
     EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F9E6));  // SOCKS
 }
diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index a031908..c9abc10 100644
--- a/tests/unittest/FontCollectionItemizeTest.cpp
+++ b/tests/unittest/FontCollectionItemizeTest.cpp
@@ -14,83 +14,105 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#include "minikin/FontCollection.h"
 
 #include <memory>
 
-#include "FontLanguageListCache.h"
-#include "FontLanguage.h"
+#include <gtest/gtest.h>
+
+#include "minikin/FontFamily.h"
+#include "minikin/LocaleList.h"
+
 #include "FontTestUtils.h"
-#include "ICUTestBase.h"
-#include "MinikinFontForTest.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "Locale.h"
+#include "LocaleListCache.h"
 #include "MinikinInternal.h"
 #include "UnicodeUtils.h"
-#include "minikin/FontFamily.h"
 
 namespace minikin {
 
-const char kItemizeFontXml[] = kTestFontDir "itemize.xml";
-const char kCherokeeFont[] = kTestFontDir "Cherokee.ttf";
-const char kEmojiFont[] = kTestFontDir "Emoji.ttf";
-const char kJAFont[] = kTestFontDir "Ja.ttf";
-const char kKOFont[] = kTestFontDir "Ko.ttf";
-const char kLatinBoldFont[] = kTestFontDir "Bold.ttf";
-const char kLatinBoldItalicFont[] = kTestFontDir "BoldItalic.ttf";
-const char kLatinFont[] = kTestFontDir "Regular.ttf";
-const char kLatinItalicFont[] = kTestFontDir "Italic.ttf";
-const char kZH_HansFont[] = kTestFontDir "ZhHans.ttf";
-const char kZH_HantFont[] = kTestFontDir "ZhHant.ttf";
+const char kItemizeFontXml[] = "itemize.xml";
+const char kCherokeeFont[] = "Cherokee.ttf";
+const char kEmojiFont[] = "Emoji.ttf";
+const char kJAFont[] = "Ja.ttf";
+const char kKOFont[] = "Ko.ttf";
+const char kLatinBoldFont[] = "Bold.ttf";
+const char kLatinBoldItalicFont[] = "BoldItalic.ttf";
+const char kLatinFont[] = "Regular.ttf";
+const char kLatinItalicFont[] = "Italic.ttf";
+const char kZH_HansFont[] = "ZhHans.ttf";
+const char kZH_HantFont[] = "ZhHant.ttf";
 
-const char kEmojiXmlFile[] = kTestFontDir "emoji.xml";
-const char kNoGlyphFont[] =  kTestFontDir "NoGlyphFont.ttf";
-const char kColorEmojiFont[] = kTestFontDir "ColorEmojiFont.ttf";
-const char kTextEmojiFont[] = kTestFontDir "TextEmojiFont.ttf";
-const char kMixedEmojiFont[] = kTestFontDir "ColorTextMixedEmojiFont.ttf";
+const char kEmojiXmlFile[] = "emoji.xml";
+const char kNoGlyphFont[] = "NoGlyphFont.ttf";
+const char kColorEmojiFont[] = "ColorEmojiFont.ttf";
+const char kTextEmojiFont[] = "TextEmojiFont.ttf";
+const char kMixedEmojiFont[] = "ColorTextMixedEmojiFont.ttf";
 
-const char kHasCmapFormat14Font[] =  kTestFontDir "NoCmapFormat14.ttf";
-const char kNoCmapFormat14Font[] =  kTestFontDir "VariationSelectorTest-Regular.ttf";
+const char kHasCmapFormat14Font[] = "NoCmapFormat14.ttf";
+const char kNoCmapFormat14Font[] = "VariationSelectorTest-Regular.ttf";
 
-typedef ICUTestBase FontCollectionItemizeTest;
-
-// Utility function for calling itemize function.
+// Utility functions for calling itemize function.
 void itemize(const std::shared_ptr<FontCollection>& collection, const char* str, FontStyle style,
-        std::vector<FontCollection::Run>* result) {
+             const std::string& localeList, std::vector<FontCollection::Run>* result) {
     const size_t BUF_SIZE = 256;
     uint16_t buf[BUF_SIZE];
     size_t len;
 
     result->clear();
     ParseUnicode(buf, BUF_SIZE, str, &len, NULL);
-    android::AutoMutex _l(gMinikinLock);
-    collection->itemize(buf, len, style, result);
+    const uint32_t localeListId = registerLocaleList(localeList);
+    MinikinPaint paint(collection);
+    paint.fontStyle = style;
+    paint.localeListId = localeListId;
+    collection->itemize(buf, len, paint, 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);
+}
+
+// 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);
+}
+
+// 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);
 }
 
 // Utility function to obtain font path associated with run.
-const std::string& getFontPath(const FontCollection::Run& run) {
+std::string getFontName(const FontCollection::Run& run) {
     EXPECT_NE(nullptr, run.fakedFont.font);
-    return ((MinikinFontForTest*)run.fakedFont.font)->fontPath();
+    return getBasename(
+            ((FreeTypeMinikinFontForTest*)run.fakedFont.font->typeface().get())->fontPath());
 }
 
-// Utility function to obtain FontLanguages from string.
-const FontLanguages& registerAndGetFontLanguages(const std::string& lang_string) {
-    android::AutoMutex _l(gMinikinLock);
-    return FontLanguageListCache::getById(FontLanguageListCache::getId(lang_string));
+// Utility function to obtain LocaleList from string.
+const LocaleList& registerAndGetLocaleList(const std::string& locale_string) {
+    return LocaleListCache::getById(LocaleListCache::getId(locale_string));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_latin) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_latin) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
     const FontStyle kRegularStyle = FontStyle();
-    const FontStyle kItalicStyle = FontStyle(4, true);
-    const FontStyle kBoldStyle = FontStyle(7, false);
-    const FontStyle kBoldItalicStyle = FontStyle(7, true);
+    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);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -98,7 +120,7 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinItalicFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinItalicFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -106,7 +128,7 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinBoldFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinBoldFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -114,7 +136,7 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinBoldItalicFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinBoldItalicFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -124,7 +146,7 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -132,7 +154,7 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
@@ -142,1400 +164,1361 @@
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_combining) {
+TEST(FontCollectionItemizeTest, itemize_combining) {
     // 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.
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kRegularStyle = FontStyle();
-
-    itemize(collection, "'a' U+0301", kRegularStyle, &runs);
+    itemize(collection, "'a' U+0301", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
     // CHEROKEE LETTER A, COMBINING ACUTE ACCENT
-    itemize(collection, "U+13A0 U+0301", kRegularStyle, &runs);
+    itemize(collection, "U+13A0 U+0301", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+    EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
 
     // CHEROKEE LETTER A, COMBINING ACUTE ACCENT, COMBINING ACUTE ACCENT
-    itemize(collection, "U+13A0 U+0301 U+0301", kRegularStyle, &runs);
+    itemize(collection, "U+13A0 U+0301 U+0301", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kCherokeeFont, getFontPath(runs[0]));
+    EXPECT_EQ(kCherokeeFont, getFontName(runs[0]));
 
-    itemize(collection, "U+0301", kRegularStyle, &runs);
+    itemize(collection, "U+0301", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
     // COMBINING ACUTE ACCENT, CHEROKEE LETTER A, COMBINING ACUTE ACCENT
-    itemize(collection, "U+0301 U+13A0 U+0301", kRegularStyle, &runs);
+    itemize(collection, "U+0301 U+13A0 U+0301", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(3, runs[1].end);
-    EXPECT_EQ(kCherokeeFont, getFontPath(runs[1]));
+    EXPECT_EQ(kCherokeeFont, getFontName(runs[1]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_emoji) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_emoji) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "U+1F469 U+1F467", FontStyle(), &runs);
+    itemize(collection, "U+1F469 U+1F467", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // U+20E3(COMBINING ENCLOSING KEYCAP) must be in the same run with preceding
     // character if the font supports.
-    itemize(collection, "'0' U+20E3", FontStyle(), &runs);
+    itemize(collection, "'0' U+20E3", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "U+1F470 U+20E3", FontStyle(), &runs);
+    itemize(collection, "U+1F470 U+20E3", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
-    itemize(collection, "U+242EE U+1F470 U+20E3", FontStyle(), &runs);
+    itemize(collection, "U+242EE U+1F470 U+20E3", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(5, runs[1].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[1]));
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
 
     // Currently there is no fonts which has a glyph for 'a' + U+20E3, so they
     // are splitted into two.
-    itemize(collection, "'a' U+20E3", FontStyle(), &runs);
+    itemize(collection, "'a' U+20E3", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(2, runs[1].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[1]));
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_non_latin) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_non_latin) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    FontStyle kJAStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"));
-    FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList("en_US"));
-    FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
-
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kUSStyle, &runs);
+    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", "ja-JP", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Korean Hangul characters.
-    itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", kUSStyle, &runs);
+    itemize(collection, "U+B300 U+D55C U+BBFC U+AD6D", "en-US", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kKOFont, getFontPath(runs[0]));
+    EXPECT_EQ(kKOFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // 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", kJAStyle, &runs);
+    itemize(collection, "U+81ED U+82B1 U+5FCD", "ja-JP", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // Simplified Chinese font should be selected if the specified language is Simplified
     // Chinese.
-    itemize(collection, "U+81ED U+82B1 U+5FCD", kZH_HansStyle, &runs);
+    itemize(collection, "U+81ED U+82B1 U+5FCD", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // 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", kJAStyle, &runs);
+    itemize(collection, "U+81ED U+4F60 U+5FCD", "ja-JP", &runs);
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(2, runs[1].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[1]));
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(2, runs[2].start);
     EXPECT_EQ(3, runs[2].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[2]));
+    EXPECT_EQ(kJAFont, getFontName(runs[2]));
     EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
 
     // Tone mark.
-    itemize(collection, "U+4444 U+302D", FontStyle(), &runs);
+    itemize(collection, "U+4444 U+302D", "", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // 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", kZH_HansStyle, &runs);
+    itemize(collection, "U+242EE", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_mixed) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_mixed) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList("en_US"));
-
-    itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", kUSStyle, &runs);
+    itemize(collection, "'a' U+4F60 'b' U+4F60 'c'", "en-US", &runs);
     ASSERT_EQ(5U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(2, runs[1].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[1]));
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(2, runs[2].start);
     EXPECT_EQ(3, runs[2].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[2]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[2]));
     EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(3, runs[3].start);
     EXPECT_EQ(4, runs[3].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[3]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[3]));
     EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeItalic());
 
     EXPECT_EQ(4, runs[4].start);
     EXPECT_EQ(5, runs[4].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[4]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[4]));
     EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeItalic());
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_variationSelector) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+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
     // Traditional Chinese font.  To avoid effects of device default locale,
     // explicitly specify the locale.
-    FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
-    FontStyle kZH_HantStyle = FontStyle(FontStyle::registerLanguageList("zh_Hant"));
 
     // 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", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
-    itemize(collection, "U+4FAE U+FE00", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE U+FE00", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+4FAE U+4FAE U+FE00", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE U+4FAE U+FE00", "zh-Hans", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(3, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE U+4FAE U+FE00 U+4FAE", "zh-Hans", &runs);
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(3, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
     EXPECT_EQ(3, runs[2].start);
     EXPECT_EQ(4, runs[2].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+4FAE U+FE00 U+FE00", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE U+FE00 U+FE00", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+FE0E.
-    itemize(collection, "U+4FAE U+FE0E", kZH_HansStyle, &runs);
+    itemize(collection, "U+4FAE U+FE0E", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
     // 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", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+FE00", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+FE00", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+242EE U+FE00", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+242EE U+FE00", "zh-Hant", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(5, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+242EE U+FE00 U+242EE", "zh-Hant", &runs);
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(5, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
     EXPECT_EQ(5, runs[2].start);
     EXPECT_EQ(7, runs[2].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+242EE U+FE00 U+FE00", kZH_HansStyle, &runs);
+    itemize(collection, "U+242EE U+FE00 U+FE00", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+FE0E
-    itemize(collection, "U+242EE U+FE0E", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+FE0E", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
     // Isolated variation selector supplement.
-    itemize(collection, "U+FE00", FontStyle(), &runs);
+    itemize(collection, "U+FE00", "", &runs);
     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 == getFontPath(runs[0]));
+    EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 
-    itemize(collection, "U+FE00", kZH_HantStyle, &runs);
+    itemize(collection, "U+FE00", "zh-Hant", &runs);
     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 == getFontPath(runs[0]));
+    EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 
     // 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", kZH_HantStyle, &runs);
+    itemize(collection, "U+203C U+FE0F", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
 
     // First font family (Regular.ttf) supports U+203C U+FE0E.
-    itemize(collection, "U+203C U+FE0E", kZH_HantStyle, &runs);
+    itemize(collection, "U+203C U+FE0E", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+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
     // Traditional Chinese font.  To avoid effects of device default locale,
     // explicitly specify the locale.
-    FontStyle kZH_HansStyle = FontStyle(FontStyle::registerLanguageList("zh_Hans"));
-    FontStyle kZH_HantStyle = FontStyle(FontStyle::registerLanguageList("zh_Hant"));
 
     // 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", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
-    itemize(collection, "U+845B U+E0100", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B U+E0100", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+845B U+845B U+E0100", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B U+845B U+E0100", "zh-Hans", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(4, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+845B U+845B U+E0100 U+845B", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B U+845B U+E0100 U+845B", "zh-Hans", &runs);
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
     EXPECT_EQ(1, runs[1].start);
     EXPECT_EQ(4, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
     EXPECT_EQ(4, runs[2].start);
     EXPECT_EQ(5, runs[2].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+845B U+E0100 U+E0100", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B U+E0100 U+E0100", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+845B U+E01E0.
-    itemize(collection, "U+845B U+E01E0", kZH_HansStyle, &runs);
+    itemize(collection, "U+845B U+E01E0", "zh-Hans", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HansFont, getFontName(runs[0]));
 
     // Isolated variation selector supplement
     // 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", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+E0101", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+E0101", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+242EE U+242EE U+E0101", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+242EE U+E0101", "zh-Hant", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(6, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
 
-    itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+242EE U+E0101 U+242EE", "zh-Hant", &runs);
     ASSERT_EQ(3U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(6, runs[1].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[1]));
+    EXPECT_EQ(kJAFont, getFontName(runs[1]));
     EXPECT_EQ(6, runs[2].start);
     EXPECT_EQ(8, runs[2].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[2]));
 
     // Validation selector after validation selector.
-    itemize(collection, "U+242EE U+E0100 U+E0100", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+E0100 U+E0100", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(6, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
     // No font supports U+242EE U+E01E0.
-    itemize(collection, "U+242EE U+E01E0", kZH_HantStyle, &runs);
+    itemize(collection, "U+242EE U+E01E0", "zh-Hant", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));
+    EXPECT_EQ(kZH_HantFont, getFontName(runs[0]));
 
     // Isolated variation selector supplement.
-    itemize(collection, "U+E0100", FontStyle(), &runs);
+    itemize(collection, "U+E0100", "", &runs);
     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 == getFontPath(runs[0]));
+    EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 
-    itemize(collection, "U+E0100", kZH_HantStyle, &runs);
+    itemize(collection, "U+E0100", "zh-Hant", &runs);
     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 == getFontPath(runs[0]));
+    EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_no_crash) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+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'", FontStyle(), &runs);
-    itemize(collection, "'a' U+DC69 'a'", FontStyle(), &runs);
-    itemize(collection, "'a' U+D83D U+D83D 'a'", FontStyle(), &runs);
-    itemize(collection, "'a' U+DC69 U+DC69 'a'", FontStyle(), &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'", &runs);
 
     // Isolated variation selector. Check only not crashing.
-    itemize(collection, "U+FE00 U+FE00", FontStyle(), &runs);
-    itemize(collection, "U+E0100 U+E0100", FontStyle(), &runs);
-    itemize(collection, "U+FE00 U+E0100", FontStyle(), &runs);
-    itemize(collection, "U+E0100 U+FE00", FontStyle(), &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", &runs);
 
     // Tone mark only. Check only not crashing.
-    itemize(collection, "U+302D", FontStyle(), &runs);
-    itemize(collection, "U+302D U+302D", FontStyle(), &runs);
+    itemize(collection, "U+302D", &runs);
+    itemize(collection, "U+302D U+302D", &runs);
 
     // Tone mark and variation selector mixed. Check only not crashing.
-    itemize(collection, "U+FE00 U+302D U+E0100", FontStyle(), &runs);
+    itemize(collection, "U+FE00 U+302D U+E0100", &runs);
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_fakery) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_fakery) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    FontStyle kJABoldStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 7, false);
-    FontStyle kJAItalicStyle = FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 5, true);
-    FontStyle kJABoldItalicStyle =
-           FontStyle(FontStyle::registerLanguageList("ja_JP"), 0, 7, true);
+    FontStyle kBoldStyle(FontStyle::Weight::BOLD);
+    FontStyle kItalicStyle(FontStyle::Slant::ITALIC);
+    FontStyle kBoldItalicStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
 
     // Currently there is no italic or bold font for Japanese. FontFakery has
     // the differences between desired and actual font style.
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJABoldStyle, &runs);
+    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldStyle, "ja-JP", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJAItalicStyle, &runs);
+    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kItalicStyle, "ja-JP", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
 
     // All Japanese Hiragana characters.
-    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kJABoldItalicStyle, &runs);
+    itemize(collection, "U+3042 U+3044 U+3046 U+3048 U+304A", kBoldItalicStyle, "ja-JP", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
     EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());
     EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
+TEST(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {
     // kVSTestFont supports U+717D U+FE02 but doesn't support U+717D.
     // kVSTestFont should be selected for U+717D U+FE02 even if it does not support the base code
     // point.
-    const std::string kVSTestFont = kTestFontDir "VariationSelectorTest-Regular.ttf";
+    const std::string kVSTestFont = "VariationSelectorTest-Regular.ttf";
 
     std::vector<std::shared_ptr<FontFamily>> families;
-    std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kLatinFont));
-    std::shared_ptr<FontFamily> family1(new FontFamily(VARIANT_DEFAULT,
-            std::vector<Font>{ Font(font, FontStyle()) }));
-    families.push_back(family1);
-
-    std::shared_ptr<MinikinFont> font2(new MinikinFontForTest(kVSTestFont));
-    std::shared_ptr<FontFamily> family2(new FontFamily(VARIANT_DEFAULT,
-            std::vector<Font>{ Font(font2, FontStyle()) }));
-    families.push_back(family2);
+    families.push_back(buildFontFamily(kLatinFont));
+    families.push_back(buildFontFamily(kVSTestFont));
 
     std::shared_ptr<FontCollection> collection(new FontCollection(families));
 
     std::vector<FontCollection::Run> runs;
 
-    itemize(collection, "U+717D U+FE02", FontStyle(), &runs);
+    itemize(collection, "U+717D U+FE02", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kVSTestFont, getFontPath(runs[0]));
+    EXPECT_EQ(kVSTestFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_format_chars) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+TEST(FontCollectionItemizeTest, itemize_format_chars) {
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
-    itemize(collection, "'a' U+061C 'b'", kDefaultFontStyle, &runs);
+    itemize(collection, "'a' U+061C 'b'", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "'a' U+200D 'b'", kDefaultFontStyle, &runs);
+    itemize(collection, "'a' U+200D 'b'", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+3042 U+061C U+3042", kDefaultFontStyle, &runs);
+    itemize(collection, "U+3042 U+061C U+3042", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C 'b'", kDefaultFontStyle, &runs);
+    itemize(collection, "U+061C 'b'", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C U+3042", kDefaultFontStyle, &runs);
+    itemize(collection, "U+061C U+3042", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kJAFont, getFontPath(runs[0]));
+    EXPECT_EQ(kJAFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C", kDefaultFontStyle, &runs);
+    itemize(collection, "U+061C", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+061C U+061C U+061C", kDefaultFontStyle, &runs);
+    itemize(collection, "U+061C U+061C U+061C", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+200D U+20E3", kDefaultFontStyle, &runs);
+    itemize(collection, "U+200D U+20E3", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+200D", kDefaultFontStyle, &runs);
+    itemize(collection, "U+200D", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kLatinFont, getFontPath(runs[0]));
+    EXPECT_EQ(kLatinFont, getFontName(runs[0]));
 
-    itemize(collection, "U+20E3", kDefaultFontStyle, &runs);
+    itemize(collection, "U+20E3", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kEmojiFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {
+TEST(FontCollectionItemizeTest, itemize_LocaleScore) {
     struct TestCase {
-        std::string userPreferredLanguages;
-        std::vector<std::string> fontLanguages;
+        std::string userPreferredLocale;
+        std::vector<std::string> fontLocales;
         int selectedFontIndex;
     } testCases[] = {
-        // Font can specify empty language.
-        { "und", { "", "" }, 0 },
-        { "und", { "", "en-Latn" }, 0 },
-        { "en-Latn", { "", "" }, 0 },
-        { "en-Latn", { "", "en-Latn" }, 1 },
+            // Font can specify empty locale.
+            {"und", {"", ""}, 0},
+            {"und", {"", "en-Latn"}, 0},
+            {"en-Latn", {"", ""}, 0},
+            {"en-Latn", {"", "en-Latn"}, 1},
 
-        // Single user preferred language.
-        // Exact match case
-        { "en-Latn", { "en-Latn", "ja-Jpan" }, 0 },
-        { "ja-Jpan", { "en-Latn", "ja-Jpan" }, 1 },
-        { "en-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 0 },
-        { "nl-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 1 },
-        { "es-Latn", { "en-Latn", "nl-Latn", "es-Latn" }, 2 },
-        { "es-Latn", { "en-Latn", "en-Latn", "nl-Latn" }, 0 },
+            // Single user preferred locale.
+            // Exact match case
+            {"en-Latn", {"en-Latn", "ja-Jpan"}, 0},
+            {"ja-Jpan", {"en-Latn", "ja-Jpan"}, 1},
+            {"en-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 0},
+            {"nl-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 1},
+            {"es-Latn", {"en-Latn", "nl-Latn", "es-Latn"}, 2},
+            {"es-Latn", {"en-Latn", "en-Latn", "nl-Latn"}, 0},
 
-        // Exact script match case
-        { "en-Latn", { "nl-Latn", "e-Latn" }, 0 },
-        { "en-Arab", { "nl-Latn", "ar-Arab" }, 1 },
-        { "en-Latn", { "be-Latn", "ar-Arab", "d-Beng" }, 0 },
-        { "en-Arab", { "be-Latn", "ar-Arab", "d-Beng" }, 1 },
-        { "en-Beng", { "be-Latn", "ar-Arab", "d-Beng" }, 2 },
-        { "en-Beng", { "be-Latn", "ar-Beng", "d-Beng" }, 1 },
-        { "zh-Hant", { "zh-Hant", "zh-Hans" }, 0 },
-        { "zh-Hans", { "zh-Hant", "zh-Hans" }, 1 },
+            // Exact script match case
+            {"en-Latn", {"nl-Latn", "e-Latn"}, 0},
+            {"en-Arab", {"nl-Latn", "ar-Arab"}, 1},
+            {"en-Latn", {"be-Latn", "ar-Arab", "d-Beng"}, 0},
+            {"en-Arab", {"be-Latn", "ar-Arab", "d-Beng"}, 1},
+            {"en-Beng", {"be-Latn", "ar-Arab", "d-Beng"}, 2},
+            {"en-Beng", {"be-Latn", "ar-Beng", "d-Beng"}, 1},
+            {"zh-Hant", {"zh-Hant", "zh-Hans"}, 0},
+            {"zh-Hans", {"zh-Hant", "zh-Hans"}, 1},
 
-        // Subscript match case, e.g. Jpan supports Hira.
-        { "en-Hira", { "ja-Jpan" }, 0 },
-        { "zh-Hani", { "zh-Hans", "zh-Hant" }, 0 },
-        { "zh-Hani", { "zh-Hant", "zh-Hans" }, 0 },
-        { "en-Hira", { "zh-Hant", "ja-Jpan", "ja-Jpan" }, 1 },
+            // Subscript match case, e.g. Jpan supports Hira.
+            {"en-Hira", {"ja-Jpan"}, 0},
+            {"zh-Hani", {"zh-Hans", "zh-Hant"}, 0},
+            {"zh-Hani", {"zh-Hant", "zh-Hans"}, 0},
+            {"en-Hira", {"zh-Hant", "ja-Jpan", "ja-Jpan"}, 1},
 
-        // Language match case
-        { "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
-        { "zh-Latn", { "zh-Latn", "ja-Latn" }, 0 },
-        { "ja-Latn", { "zh-Latn", "ja-Latn" }, 1 },
-        { "ja-Latn", { "zh-Latn", "ja-Latn", "ja-Latn" }, 1 },
+            // Language match case
+            {"ja-Latn", {"zh-Latn", "ja-Latn"}, 1},
+            {"zh-Latn", {"zh-Latn", "ja-Latn"}, 0},
+            {"ja-Latn", {"zh-Latn", "ja-Latn"}, 1},
+            {"ja-Latn", {"zh-Latn", "ja-Latn", "ja-Latn"}, 1},
 
-        // Mixed case
-        // Script/subscript match is strongest.
-        { "ja-Jpan", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
-        { "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan" }, 2 },
-        { "ja-Hira", { "en-Latn", "ja-Latn", "en-Jpan", "en-Jpan" }, 2 },
+            // Mixed case
+            // Script/subscript match is strongest.
+            {"ja-Jpan", {"en-Latn", "ja-Latn", "en-Jpan"}, 2},
+            {"ja-Hira", {"en-Latn", "ja-Latn", "en-Jpan"}, 2},
+            {"ja-Hira", {"en-Latn", "ja-Latn", "en-Jpan", "en-Jpan"}, 2},
 
-        // Language match only happens if the script matches.
-        { "ja-Hira", { "en-Latn", "ja-Latn" }, 0 },
-        { "ja-Hira", { "en-Jpan", "ja-Jpan" }, 1 },
+            // Language match only happens if the script matches.
+            {"ja-Hira", {"en-Latn", "ja-Latn"}, 0},
+            {"ja-Hira", {"en-Jpan", "ja-Jpan"}, 1},
 
-        // Multiple languages.
-        // Even if all fonts have the same score, use the 2nd language for better selection.
-        { "en-Latn,ja-Jpan", { "zh-Hant", "zh-Hans", "ja-Jpan" }, 2 },
-        { "en-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
-        { "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn" }, 2 },
-        { "en-Latn,br-Latn,nl-Latn", { "es-Latn", "be-Latn", "nl-Latn", "nl-Latn" }, 2 },
+            // Multiple locales.
+            // Even if all fonts have the same score, use the 2nd locale for better selection.
+            {"en-Latn,ja-Jpan", {"zh-Hant", "zh-Hans", "ja-Jpan"}, 2},
+            {"en-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn"}, 2},
+            {"en-Latn,br-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn"}, 2},
+            {"en-Latn,br-Latn,nl-Latn", {"es-Latn", "be-Latn", "nl-Latn", "nl-Latn"}, 2},
 
-        // Script score.
-        { "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan" }, 1 },
-        { "en-Latn,ja-Jpan", { "en-Arab", "en-Jpan", "en-Jpan" }, 1 },
+            // Script score.
+            {"en-Latn,ja-Jpan", {"en-Arab", "en-Jpan"}, 1},
+            {"en-Latn,ja-Jpan", {"en-Arab", "en-Jpan", "en-Jpan"}, 1},
 
-        // Language match case
-        { "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn" }, 1 },
-        { "en-Latn,ja-Latn", { "bd-Latn", "ja-Latn", "ja-Latn" }, 1 },
+            // Language match case
+            {"en-Latn,ja-Latn", {"bd-Latn", "ja-Latn"}, 1},
+            {"en-Latn,ja-Latn", {"bd-Latn", "ja-Latn", "ja-Latn"}, 1},
 
-        // Language match only happens if the script matches.
-        { "en-Latn,ar-Arab", { "en-Beng", "ar-Arab" }, 1 },
+            // Language match only happens if the script matches.
+            {"en-Latn,ar-Arab", {"en-Beng", "ar-Arab"}, 1},
 
-        // Multiple languages in the font settings.
-        { "ko-Jamo", { "ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2 },
-        { "en-Latn", { "ja-Jpan", "en-Latn,ja-Jpan"}, 1 },
-        { "en-Latn", { "ja-Jpan", "ja-Jpan,en-Latn"}, 1 },
-        { "en-Latn", { "ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1 },
-        { "en-Latn", { "zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1 },
+            // Multiple locales in the font settings.
+            {"ko-Jamo", {"ja-Jpan", "ko-Kore", "ko-Kore,ko-Jamo"}, 2},
+            {"en-Latn", {"ja-Jpan", "en-Latn,ja-Jpan"}, 1},
+            {"en-Latn", {"ja-Jpan", "ja-Jpan,en-Latn"}, 1},
+            {"en-Latn", {"ja-Jpan,zh-Hant", "en-Latn,ja-Jpan", "en-Latn"}, 1},
+            {"en-Latn", {"zh-Hant,ja-Jpan", "ja-Jpan,en-Latn", "en-Latn"}, 1},
 
-        // Kore = Hang + Hani, etc.
-        { "ko-Kore", { "ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2 },
-        { "ja-Hrkt", { "ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2 },
-        { "ja-Jpan", { "ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3 },
-        { "zh-Hanb", { "zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2 },
-        { "zh-Hanb", { "ja-Hanb", "zh-Hant,zh-Bopo"}, 1 },
+            // Kore = Hang + Hani, etc.
+            {"ko-Kore", {"ko-Hang", "ko-Jamo,ko-Hani", "ko-Hang,ko-Hani"}, 2},
+            {"ja-Hrkt", {"ja-Hira", "ja-Kana", "ja-Hira,ja-Kana"}, 2},
+            {"ja-Jpan", {"ja-Hira", "ja-Kana", "ja-Hani", "ja-Hira,ja-Kana,ja-Hani"}, 3},
+            {"zh-Hanb", {"zh-Hant", "zh-Bopo", "zh-Hant,zh-Bopo"}, 2},
+            {"zh-Hanb", {"ja-Hanb", "zh-Hant,zh-Bopo"}, 1},
 
-        // Language match with unified subscript bits.
-        { "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3 },
-        { "zh-Hanb", { "zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3 },
+            // Language match with unified subscript bits.
+            {"zh-Hanb", {"zh-Hant", "zh-Bopo", "ja-Hant,ja-Bopo", "zh-Hant,zh-Bopo"}, 3},
+            {"zh-Hanb", {"zh-Hant", "zh-Bopo", "ja-Hant,zh-Bopo", "zh-Hant,zh-Bopo"}, 3},
 
-        // Two elements subtag matching: language and subtag or language or script.
-        { "ja-Kana-u-em-emoji", { "zh-Hant", "ja-Kana"}, 1 },
-        { "ja-Kana-u-em-emoji", { "zh-Hant", "ja-Kana", "ja-Zsye"}, 2 },
-        { "ja-Zsym-u-em-emoji", { "ja-Kana", "ja-Zsym", "ja-Zsye"}, 2 },
+            // Two elements subtag matching: language and subtag or language or script.
+            {"ja-Kana-u-em-emoji", {"zh-Hant", "ja-Kana"}, 1},
+            {"ja-Kana-u-em-emoji", {"zh-Hant", "ja-Kana", "ja-Zsye"}, 2},
+            {"ja-Zsym-u-em-emoji", {"ja-Kana", "ja-Zsym", "ja-Zsye"}, 2},
 
-        // One element subtag matching: subtag only or script only.
-        { "en-Latn-u-em-emoji", { "ja-Latn", "ja-Zsye"}, 1 },
-        { "en-Zsym-u-em-emoji", { "ja-Zsym", "ja-Zsye"}, 1 },
-        { "en-Zsye-u-em-text", { "ja-Zsym", "ja-Zsye"}, 0 },
+            // One element subtag matching: subtag only or script only.
+            {"en-Latn-u-em-emoji", {"ja-Latn", "ja-Zsye"}, 1},
+            {"en-Zsym-u-em-emoji", {"ja-Zsym", "ja-Zsye"}, 1},
+            {"en-Zsye-u-em-text", {"ja-Zsym", "ja-Zsye"}, 0},
 
-        // Multiple languages list with subtags.
-        { "en-Latn,ja-Jpan-u-em-text", { "en-Latn", "en-Zsye", "en-Zsym"}, 0 },
-        { "en-Latn,en-Zsye,ja-Jpan-u-em-text", { "zh", "en-Zsye", "en-Zsym"}, 1 },
+            // Multiple locale list with subtags.
+            {"en-Latn,ja-Jpan-u-em-text", {"en-Latn", "en-Zsye", "en-Zsym"}, 0},
+            {"en-Latn,en-Zsye,ja-Jpan-u-em-text", {"zh", "en-Zsye", "en-Zsym"}, 1},
     };
 
     for (auto testCase : testCases) {
-        std::string fontLanguagesStr = "{";
-        for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
+        std::string fontLocaleStr = "{";
+        for (size_t i = 0; i < testCase.fontLocales.size(); ++i) {
             if (i != 0) {
-                fontLanguagesStr += ", ";
+                fontLocaleStr += ", ";
             }
-            fontLanguagesStr += "\"" + testCase.fontLanguages[i] + "\"";
+            fontLocaleStr += "\"" + testCase.fontLocales[i] + "\"";
         }
-        fontLanguagesStr += "}";
-        SCOPED_TRACE("Test of user preferred languages: \"" + testCase.userPreferredLanguages +
-                     "\" with font languages: " + fontLanguagesStr);
+        fontLocaleStr += "}";
+        SCOPED_TRACE("Test of user preferred locale: \"" + testCase.userPreferredLocale +
+                     "\" with font locale: " + fontLocaleStr);
 
         std::vector<std::shared_ptr<FontFamily>> families;
 
         // Prepare first font which doesn't supports U+9AA8
-        std::shared_ptr<MinikinFont> firstFamilyMinikinFont(
-                new MinikinFontForTest(kNoGlyphFont));
-        std::shared_ptr<FontFamily> firstFamily(new FontFamily(
-                FontStyle::registerLanguageList("und"), 0 /* variant */,
-                std::vector<Font>({ Font(firstFamilyMinikinFont, FontStyle()) })));
+        auto firstFamilyMinikinFont =
+                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));
         families.push_back(firstFamily);
 
         // Prepare font families
-        // Each font family is associated with a specified language. All font families except for
+        // Each font family is associated with a specified locale. All font families except for
         // the first font support U+9AA8.
-        std::unordered_map<MinikinFont*, int> fontLangIdxMap;
+        std::unordered_map<MinikinFont*, int> fontLocaleIdxMap;
 
-        for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {
-            std::shared_ptr<MinikinFont> minikin_font(new MinikinFontForTest(kJAFont));
-            std::shared_ptr<FontFamily> family(new FontFamily(
-                    FontStyle::registerLanguageList(testCase.fontLanguages[i]), 0 /* variant */,
-                    std::vector<Font>({ Font(minikin_font, FontStyle()) })));
+        for (size_t i = 0; i < testCase.fontLocales.size(); ++i) {
+            auto minikinFont =
+                    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));
             families.push_back(family);
-            fontLangIdxMap.insert(std::make_pair(minikin_font.get(), i));
+            fontLocaleIdxMap.insert(std::make_pair(minikinFont.get(), i));
         }
         std::shared_ptr<FontCollection> collection(new FontCollection(families));
         // Do itemize
-        const FontStyle style = FontStyle(
-                FontStyle::registerLanguageList(testCase.userPreferredLanguages));
         std::vector<FontCollection::Run> runs;
-        itemize(collection, "U+9AA8", style, &runs);
+        itemize(collection, "U+9AA8", testCase.userPreferredLocale, &runs);
         ASSERT_EQ(1U, runs.size());
         ASSERT_NE(nullptr, runs[0].fakedFont.font);
 
         // First family doesn't support U+9AA8 and others support it, so the first font should not
         // be selected.
-        EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font);
+        EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font->typeface().get());
 
         // Lookup used font family by MinikinFont*.
-        const int usedLangIndex = fontLangIdxMap[runs[0].fakedFont.font];
-        EXPECT_EQ(testCase.selectedFontIndex, usedLangIndex);
+        const int usedLocaleIndex = fontLocaleIdxMap[runs[0].fakedFont.font->typeface().get()];
+        EXPECT_EQ(testCase.selectedFontIndex, usedLocaleIndex);
     }
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_LanguageAndCoverage) {
+TEST(FontCollectionItemizeTest, itemize_LocaleAndCoverage) {
     struct TestCase {
         std::string testString;
-        std::string requestedLanguages;
+        std::string requestedLocales;
         std::string expectedFont;
     } testCases[] = {
-        // Following test cases verify that following rules in font fallback chain.
-        // - If the first font in the collection supports the given character or variation sequence,
-        //   it should be selected.
-        // - If the font doesn't support the given character, variation sequence or its base
-        //   character, it should not be selected.
-        // - If two or more fonts match the requested languages, the font matches with the highest
-        //   priority language should be selected.
-        // - If two or more fonts get the same score, the font listed earlier in the XML file
-        //   (here, kItemizeFontXml) should be selected.
+            // Following test cases verify that following rules in font fallback chain.
+            // - If the first font in the collection supports the given character or variation
+            // sequence,
+            //   it should be selected.
+            // - If the font doesn't support the given character, variation sequence or its base
+            //   character, it should not be selected.
+            // - If two or more fonts match the requested locales, the font matches with the highest
+            //   priority locale should be selected.
+            // - If two or more fonts get the same score, the font listed earlier in the XML file
+            //   (here, kItemizeFontXml) should be selected.
 
-        // Regardless of language, the first font is always selected if it covers the code point.
-        { "'a'", "", kLatinFont},
-        { "'a'", "en-Latn", kLatinFont},
-        { "'a'", "ja-Jpan", kLatinFont},
-        { "'a'", "ja-Jpan,en-Latn", kLatinFont},
-        { "'a'", "zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn", kLatinFont},
+            // Regardless of locale, the first font is always selected if it covers the code point.
+            {"'a'", "", kLatinFont},
+            {"'a'", "en-Latn", kLatinFont},
+            {"'a'", "ja-Jpan", kLatinFont},
+            {"'a'", "ja-Jpan,en-Latn", kLatinFont},
+            {"'a'", "zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn", kLatinFont},
 
-        // U+81ED is supported by both the ja font and zh-Hans font.
-        { "U+81ED", "", kZH_HansFont },  // zh-Hans font is listed before ja font.
-        { "U+81ED", "en-Latn", kZH_HansFont },  // zh-Hans font is listed before ja font.
-        { "U+81ED", "ja-Jpan", kJAFont },
-        { "U+81ED", "zh-Hans", kZH_HansFont },
+            // U+81ED is supported by both the ja font and zh-Hans font.
+            {"U+81ED", "", kZH_HansFont},         // zh-Hans font is listed before ja font.
+            {"U+81ED", "en-Latn", kZH_HansFont},  // zh-Hans font is listed before ja font.
+            {"U+81ED", "ja-Jpan", kJAFont},
+            {"U+81ED", "zh-Hans", kZH_HansFont},
 
-        { "U+81ED", "ja-Jpan,en-Latn", kJAFont },
-        { "U+81ED", "en-Latn,ja-Jpan", kJAFont },
-        { "U+81ED", "en-Latn,zh-Hans", kZH_HansFont },
-        { "U+81ED", "zh-Hans,en-Latn", kZH_HansFont },
-        { "U+81ED", "ja-Jpan,zh-Hans", kJAFont },
-        { "U+81ED", "zh-Hans,ja-Jpan", kZH_HansFont },
+            {"U+81ED", "ja-Jpan,en-Latn", kJAFont},
+            {"U+81ED", "en-Latn,ja-Jpan", kJAFont},
+            {"U+81ED", "en-Latn,zh-Hans", kZH_HansFont},
+            {"U+81ED", "zh-Hans,en-Latn", kZH_HansFont},
+            {"U+81ED", "ja-Jpan,zh-Hans", kJAFont},
+            {"U+81ED", "zh-Hans,ja-Jpan", kZH_HansFont},
 
-        { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+81ED", "en-Latn,ja-Jpan,zh-Hans", kJAFont },
-        { "U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+81ED", "ja-Jpan,en-Latn,zh-Hans", kJAFont },
-        { "U+81ED", "ja-Jpan,zh-Hans,en-Latn", kJAFont },
-        { "U+81ED", "zh-Hans,en-Latn,ja-Jpan", kZH_HansFont },
-        { "U+81ED", "zh-Hans,ja-Jpan,en-Latn", kZH_HansFont },
+            {"U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+81ED", "en-Latn,ja-Jpan,zh-Hans", kJAFont},
+            {"U+81ED", "en-Latn,zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+81ED", "ja-Jpan,en-Latn,zh-Hans", kJAFont},
+            {"U+81ED", "ja-Jpan,zh-Hans,en-Latn", kJAFont},
+            {"U+81ED", "zh-Hans,en-Latn,ja-Jpan", kZH_HansFont},
+            {"U+81ED", "zh-Hans,ja-Jpan,en-Latn", kZH_HansFont},
 
-        // U+304A is only supported by ja font.
-        { "U+304A", "", kJAFont },
-        { "U+304A", "ja-Jpan", kJAFont },
-        { "U+304A", "zh-Hant", kJAFont },
-        { "U+304A", "zh-Hans", kJAFont },
+            // U+304A is only supported by ja font.
+            {"U+304A", "", kJAFont},
+            {"U+304A", "ja-Jpan", kJAFont},
+            {"U+304A", "zh-Hant", kJAFont},
+            {"U+304A", "zh-Hans", kJAFont},
 
-        { "U+304A", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+304A", "zh-Hant,ja-Jpan", kJAFont },
-        { "U+304A", "zh-Hans,zh-Hant", kJAFont },
-        { "U+304A", "zh-Hant,zh-Hans", kJAFont },
-        { "U+304A", "zh-Hans,ja-Jpan", kJAFont },
-        { "U+304A", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+304A", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+304A", "zh-Hant,ja-Jpan", kJAFont},
+            {"U+304A", "zh-Hans,zh-Hant", kJAFont},
+            {"U+304A", "zh-Hant,zh-Hans", kJAFont},
+            {"U+304A", "zh-Hans,ja-Jpan", kJAFont},
+            {"U+304A", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+304A", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
-        { "U+304A", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
-        { "U+304A", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+304A", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+304A", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
-        { "U+304A", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+            {"U+304A", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+            {"U+304A", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+            {"U+304A", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+304A", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+304A", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+            {"U+304A", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
 
-        // U+242EE is supported by both ja font and zh-Hant fonts but not by zh-Hans font.
-        { "U+242EE", "", kJAFont },  // ja font is listed before zh-Hant font.
-        { "U+242EE", "ja-Jpan", kJAFont },
-        { "U+242EE", "zh-Hans", kJAFont },
-        { "U+242EE", "zh-Hant", kZH_HantFont },
+            // U+242EE is supported by both ja font and zh-Hant fonts but not by zh-Hans font.
+            {"U+242EE", "", kJAFont},  // ja font is listed before zh-Hant font.
+            {"U+242EE", "ja-Jpan", kJAFont},
+            {"U+242EE", "zh-Hans", kJAFont},
+            {"U+242EE", "zh-Hant", kZH_HantFont},
 
-        { "U+242EE", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+242EE", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+242EE", "zh-Hans,zh-Hant", kZH_HantFont },
-        { "U+242EE", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+242EE", "zh-Hans,ja-Jpan", kJAFont },
-        { "U+242EE", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+242EE", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+242EE", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+242EE", "zh-Hans,zh-Hant", kZH_HantFont},
+            {"U+242EE", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+242EE", "zh-Hans,ja-Jpan", kJAFont},
+            {"U+242EE", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+242EE", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
-        { "U+242EE", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+242EE", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+242EE", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+242EE", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+242EE", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+242EE", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+            {"U+242EE", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+242EE", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+242EE", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+242EE", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+242EE", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts.
-        { "U+9AA8", "", kZH_HansFont },  // zh-Hans font is listed before ja and zh-Hant fonts.
-        { "U+9AA8", "ja-Jpan", kJAFont },
-        { "U+9AA8", "zh-Hans", kZH_HansFont },
-        { "U+9AA8", "zh-Hant", kZH_HantFont },
+            // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts.
+            {"U+9AA8", "", kZH_HansFont},  // zh-Hans font is listed before ja and zh-Hant fonts.
+            {"U+9AA8", "ja-Jpan", kJAFont},
+            {"U+9AA8", "zh-Hans", kZH_HansFont},
+            {"U+9AA8", "zh-Hant", kZH_HantFont},
 
-        { "U+9AA8", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+9AA8", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+9AA8", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+9AA8", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+9AA8", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+9AA8", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+9AA8", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+9AA8", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+9AA8", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+9AA8", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+9AA8", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+9AA8", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+9AA8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+9AA8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+9AA8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+9AA8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+9AA8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+9AA8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+9AA8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+9AA8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+9AA8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+9AA8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+9AA8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+9AA8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant fonts.
-        { "U+242EE U+FE00", "", kJAFont },
-        { "U+242EE U+FE00", "ja-Jpan", kJAFont },
-        { "U+242EE U+FE00", "zh-Hant", kJAFont },
-        { "U+242EE U+FE00", "zh-Hans", kJAFont },
+            // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant fonts.
+            {"U+242EE U+FE00", "", kJAFont},
+            {"U+242EE U+FE00", "ja-Jpan", kJAFont},
+            {"U+242EE U+FE00", "zh-Hant", kJAFont},
+            {"U+242EE U+FE00", "zh-Hans", kJAFont},
 
-        { "U+242EE U+FE00", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+242EE U+FE00", "zh-Hant,ja-Jpan", kJAFont },
-        { "U+242EE U+FE00", "zh-Hans,zh-Hant", kJAFont },
-        { "U+242EE U+FE00", "zh-Hant,zh-Hans", kJAFont },
-        { "U+242EE U+FE00", "zh-Hans,ja-Jpan", kJAFont },
-        { "U+242EE U+FE00", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+242EE U+FE00", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+242EE U+FE00", "zh-Hant,ja-Jpan", kJAFont},
+            {"U+242EE U+FE00", "zh-Hans,zh-Hant", kJAFont},
+            {"U+242EE U+FE00", "zh-Hant,zh-Hans", kJAFont},
+            {"U+242EE U+FE00", "zh-Hans,ja-Jpan", kJAFont},
+            {"U+242EE U+FE00", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+242EE U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
-        { "U+242EE U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
-        { "U+242EE U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+242EE U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+242EE U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
-        { "U+242EE U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+            {"U+242EE U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+            {"U+242EE U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+            {"U+242EE U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+242EE U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+242EE U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+            {"U+242EE U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
 
-        // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja font.
-        { "U+3402 U+E0100", "", kZH_HansFont },  // zh-Hans font is listed before zh-Hant font.
-        { "U+3402 U+E0100", "ja-Jpan", kZH_HansFont },  // zh-Hans font is listed before zh-Hant font.
-        { "U+3402 U+E0100", "zh-Hant", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hans", kZH_HansFont },
+            // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja font.
+            {"U+3402 U+E0100", "", kZH_HansFont},  // zh-Hans font is listed before zh-Hant font.
+            {"U+3402 U+E0100", "ja-Jpan",
+             kZH_HansFont},  // zh-Hans font is listed before zh-Hant font.
+            {"U+3402 U+E0100", "zh-Hant", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hans", kZH_HansFont},
 
-        { "U+3402 U+E0100", "ja-Jpan,zh-Hant", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+3402 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+3402 U+E0100", "ja-Jpan,zh-Hans", kZH_HansFont },
+            {"U+3402 U+E0100", "ja-Jpan,zh-Hant", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+3402 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+3402 U+E0100", "ja-Jpan,zh-Hans", kZH_HansFont},
 
-        { "U+3402 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+3402 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+3402 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+3402 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+3402 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+3402 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+3402 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+3402 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+3402 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+3402 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // No font supports U+4444 U+FE00 but only zh-Hans supports its base character U+4444.
-        { "U+4444 U+FE00", "", kZH_HansFont },
-        { "U+4444 U+FE00", "ja-Jpan", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hant", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hans", kZH_HansFont },
+            // No font supports U+4444 U+FE00 but only zh-Hans supports its base character U+4444.
+            {"U+4444 U+FE00", "", kZH_HansFont},
+            {"U+4444 U+FE00", "ja-Jpan", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hant", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hans", kZH_HansFont},
 
-        { "U+4444 U+FE00", "ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hant,zh-Hans", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+4444 U+FE00", "ja-Jpan,zh-Hans", kZH_HansFont },
+            {"U+4444 U+FE00", "ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hant,zh-Hans", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+4444 U+FE00", "ja-Jpan,zh-Hans", kZH_HansFont},
 
-        { "U+4444 U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+4444 U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+4444 U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+4444 U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kZH_HansFont },
+            {"U+4444 U+FE00", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+4444 U+FE00", "ja-Jpan,zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+4444 U+FE00", "ja-Jpan,zh-Hant,zh-Hans", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+4444 U+FE00", "zh-Hant,ja-Jpan,zh-Hans", kZH_HansFont},
 
-        // No font supports U+81ED U+E0100 but ja and zh-Hans support its base character U+81ED.
-        // zh-Hans font is listed before ja font.
-        { "U+81ED U+E0100", "", kZH_HansFont },
-        { "U+81ED U+E0100", "ja-Jpan", kJAFont },
-        { "U+81ED U+E0100", "zh-Hant", kZH_HansFont },
-        { "U+81ED U+E0100", "zh-Hans", kZH_HansFont },
+            // No font supports U+81ED U+E0100 but ja and zh-Hans support its base character U+81ED.
+            // zh-Hans font is listed before ja font.
+            {"U+81ED U+E0100", "", kZH_HansFont},
+            {"U+81ED U+E0100", "ja-Jpan", kJAFont},
+            {"U+81ED U+E0100", "zh-Hant", kZH_HansFont},
+            {"U+81ED U+E0100", "zh-Hans", kZH_HansFont},
 
-        { "U+81ED U+E0100", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+81ED U+E0100", "zh-Hant,ja-Jpan", kJAFont },
-        { "U+81ED U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+81ED U+E0100", "zh-Hant,zh-Hans", kZH_HansFont },
-        { "U+81ED U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+81ED U+E0100", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+81ED U+E0100", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+81ED U+E0100", "zh-Hant,ja-Jpan", kJAFont},
+            {"U+81ED U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+81ED U+E0100", "zh-Hant,zh-Hans", kZH_HansFont},
+            {"U+81ED U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+81ED U+E0100", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+81ED U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+81ED U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+81ED U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+81ED U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+81ED U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+81ED U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+            {"U+81ED U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+81ED U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+81ED U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+81ED U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+81ED U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+81ED U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
 
-        // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts support its base
-        // character U+9AA8.
-        // zh-Hans font is listed before ja and zh-Hant fonts.
-        { "U+9AA8 U+E0100", "", kZH_HansFont },
-        { "U+9AA8 U+E0100", "ja-Jpan", kJAFont },
-        { "U+9AA8 U+E0100", "zh-Hans", kZH_HansFont },
-        { "U+9AA8 U+E0100", "zh-Hant", kZH_HantFont },
+            // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts support its base
+            // character U+9AA8.
+            // zh-Hans font is listed before ja and zh-Hant fonts.
+            {"U+9AA8 U+E0100", "", kZH_HansFont},
+            {"U+9AA8 U+E0100", "ja-Jpan", kJAFont},
+            {"U+9AA8 U+E0100", "zh-Hans", kZH_HansFont},
+            {"U+9AA8 U+E0100", "zh-Hant", kZH_HantFont},
 
-        { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+9AA8 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+9AA8 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+9AA8 U+E0100", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+9AA8 U+E0100", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+9AA8 U+E0100", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+9AA8 U+E0100", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+9AA8 U+E0100", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+9AA8 U+E0100", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+9AA8 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+9AA8 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+9AA8 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+9AA8 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+9AA8 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+9AA8 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+9AA8 U+E0100", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+9AA8 U+E0100", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+9AA8 U+E0100", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+9AA8 U+E0100", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+9AA8 U+E0100", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+9AA8 U+E0100", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base character U+35A8.
-        // zh-Hans font is listed before ja and zh-Hant fonts.
-        { "U+35A8", "", kZH_HansFont },
-        { "U+35A8", "ja-Jpan", kJAFont },
-        { "U+35A8", "zh-Hans", kZH_HansFont },
-        { "U+35A8", "zh-Hant", kZH_HantFont },
+            // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base character U+35A8.
+            // zh-Hans font is listed before ja and zh-Hant fonts.
+            {"U+35A8", "", kZH_HansFont},
+            {"U+35A8", "ja-Jpan", kJAFont},
+            {"U+35A8", "zh-Hans", kZH_HansFont},
+            {"U+35A8", "zh-Hant", kZH_HantFont},
 
-        { "U+35A8", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+35A8", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+35A8", "zh-Hans,zh-Hant", kZH_HansFont },
-        { "U+35A8", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+35A8", "zh-Hans,ja-Jpan", kZH_HansFont },
-        { "U+35A8", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+35A8", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+35A8", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+35A8", "zh-Hans,zh-Hant", kZH_HansFont},
+            {"U+35A8", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+35A8", "zh-Hans,ja-Jpan", kZH_HansFont},
+            {"U+35A8", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+35A8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont },
-        { "U+35A8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont },
-        { "U+35A8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+35A8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+35A8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+35A8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+35A8", "zh-Hans,ja-Jpan,zh-Hant", kZH_HansFont},
+            {"U+35A8", "zh-Hans,zh-Hant,ja-Jpan", kZH_HansFont},
+            {"U+35A8", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+35A8", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+35A8", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+35A8", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja fonts support its
-        // base character U+35B6.
-        // ja font is listed before zh-Hant font.
-        { "U+35B6", "", kJAFont },
-        { "U+35B6", "ja-Jpan", kJAFont },
-        { "U+35B6", "zh-Hant", kZH_HantFont },
-        { "U+35B6", "zh-Hans", kJAFont },
+            // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja fonts support
+            // its
+            // base character U+35B6.
+            // ja font is listed before zh-Hant font.
+            {"U+35B6", "", kJAFont},
+            {"U+35B6", "ja-Jpan", kJAFont},
+            {"U+35B6", "zh-Hant", kZH_HantFont},
+            {"U+35B6", "zh-Hans", kJAFont},
 
-        { "U+35B6", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+35B6", "zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+35B6", "zh-Hans,zh-Hant", kZH_HantFont },
-        { "U+35B6", "zh-Hant,zh-Hans", kZH_HantFont },
-        { "U+35B6", "zh-Hans,ja-Jpan", kJAFont },
-        { "U+35B6", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+35B6", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+35B6", "zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+35B6", "zh-Hans,zh-Hant", kZH_HantFont},
+            {"U+35B6", "zh-Hant,zh-Hans", kZH_HantFont},
+            {"U+35B6", "zh-Hans,ja-Jpan", kJAFont},
+            {"U+35B6", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+35B6", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
-        { "U+35B6", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont },
-        { "U+35B6", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+35B6", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+35B6", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont },
-        { "U+35B6", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont },
+            {"U+35B6", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+            {"U+35B6", "zh-Hans,zh-Hant,ja-Jpan", kZH_HantFont},
+            {"U+35B6", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+35B6", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+35B6", "zh-Hant,zh-Hans,ja-Jpan", kZH_HantFont},
+            {"U+35B6", "zh-Hant,ja-Jpan,zh-Hans", kZH_HantFont},
 
-        // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font supports its base
-        // character U+35C5.
-        { "U+35C5", "", kJAFont },
-        { "U+35C5", "ja-Jpan", kJAFont },
-        { "U+35C5", "zh-Hant", kJAFont },
-        { "U+35C5", "zh-Hans", kJAFont },
+            // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font supports its
+            // base
+            // character U+35C5.
+            {"U+35C5", "", kJAFont},
+            {"U+35C5", "ja-Jpan", kJAFont},
+            {"U+35C5", "zh-Hant", kJAFont},
+            {"U+35C5", "zh-Hans", kJAFont},
 
-        { "U+35C5", "ja-Jpan,zh-Hant", kJAFont },
-        { "U+35C5", "zh-Hant,ja-Jpan", kJAFont },
-        { "U+35C5", "zh-Hans,zh-Hant", kJAFont },
-        { "U+35C5", "zh-Hant,zh-Hans", kJAFont },
-        { "U+35C5", "zh-Hans,ja-Jpan", kJAFont },
-        { "U+35C5", "ja-Jpan,zh-Hans", kJAFont },
+            {"U+35C5", "ja-Jpan,zh-Hant", kJAFont},
+            {"U+35C5", "zh-Hant,ja-Jpan", kJAFont},
+            {"U+35C5", "zh-Hans,zh-Hant", kJAFont},
+            {"U+35C5", "zh-Hant,zh-Hans", kJAFont},
+            {"U+35C5", "zh-Hans,ja-Jpan", kJAFont},
+            {"U+35C5", "ja-Jpan,zh-Hans", kJAFont},
 
-        { "U+35C5", "zh-Hans,ja-Jpan,zh-Hant", kJAFont },
-        { "U+35C5", "zh-Hans,zh-Hant,ja-Jpan", kJAFont },
-        { "U+35C5", "ja-Jpan,zh-Hans,zh-Hant", kJAFont },
-        { "U+35C5", "ja-Jpan,zh-Hant,zh-Hans", kJAFont },
-        { "U+35C5", "zh-Hant,zh-Hans,ja-Jpan", kJAFont },
-        { "U+35C5", "zh-Hant,ja-Jpan,zh-Hans", kJAFont },
+            {"U+35C5", "zh-Hans,ja-Jpan,zh-Hant", kJAFont},
+            {"U+35C5", "zh-Hans,zh-Hant,ja-Jpan", kJAFont},
+            {"U+35C5", "ja-Jpan,zh-Hans,zh-Hant", kJAFont},
+            {"U+35C5", "ja-Jpan,zh-Hant,zh-Hans", kJAFont},
+            {"U+35C5", "zh-Hant,zh-Hans,ja-Jpan", kJAFont},
+            {"U+35C5", "zh-Hant,ja-Jpan,zh-Hans", kJAFont},
 
-        // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font supports it.
-        { "U+1F469", "", kEmojiFont },
-        { "U+1F469", "ja-Jpan", kEmojiFont },
-        { "U+1F469", "zh-Hant", kEmojiFont },
-        { "U+1F469", "zh-Hans", kEmojiFont },
+            // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font supports it.
+            {"U+1F469", "", kEmojiFont},
+            {"U+1F469", "ja-Jpan", kEmojiFont},
+            {"U+1F469", "zh-Hant", kEmojiFont},
+            {"U+1F469", "zh-Hans", kEmojiFont},
 
-        { "U+1F469", "ja-Jpan,zh-Hant", kEmojiFont },
-        { "U+1F469", "zh-Hant,ja-Jpan", kEmojiFont },
-        { "U+1F469", "zh-Hans,zh-Hant", kEmojiFont },
-        { "U+1F469", "zh-Hant,zh-Hans", kEmojiFont },
-        { "U+1F469", "zh-Hans,ja-Jpan", kEmojiFont },
-        { "U+1F469", "ja-Jpan,zh-Hans", kEmojiFont },
+            {"U+1F469", "ja-Jpan,zh-Hant", kEmojiFont},
+            {"U+1F469", "zh-Hant,ja-Jpan", kEmojiFont},
+            {"U+1F469", "zh-Hans,zh-Hant", kEmojiFont},
+            {"U+1F469", "zh-Hant,zh-Hans", kEmojiFont},
+            {"U+1F469", "zh-Hans,ja-Jpan", kEmojiFont},
+            {"U+1F469", "ja-Jpan,zh-Hans", kEmojiFont},
 
-        { "U+1F469", "zh-Hans,ja-Jpan,zh-Hant", kEmojiFont },
-        { "U+1F469", "zh-Hans,zh-Hant,ja-Jpan", kEmojiFont },
-        { "U+1F469", "ja-Jpan,zh-Hans,zh-Hant", kEmojiFont },
-        { "U+1F469", "ja-Jpan,zh-Hant,zh-Hans", kEmojiFont },
-        { "U+1F469", "zh-Hant,zh-Hans,ja-Jpan", kEmojiFont },
-        { "U+1F469", "zh-Hant,ja-Jpan,zh-Hans", kEmojiFont },
+            {"U+1F469", "zh-Hans,ja-Jpan,zh-Hant", kEmojiFont},
+            {"U+1F469", "zh-Hans,zh-Hant,ja-Jpan", kEmojiFont},
+            {"U+1F469", "ja-Jpan,zh-Hans,zh-Hant", kEmojiFont},
+            {"U+1F469", "ja-Jpan,zh-Hant,zh-Hans", kEmojiFont},
+            {"U+1F469", "zh-Hant,zh-Hans,ja-Jpan", kEmojiFont},
+            {"U+1F469", "zh-Hant,ja-Jpan,zh-Hans", kEmojiFont},
     };
 
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kItemizeFontXml));
+    auto collection = buildFontCollectionFromXml(kItemizeFontXml);
 
     for (auto testCase : testCases) {
-        SCOPED_TRACE("Test for \"" + testCase.testString + "\" with languages " +
-                     testCase.requestedLanguages);
+        SCOPED_TRACE("Test for \"" + testCase.testString + "\" with locales " +
+                     testCase.requestedLocales);
 
         std::vector<FontCollection::Run> runs;
-        const FontStyle style =
-                FontStyle(FontStyle::registerLanguageList(testCase.requestedLanguages));
-        itemize(collection, testCase.testString.c_str(), style, &runs);
+        itemize(collection, testCase.testString.c_str(), testCase.requestedLocales, &runs);
         ASSERT_EQ(1U, runs.size());
-        EXPECT_EQ(testCase.expectedFont, getFontPath(runs[0]));
+        EXPECT_EQ(testCase.expectedFont, getFontName(runs[0]));
     }
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+00A9 U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+00AE U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     // Text emoji is specified but it is not available. Use color emoji instead.
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+203C U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+2049 U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
     // U+231A is a emoji default emoji which is available only in TextEmojifFont.
     // TextEmojiFont.ttf sohuld be selected.
-    itemize(collection, "U+231A U+FE0E", kDefaultFontStyle, &runs);
+    itemize(collection, "U+231A U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+231B U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     // Text emoji is specified but it is not available. Use color emoji instead.
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+23E9 U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+23EA U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+26FA U+FE0E", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kMixedEmojiFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+00A9 U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     // Color emoji is specified but it is not available. Use text representaion instead.
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+00AE U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+203C U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+2049 U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+231A U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
     // Color emoji is specified but it is not available. Use text representation instead.
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+231B U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+23E9 U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+23EA U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+26F9 U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kMixedEmojiFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
     // TextEmoji font is selected since it is listed before ColorEmoji font.
-    itemize(collection, "U+261D", kDefaultFontStyle, &runs);
+    itemize(collection, "U+261D", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(1, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
 
     // If skin tone is specified, it should be colored.
-    itemize(collection, "U+261D U+1F3FD", kDefaultFontStyle, &runs);
+    itemize(collection, "U+261D U+1F3FD", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(3, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+261D U+FE0F U+1F3FD", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
     // 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", kDefaultFontStyle, &runs);
+    itemize(collection, "U+261D U+FE0E U+1F3FD", &runs);
     ASSERT_EQ(2U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kTextEmojiFont, getFontName(runs[0]));
     EXPECT_EQ(2, runs[1].start);
     EXPECT_EQ(4, runs[1].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[1]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[1]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_PrivateUseArea) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_PrivateUseArea) {
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
     // Should not set nullptr to the result run. (Issue 26808815)
-    itemize(collection, "U+FEE10", kDefaultFontStyle, &runs);
+    itemize(collection, "U+FEE10", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(2, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 
-    itemize(collection, "U+FEE40 U+FE4C5", kDefaultFontStyle, &runs);
+    itemize(collection, "U+FEE40 U+FE4C5", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));
+    EXPECT_EQ(kNoGlyphFont, getFontName(runs[0]));
 }
 
-TEST_F(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+TEST(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
     std::vector<FontCollection::Run> runs;
 
-    const FontStyle kDefaultFontStyle;
-
-    itemize(collection, "U+1F469 U+200D U+1F373", kDefaultFontStyle, &runs);
+    itemize(collection, "U+1F469 U+200D U+1F373", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", kDefaultFontStyle, &runs);
+    itemize(collection, "U+1F469 U+200D U+2695 U+FE0F", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(5, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 
-    itemize(collection, "U+1F469 U+200D U+2695", kDefaultFontStyle, &runs);
+    itemize(collection, "U+1F469 U+200D U+2695", &runs);
     ASSERT_EQ(1U, runs.size());
     EXPECT_EQ(0, runs[0].start);
     EXPECT_EQ(4, runs[0].end);
-    EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));
+    EXPECT_EQ(kColorEmojiFont, getFontName(runs[0]));
 }
 
 // For b/29585939
-TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {
-    const FontStyle kDefaultFontStyle;
+TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {
+    std::shared_ptr<FontFamily> dummyFamily = buildFontFamily(kNoGlyphFont);
+    std::shared_ptr<FontFamily> familyA = buildFontFamily(kZH_HansFont);
+    std::shared_ptr<FontFamily> familyB = buildFontFamily(kZH_HansFont);
 
-    std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));
-    std::shared_ptr<MinikinFont> fontA(new MinikinFontForTest(kZH_HansFont));
-    std::shared_ptr<MinikinFont> fontB(new MinikinFontForTest(kZH_HansFont));
-
-    std::shared_ptr<FontFamily> dummyFamily(new FontFamily(
-            std::vector<Font>({ Font(dummyFont, FontStyle()) })));
-    std::shared_ptr<FontFamily> familyA(new FontFamily(
-            std::vector<Font>({ Font(fontA, FontStyle()) })));
-    std::shared_ptr<FontFamily> familyB(new FontFamily(
-            std::vector<Font>({ Font(fontB, FontStyle()) })));
-
-    std::vector<std::shared_ptr<FontFamily>> families =
-            { dummyFamily, familyA, familyB };
-    std::vector<std::shared_ptr<FontFamily>> reversedFamilies =
-            { dummyFamily, familyB, familyA };
+    std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, familyA, familyB};
+    std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, familyB, familyA};
 
     std::shared_ptr<FontCollection> collection(new FontCollection(families));
     std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies));
@@ -1543,34 +1526,23 @@
     // 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", kDefaultFontStyle, &runs);
-    EXPECT_EQ(fontA.get(), runs[0].fakedFont.font);
+    itemize(collection, "U+35A8 U+E0100", &runs);
+    EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font);
 
-    itemize(reversedCollection, "U+35A8 U+E0100", kDefaultFontStyle, &runs);
-    EXPECT_EQ(fontB.get(), runs[0].fakedFont.font);
+    itemize(reversedCollection, "U+35A8 U+E0100", &runs);
+    EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font);
 }
 
 // For b/29585939
-TEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {
-    const FontStyle kDefaultFontStyle;
+TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {
+    std::shared_ptr<FontFamily> dummyFamily = buildFontFamily(kNoGlyphFont);
+    std::shared_ptr<FontFamily> hasCmapFormat14Family = buildFontFamily(kHasCmapFormat14Font);
+    std::shared_ptr<FontFamily> noCmapFormat14Family = buildFontFamily(kNoCmapFormat14Font);
 
-    std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));
-    std::shared_ptr<MinikinFont> hasCmapFormat14Font(
-            new MinikinFontForTest(kHasCmapFormat14Font));
-    std::shared_ptr<MinikinFont> noCmapFormat14Font(
-            new MinikinFontForTest(kNoCmapFormat14Font));
-
-    std::shared_ptr<FontFamily> dummyFamily(new FontFamily(
-            std::vector<Font>({ Font(dummyFont, FontStyle()) })));
-    std::shared_ptr<FontFamily> hasCmapFormat14Family(new FontFamily(
-            std::vector<Font>({ Font(hasCmapFormat14Font, FontStyle()) })));
-    std::shared_ptr<FontFamily> noCmapFormat14Family(new FontFamily(
-            std::vector<Font>({ Font(noCmapFormat14Font, FontStyle()) })));
-
-    std::vector<std::shared_ptr<FontFamily>> families =
-            { dummyFamily, hasCmapFormat14Family, noCmapFormat14Family };
-    std::vector<std::shared_ptr<FontFamily>> reversedFamilies =
-            { dummyFamily, noCmapFormat14Family, hasCmapFormat14Family };
+    std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, hasCmapFormat14Family,
+                                                         noCmapFormat14Family};
+    std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, noCmapFormat14Family,
+                                                                 hasCmapFormat14Family};
 
     std::shared_ptr<FontCollection> collection(new FontCollection(families));
     std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies));
@@ -1578,11 +1550,63 @@
     // 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", kDefaultFontStyle, &runs);
-    EXPECT_EQ(hasCmapFormat14Font.get(), runs[0].fakedFont.font);
+    itemize(collection, "U+5380 U+E0100", &runs);
+    EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
 
-    itemize(reversedCollection, "U+5380 U+E0100", kDefaultFontStyle, &runs);
-    EXPECT_EQ(noCmapFormat14Font.get(), runs[0].fakedFont.font);
+    itemize(reversedCollection, "U+5380 U+E0100", &runs);
+    EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font);
+}
+
+TEST(FontCollectionItemizeTest, colorEmojiSelectionTest) {
+    auto dummyFamily = buildFontFamily(kNoGlyphFont);
+    auto textEmojiFamily = buildFontFamily(kTextEmojiFont, "ja-JP");
+    auto colorEmojiFamily = buildFontFamily(kColorEmojiFont, "und-Zsye");
+
+    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);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "en-US,en-Zsym", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "en-US,en-Zsye", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "en-US,en-Zsye", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-Zsym-JP", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-Zsym-JP", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-Zsye-JP", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-Zsye-JP", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-JP-u-em-text", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-JP-u-em-text", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-JP-u-em-emoji", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-JP-u-em-emoji", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-JP,und-Zsym", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-JP,und-Zsym", &runs);
+    EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font);
+
+    itemize(collection, "U+203C", "ja-JP,und-Zsye", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
+    itemize(collection, "U+23E9", "ja-JP,und-Zsye", &runs);
+    EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font);
 }
 
 }  // namespace minikin
diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp
index bef1c63..6b39508 100644
--- a/tests/unittest/FontCollectionTest.cpp
+++ b/tests/unittest/FontCollectionTest.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+#include "minikin/FontCollection.h"
+
 #include <gtest/gtest.h>
 
-#include <minikin/FontCollection.h>
 #include "FontTestUtils.h"
-#include "MinikinFontForTest.h"
 #include "MinikinInternal.h"
 
 namespace minikin {
@@ -38,7 +38,7 @@
 // U+717D U+FE02 (VS3)
 // U+717D U+E0102 (VS19)
 // U+717D U+E0103 (VS20)
-const char kVsTestFont[] = kTestFontDir "/VariationSelectorTest-Regular.ttf";
+const char kVsTestFont[] = "VariationSelectorTest-Regular.ttf";
 
 void expectVSGlyphs(const FontCollection* fc, uint32_t codepoint, const std::set<uint32_t>& vsSet) {
     for (uint32_t vs = 0xFE00; vs <= 0xE01EF; ++vs) {
@@ -48,38 +48,36 @@
         }
         if (vsSet.find(vs) == vsSet.end()) {
             EXPECT_FALSE(fc->hasVariationSelector(codepoint, vs))
-                << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
+                    << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
         } else {
             EXPECT_TRUE(fc->hasVariationSelector(codepoint, vs))
-                << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
+                    << "Glyph for U+" << std::hex << codepoint << " U+" << vs;
         }
     }
 }
 
 TEST(FontCollectionTest, hasVariationSelectorTest) {
-  std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kVsTestFont));
-  std::shared_ptr<FontFamily> family(new FontFamily(
-          std::vector<Font>({ Font(font, FontStyle()) })));
-  std::vector<std::shared_ptr<FontFamily>> families({ family });
-  std::shared_ptr<FontCollection> fc(new FontCollection(families));
+    auto fc = buildFontCollection(kVsTestFont);
 
-  EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0));
-  expectVSGlyphs(fc.get(), 0x82A6, std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102}));
+    EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0));
+    expectVSGlyphs(fc.get(), 0x82A6,
+                   std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102}));
 
-  EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0));
-  expectVSGlyphs(fc.get(), 0x845B, std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103}));
+    EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0));
+    expectVSGlyphs(fc.get(), 0x845B,
+                   std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103}));
 
-  EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0));
-  expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E}));
+    EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0));
+    expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E}));
 
-  EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0));
-  expectVSGlyphs(fc.get(), 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103}));
+    EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0));
+    expectVSGlyphs(fc.get(), 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103}));
 }
 
-const char kEmojiXmlFile[] = kTestFontDir "emoji.xml";
+const char kEmojiXmlFile[] = "emoji.xml";
 
 TEST(FontCollectionTest, hasVariationSelectorTest_emoji) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
 
     // Both text/color font have cmap format 14 subtable entry for VS15/VS16 respectively.
     EXPECT_TRUE(collection->hasVariationSelector(0x2623, 0xFE0E));
@@ -108,11 +106,10 @@
     // None of the fonts support U+2229.
     EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0E));
     EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0F));
-
 }
 
 TEST(FontCollectionTest, newEmojiTest) {
-    std::shared_ptr<FontCollection> collection(getFontCollection(kTestFontDir, kEmojiXmlFile));
+    auto collection = buildFontCollectionFromXml(kEmojiXmlFile);
 
     // U+2695, U+2640, U+2642 are not in emoji catrgory in Unicode 9 but they are now in emoji
     // category. Should return true even if U+FE0E was appended.
@@ -126,33 +123,21 @@
 
 TEST(FontCollectionTest, createWithVariations) {
     // This font has 'wdth' and 'wght' axes.
-    const char kMultiAxisFont[] = kTestFontDir "/MultiAxis.ttf";
-    const char kNoAxisFont[] = kTestFontDir "/Regular.ttf";
+    const char kMultiAxisFont[] = "MultiAxis.ttf";
+    const char kNoAxisFont[] = "Regular.ttf";
 
-    std::shared_ptr<MinikinFont> multiAxisFont(new MinikinFontForTest(kMultiAxisFont));
-    std::shared_ptr<FontFamily> multiAxisFamily(new FontFamily(
-            std::vector<Font>({ Font(multiAxisFont, FontStyle()) })));
-    std::vector<std::shared_ptr<FontFamily>> multiAxisFamilies({multiAxisFamily});
-    std::shared_ptr<FontCollection> multiAxisFc(new FontCollection(multiAxisFamilies));
-
-    std::shared_ptr<MinikinFont> noAxisFont(new MinikinFontForTest(kNoAxisFont));
-    std::shared_ptr<FontFamily> noAxisFamily(new FontFamily(
-            std::vector<Font>({ Font(noAxisFont, FontStyle()) })));
-    std::vector<std::shared_ptr<FontFamily>> noAxisFamilies({noAxisFamily});
-    std::shared_ptr<FontCollection> noAxisFc(new FontCollection(noAxisFamilies));
+    std::shared_ptr<FontCollection> multiAxisFc = buildFontCollection(kMultiAxisFont);
+    std::shared_ptr<FontCollection> noAxisFc = buildFontCollection(kNoAxisFont);
 
     {
         // Do not ceate new instance if none of variations are specified.
         EXPECT_EQ(nullptr,
-                multiAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
-        EXPECT_EQ(nullptr,
-                noAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
+                  multiAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
+        EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(std::vector<FontVariation>()));
     }
     {
         // New instance should be used for supported variation.
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f}};
         std::shared_ptr<FontCollection> newFc(
                 multiAxisFc->createCollectionWithVariation(variations));
         EXPECT_NE(nullptr, newFc.get());
@@ -162,10 +147,8 @@
     }
     {
         // New instance should be used for supported variation (multiple variations case).
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
-                { MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+                                                 {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};
         std::shared_ptr<FontCollection> newFc(
                 multiAxisFc->createCollectionWithVariation(variations));
         EXPECT_NE(nullptr, newFc.get());
@@ -175,18 +158,14 @@
     }
     {
         // Do not ceate new instance if none of variations are supported.
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
         EXPECT_EQ(nullptr, multiAxisFc->createCollectionWithVariation(variations));
         EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));
     }
     {
         // At least one axis is supported, should create new instance.
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
-                { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+                                                 {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
         std::shared_ptr<FontCollection> newFc(
                 multiAxisFc->createCollectionWithVariation(variations));
         EXPECT_NE(nullptr, newFc.get());
diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp
index 90e2a64..28662c1 100644
--- a/tests/unittest/FontFamilyTest.cpp
+++ b/tests/unittest/FontFamilyTest.cpp
@@ -14,68 +14,59 @@
  * limitations under the License.
  */
 
-#include <minikin/FontFamily.h>
+#include "minikin/FontFamily.h"
 
-#include <android/log.h>
 #include <gtest/gtest.h>
 
-#include "FontLanguageListCache.h"
-#include "ICUTestBase.h"
-#include "MinikinFontForTest.h"
+#include "minikin/LocaleList.h"
+
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "LocaleListCache.h"
 #include "MinikinInternal.h"
 
 namespace minikin {
 
-typedef ICUTestBase FontLanguagesTest;
-typedef ICUTestBase FontLanguageTest;
-
-static const FontLanguages& createFontLanguages(const std::string& input) {
-    android::AutoMutex _l(gMinikinLock);
-    uint32_t langId = FontLanguageListCache::getId(input);
-    return FontLanguageListCache::getById(langId);
+static const LocaleList& createLocaleList(const std::string& input) {
+    uint32_t localeListId = LocaleListCache::getId(input);
+    return LocaleListCache::getById(localeListId);
 }
 
-static FontLanguage createFontLanguage(const std::string& input) {
-    android::AutoMutex _l(gMinikinLock);
-    uint32_t langId = FontLanguageListCache::getId(input);
-    return FontLanguageListCache::getById(langId)[0];
+static Locale createLocale(const std::string& input) {
+    uint32_t localeListId = LocaleListCache::getId(input);
+    return LocaleListCache::getById(localeListId)[0];
 }
 
-static FontLanguage createFontLanguageWithoutICUSanitization(const std::string& input) {
-    return FontLanguage(input.c_str(), input.size());
+static Locale createLocaleWithoutICUSanitization(const std::string& input) {
+    return Locale(input);
 }
 
-std::shared_ptr<FontFamily> makeFamily(const std::string& fontPath) {
-    std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath));
-    return std::make_shared<FontFamily>(
-            std::vector<Font>({Font(font, FontStyle())}));
-}
-
-TEST_F(FontLanguageTest, basicTests) {
-    FontLanguage defaultLang;
-    FontLanguage emptyLang("", 0);
-    FontLanguage english = createFontLanguage("en");
-    FontLanguage french = createFontLanguage("fr");
-    FontLanguage und = createFontLanguage("und");
-    FontLanguage undZsye = createFontLanguage("und-Zsye");
+TEST(LocaleTest, basicTests) {
+    Locale defaultLocale;
+    Locale emptyLocale("");
+    Locale english = createLocale("en");
+    Locale french = createLocale("fr");
+    Locale und = createLocale("und");
+    Locale undZsye = createLocale("und-Zsye");
 
     EXPECT_EQ(english, english);
     EXPECT_EQ(french, french);
 
-    EXPECT_TRUE(defaultLang != defaultLang);
-    EXPECT_TRUE(emptyLang != emptyLang);
-    EXPECT_TRUE(defaultLang != emptyLang);
-    EXPECT_TRUE(defaultLang != und);
-    EXPECT_TRUE(emptyLang != und);
-    EXPECT_TRUE(english != defaultLang);
-    EXPECT_TRUE(english != emptyLang);
+    EXPECT_TRUE(defaultLocale != defaultLocale);
+    EXPECT_TRUE(emptyLocale != emptyLocale);
+    EXPECT_TRUE(defaultLocale != emptyLocale);
+    EXPECT_TRUE(defaultLocale != und);
+    EXPECT_TRUE(emptyLocale != und);
+    EXPECT_TRUE(english != defaultLocale);
+    EXPECT_TRUE(english != emptyLocale);
     EXPECT_TRUE(english != french);
     EXPECT_TRUE(english != undZsye);
     EXPECT_TRUE(und != undZsye);
     EXPECT_TRUE(english != und);
+    EXPECT_TRUE(createLocale("de-1901") != createLocale("de-1996"));
 
-    EXPECT_TRUE(defaultLang.isUnsupported());
-    EXPECT_TRUE(emptyLang.isUnsupported());
+    EXPECT_TRUE(defaultLocale.isUnsupported());
+    EXPECT_TRUE(emptyLocale.isUnsupported());
 
     EXPECT_FALSE(english.isUnsupported());
     EXPECT_FALSE(french.isUnsupported());
@@ -83,84 +74,87 @@
     EXPECT_FALSE(undZsye.isUnsupported());
 }
 
-TEST_F(FontLanguageTest, getStringTest) {
-    EXPECT_EQ("en-Latn-US", createFontLanguage("en").getString());
-    EXPECT_EQ("en-Latn-US", createFontLanguage("en-Latn").getString());
+TEST(LocaleTest, getStringTest) {
+    EXPECT_EQ("en-Latn-US", createLocale("en").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("en-Latn").getString());
 
     // Capitalized language code or lowercased script should be normalized.
-    EXPECT_EQ("en-Latn-US", createFontLanguage("EN-LATN").getString());
-    EXPECT_EQ("en-Latn-US", createFontLanguage("EN-latn").getString());
-    EXPECT_EQ("en-Latn-US", createFontLanguage("en-latn").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("EN-LATN").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("EN-latn").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("en-latn").getString());
 
     // Invalid script should be kept.
-    EXPECT_EQ("en-Xyzt-US", createFontLanguage("en-xyzt").getString());
+    EXPECT_EQ("en-Xyzt-US", createLocale("en-xyzt").getString());
 
-    EXPECT_EQ("en-Latn-US", createFontLanguage("en-Latn-US").getString());
-    EXPECT_EQ("ja-Jpan-JP", createFontLanguage("ja").getString());
-    EXPECT_EQ("zh-Hant-TW", createFontLanguage("zh-TW").getString());
-    EXPECT_EQ("zh-Hant-HK", createFontLanguage("zh-HK").getString());
-    EXPECT_EQ("zh-Hant-MO", createFontLanguage("zh-MO").getString());
-    EXPECT_EQ("zh-Hans-CN", createFontLanguage("zh").getString());
-    EXPECT_EQ("zh-Hans-CN", createFontLanguage("zh-CN").getString());
-    EXPECT_EQ("zh-Hans-SG", createFontLanguage("zh-SG").getString());
-    EXPECT_EQ("und", createFontLanguage("und").getString());
-    EXPECT_EQ("und", createFontLanguage("UND").getString());
-    EXPECT_EQ("und", createFontLanguage("Und").getString());
-    EXPECT_EQ("und-Zsye", createFontLanguage("und-Zsye").getString());
-    EXPECT_EQ("und-Zsye", createFontLanguage("Und-ZSYE").getString());
-    EXPECT_EQ("und-Zsye", createFontLanguage("Und-zsye").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("en-Latn-US").getString());
+    EXPECT_EQ("ja-Jpan-JP", createLocale("ja").getString());
+    EXPECT_EQ("zh-Hant-TW", createLocale("zh-TW").getString());
+    EXPECT_EQ("zh-Hant-HK", createLocale("zh-HK").getString());
+    EXPECT_EQ("zh-Hant-MO", createLocale("zh-MO").getString());
+    EXPECT_EQ("zh-Hans-CN", createLocale("zh").getString());
+    EXPECT_EQ("zh-Hans-CN", createLocale("zh-CN").getString());
+    EXPECT_EQ("zh-Hans-SG", createLocale("zh-SG").getString());
+    EXPECT_EQ("und", createLocale("und").getString());
+    EXPECT_EQ("und", createLocale("UND").getString());
+    EXPECT_EQ("und", createLocale("Und").getString());
+    EXPECT_EQ("und-Zsye", createLocale("und-Zsye").getString());
+    EXPECT_EQ("und-Zsye", createLocale("Und-ZSYE").getString());
+    EXPECT_EQ("und-Zsye", createLocale("Und-zsye").getString());
 
-    EXPECT_EQ("de-Latn-DE", createFontLanguage("de-1901").getString());
+    EXPECT_EQ("es-Latn-419", createLocale("es-Latn-419").getString());
 
-    EXPECT_EQ("es-Latn-419", createFontLanguage("es-Latn-419").getString());
+    // Variant
+    EXPECT_EQ("de-Latn-DE", createLocale("de").getString());
+    EXPECT_EQ("de-Latn-DE-1901", createLocale("de-1901").getString());
+    EXPECT_EQ("de-Latn-DE-1996", createLocale("de-DE-1996").getString());
 
     // Emoji subtag is dropped from getString().
-    EXPECT_EQ("es-Latn-419", createFontLanguage("es-419-u-em-emoji").getString());
-    EXPECT_EQ("es-Latn-419", createFontLanguage("es-Latn-419-u-em-emoji").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());
 
     // This is not a necessary desired behavior, just known behavior.
-    EXPECT_EQ("en-Latn-US", createFontLanguage("und-Abcdefgh").getString());
+    EXPECT_EQ("en-Latn-US", createLocale("und-Abcdefgh").getString());
 }
 
-TEST_F(FontLanguageTest, testReconstruction) {
-    EXPECT_EQ("en", createFontLanguageWithoutICUSanitization("en").getString());
-    EXPECT_EQ("fil", createFontLanguageWithoutICUSanitization("fil").getString());
-    EXPECT_EQ("und", createFontLanguageWithoutICUSanitization("und").getString());
+TEST(LocaleTest, testReconstruction) {
+    EXPECT_EQ("en", createLocaleWithoutICUSanitization("en").getString());
+    EXPECT_EQ("fil", createLocaleWithoutICUSanitization("fil").getString());
+    EXPECT_EQ("und", createLocaleWithoutICUSanitization("und").getString());
 
-    EXPECT_EQ("en-Latn", createFontLanguageWithoutICUSanitization("en-Latn").getString());
-    EXPECT_EQ("fil-Taga", createFontLanguageWithoutICUSanitization("fil-Taga").getString());
-    EXPECT_EQ("und-Zsye", createFontLanguageWithoutICUSanitization("und-Zsye").getString());
+    EXPECT_EQ("en-Latn", createLocaleWithoutICUSanitization("en-Latn").getString());
+    EXPECT_EQ("fil-Taga", createLocaleWithoutICUSanitization("fil-Taga").getString());
+    EXPECT_EQ("und-Zsye", createLocaleWithoutICUSanitization("und-Zsye").getString());
 
-    EXPECT_EQ("en-US", createFontLanguageWithoutICUSanitization("en-US").getString());
-    EXPECT_EQ("fil-PH", createFontLanguageWithoutICUSanitization("fil-PH").getString());
-    EXPECT_EQ("es-419", createFontLanguageWithoutICUSanitization("es-419").getString());
+    EXPECT_EQ("en-US", createLocaleWithoutICUSanitization("en-US").getString());
+    EXPECT_EQ("fil-PH", createLocaleWithoutICUSanitization("fil-PH").getString());
+    EXPECT_EQ("es-419", createLocaleWithoutICUSanitization("es-419").getString());
 
-    EXPECT_EQ("en-Latn-US", createFontLanguageWithoutICUSanitization("en-Latn-US").getString());
-    EXPECT_EQ("fil-Taga-PH", createFontLanguageWithoutICUSanitization("fil-Taga-PH").getString());
-    EXPECT_EQ("es-Latn-419", createFontLanguageWithoutICUSanitization("es-Latn-419").getString());
+    EXPECT_EQ("en-Latn-US", createLocaleWithoutICUSanitization("en-Latn-US").getString());
+    EXPECT_EQ("fil-Taga-PH", createLocaleWithoutICUSanitization("fil-Taga-PH").getString());
+    EXPECT_EQ("es-Latn-419", createLocaleWithoutICUSanitization("es-Latn-419").getString());
 
     // Possible minimum/maximum values.
-    EXPECT_EQ("aa", createFontLanguageWithoutICUSanitization("aa").getString());
-    EXPECT_EQ("zz", createFontLanguageWithoutICUSanitization("zz").getString());
-    EXPECT_EQ("aa-Aaaa", createFontLanguageWithoutICUSanitization("aa-Aaaa").getString());
-    EXPECT_EQ("zz-Zzzz", createFontLanguageWithoutICUSanitization("zz-Zzzz").getString());
-    EXPECT_EQ("aaa-Aaaa-AA", createFontLanguageWithoutICUSanitization("aaa-Aaaa-AA").getString());
-    EXPECT_EQ("zzz-Zzzz-ZZ", createFontLanguageWithoutICUSanitization("zzz-Zzzz-ZZ").getString());
-    EXPECT_EQ("aaa-Aaaa-000", createFontLanguageWithoutICUSanitization("aaa-Aaaa-000").getString());
-    EXPECT_EQ("zzz-Zzzz-999", createFontLanguageWithoutICUSanitization("zzz-Zzzz-999").getString());
+    EXPECT_EQ("aa", createLocaleWithoutICUSanitization("aa").getString());
+    EXPECT_EQ("zz", createLocaleWithoutICUSanitization("zz").getString());
+    EXPECT_EQ("aa-Aaaa", createLocaleWithoutICUSanitization("aa-Aaaa").getString());
+    EXPECT_EQ("zz-Zzzz", createLocaleWithoutICUSanitization("zz-Zzzz").getString());
+    EXPECT_EQ("aaa-Aaaa-AA", createLocaleWithoutICUSanitization("aaa-Aaaa-AA").getString());
+    EXPECT_EQ("zzz-Zzzz-ZZ", createLocaleWithoutICUSanitization("zzz-Zzzz-ZZ").getString());
+    EXPECT_EQ("aaa-Aaaa-000", createLocaleWithoutICUSanitization("aaa-Aaaa-000").getString());
+    EXPECT_EQ("zzz-Zzzz-999", createLocaleWithoutICUSanitization("zzz-Zzzz-999").getString());
 }
 
-TEST_F(FontLanguageTest, ScriptEqualTest) {
-    EXPECT_TRUE(createFontLanguage("en").isEqualScript(createFontLanguage("en")));
-    EXPECT_TRUE(createFontLanguage("en-Latn").isEqualScript(createFontLanguage("en")));
-    EXPECT_TRUE(createFontLanguage("jp-Latn").isEqualScript(createFontLanguage("en-Latn")));
-    EXPECT_TRUE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Jpan")));
+TEST(LocaleTest, ScriptEqualTest) {
+    EXPECT_TRUE(createLocale("en").isEqualScript(createLocale("en")));
+    EXPECT_TRUE(createLocale("en-Latn").isEqualScript(createLocale("en")));
+    EXPECT_TRUE(createLocale("jp-Latn").isEqualScript(createLocale("en-Latn")));
+    EXPECT_TRUE(createLocale("en-Jpan").isEqualScript(createLocale("en-Jpan")));
 
-    EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hira")));
-    EXPECT_FALSE(createFontLanguage("en-Jpan").isEqualScript(createFontLanguage("en-Hani")));
+    EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hira")));
+    EXPECT_FALSE(createLocale("en-Jpan").isEqualScript(createLocale("en-Hani")));
 }
 
-TEST_F(FontLanguageTest, ScriptMatchTest) {
+TEST(LocaleTest, ScriptMatchTest) {
     const bool SUPPORTED = true;
     const bool NOT_SUPPORTED = false;
 
@@ -169,80 +163,80 @@
         const std::string requestedScript;
         bool isSupported;
     } testCases[] = {
-        // Same scripts
-        { "en-Latn", "Latn", SUPPORTED },
-        { "ja-Jpan", "Jpan", SUPPORTED },
-        { "ja-Hira", "Hira", SUPPORTED },
-        { "ja-Kana", "Kana", SUPPORTED },
-        { "ja-Hrkt", "Hrkt", SUPPORTED },
-        { "zh-Hans", "Hans", SUPPORTED },
-        { "zh-Hant", "Hant", SUPPORTED },
-        { "zh-Hani", "Hani", SUPPORTED },
-        { "ko-Kore", "Kore", SUPPORTED },
-        { "ko-Hang", "Hang", SUPPORTED },
-        { "zh-Hanb", "Hanb", SUPPORTED },
+            // Same scripts
+            {"en-Latn", "Latn", SUPPORTED},
+            {"ja-Jpan", "Jpan", SUPPORTED},
+            {"ja-Hira", "Hira", SUPPORTED},
+            {"ja-Kana", "Kana", SUPPORTED},
+            {"ja-Hrkt", "Hrkt", SUPPORTED},
+            {"zh-Hans", "Hans", SUPPORTED},
+            {"zh-Hant", "Hant", SUPPORTED},
+            {"zh-Hani", "Hani", SUPPORTED},
+            {"ko-Kore", "Kore", SUPPORTED},
+            {"ko-Hang", "Hang", SUPPORTED},
+            {"zh-Hanb", "Hanb", SUPPORTED},
 
-        // Japanese supports Hiragana, Katakanara, etc.
-        { "ja-Jpan", "Hira", SUPPORTED },
-        { "ja-Jpan", "Kana", SUPPORTED },
-        { "ja-Jpan", "Hrkt", SUPPORTED },
-        { "ja-Hrkt", "Hira", SUPPORTED },
-        { "ja-Hrkt", "Kana", SUPPORTED },
+            // Japanese supports Hiragana, Katakanara, etc.
+            {"ja-Jpan", "Hira", SUPPORTED},
+            {"ja-Jpan", "Kana", SUPPORTED},
+            {"ja-Jpan", "Hrkt", SUPPORTED},
+            {"ja-Hrkt", "Hira", SUPPORTED},
+            {"ja-Hrkt", "Kana", SUPPORTED},
 
-        // Chinese supports Han.
-        { "zh-Hans", "Hani", SUPPORTED },
-        { "zh-Hant", "Hani", SUPPORTED },
-        { "zh-Hanb", "Hani", SUPPORTED },
+            // Chinese supports Han.
+            {"zh-Hans", "Hani", SUPPORTED},
+            {"zh-Hant", "Hani", SUPPORTED},
+            {"zh-Hanb", "Hani", SUPPORTED},
 
-        // Hanb supports Bopomofo.
-        { "zh-Hanb", "Bopo", SUPPORTED },
+            // Hanb supports Bopomofo.
+            {"zh-Hanb", "Bopo", SUPPORTED},
 
-        // Korean supports Hangul.
-        { "ko-Kore", "Hang", SUPPORTED },
+            // Korean supports Hangul.
+            {"ko-Kore", "Hang", SUPPORTED},
 
-        // Different scripts
-        { "ja-Jpan", "Latn", NOT_SUPPORTED },
-        { "en-Latn", "Jpan", NOT_SUPPORTED },
-        { "ja-Jpan", "Hant", NOT_SUPPORTED },
-        { "zh-Hant", "Jpan", NOT_SUPPORTED },
-        { "ja-Jpan", "Hans", NOT_SUPPORTED },
-        { "zh-Hans", "Jpan", NOT_SUPPORTED },
-        { "ja-Jpan", "Kore", NOT_SUPPORTED },
-        { "ko-Kore", "Jpan", NOT_SUPPORTED },
-        { "zh-Hans", "Hant", NOT_SUPPORTED },
-        { "zh-Hant", "Hans", NOT_SUPPORTED },
-        { "zh-Hans", "Kore", NOT_SUPPORTED },
-        { "ko-Kore", "Hans", NOT_SUPPORTED },
-        { "zh-Hant", "Kore", NOT_SUPPORTED },
-        { "ko-Kore", "Hant", NOT_SUPPORTED },
+            // Different scripts
+            {"ja-Jpan", "Latn", NOT_SUPPORTED},
+            {"en-Latn", "Jpan", NOT_SUPPORTED},
+            {"ja-Jpan", "Hant", NOT_SUPPORTED},
+            {"zh-Hant", "Jpan", NOT_SUPPORTED},
+            {"ja-Jpan", "Hans", NOT_SUPPORTED},
+            {"zh-Hans", "Jpan", NOT_SUPPORTED},
+            {"ja-Jpan", "Kore", NOT_SUPPORTED},
+            {"ko-Kore", "Jpan", NOT_SUPPORTED},
+            {"zh-Hans", "Hant", NOT_SUPPORTED},
+            {"zh-Hant", "Hans", NOT_SUPPORTED},
+            {"zh-Hans", "Kore", NOT_SUPPORTED},
+            {"ko-Kore", "Hans", NOT_SUPPORTED},
+            {"zh-Hant", "Kore", NOT_SUPPORTED},
+            {"ko-Kore", "Hant", NOT_SUPPORTED},
 
-        // Hiragana doesn't support Japanese, etc.
-        { "ja-Hira", "Jpan", NOT_SUPPORTED },
-        { "ja-Kana", "Jpan", NOT_SUPPORTED },
-        { "ja-Hrkt", "Jpan", NOT_SUPPORTED },
-        { "ja-Hani", "Jpan", NOT_SUPPORTED },
-        { "ja-Hira", "Hrkt", NOT_SUPPORTED },
-        { "ja-Kana", "Hrkt", NOT_SUPPORTED },
-        { "ja-Hani", "Hrkt", NOT_SUPPORTED },
-        { "ja-Hani", "Hira", NOT_SUPPORTED },
-        { "ja-Hani", "Kana", NOT_SUPPORTED },
+            // Hiragana doesn't support Japanese, etc.
+            {"ja-Hira", "Jpan", NOT_SUPPORTED},
+            {"ja-Kana", "Jpan", NOT_SUPPORTED},
+            {"ja-Hrkt", "Jpan", NOT_SUPPORTED},
+            {"ja-Hani", "Jpan", NOT_SUPPORTED},
+            {"ja-Hira", "Hrkt", NOT_SUPPORTED},
+            {"ja-Kana", "Hrkt", NOT_SUPPORTED},
+            {"ja-Hani", "Hrkt", NOT_SUPPORTED},
+            {"ja-Hani", "Hira", NOT_SUPPORTED},
+            {"ja-Hani", "Kana", NOT_SUPPORTED},
 
-        // Kanji doesn't support Chinese, etc.
-        { "zh-Hani", "Hant", NOT_SUPPORTED },
-        { "zh-Hani", "Hans", NOT_SUPPORTED },
-        { "zh-Hani", "Hanb", NOT_SUPPORTED },
+            // Kanji doesn't support Chinese, etc.
+            {"zh-Hani", "Hant", NOT_SUPPORTED},
+            {"zh-Hani", "Hans", NOT_SUPPORTED},
+            {"zh-Hani", "Hanb", NOT_SUPPORTED},
 
-        // Hangul doesn't support Korean, etc.
-        { "ko-Hang", "Kore", NOT_SUPPORTED },
-        { "ko-Hani", "Kore", NOT_SUPPORTED },
-        { "ko-Hani", "Hang", NOT_SUPPORTED },
-        { "ko-Hang", "Hani", NOT_SUPPORTED },
+            // Hangul doesn't support Korean, etc.
+            {"ko-Hang", "Kore", NOT_SUPPORTED},
+            {"ko-Hani", "Kore", NOT_SUPPORTED},
+            {"ko-Hani", "Hang", NOT_SUPPORTED},
+            {"ko-Hang", "Hani", NOT_SUPPORTED},
 
-        // Han with botomofo doesn't support simplified Chinese, etc.
-        { "zh-Hanb", "Hant", NOT_SUPPORTED },
-        { "zh-Hanb", "Hans", NOT_SUPPORTED },
-        { "zh-Hanb", "Jpan", NOT_SUPPORTED },
-        { "zh-Hanb", "Kore", NOT_SUPPORTED },
+            // Han with botomofo doesn't support simplified Chinese, etc.
+            {"zh-Hanb", "Hant", NOT_SUPPORTED},
+            {"zh-Hanb", "Hans", NOT_SUPPORTED},
+            {"zh-Hanb", "Jpan", NOT_SUPPORTED},
+            {"zh-Hanb", "Kore", NOT_SUPPORTED},
     };
 
     for (auto testCase : testCases) {
@@ -250,240 +244,217 @@
                 HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1],
                        testCase.requestedScript[2], testCase.requestedScript[3]));
         if (testCase.isSupported) {
-            EXPECT_TRUE(
-                    createFontLanguage(testCase.baseScript).supportsHbScript(script))
+            EXPECT_TRUE(createLocale(testCase.baseScript).supportsHbScript(script))
                     << testCase.baseScript << " should support " << testCase.requestedScript;
         } else {
-            EXPECT_FALSE(
-                    createFontLanguage(testCase.baseScript).supportsHbScript(script))
+            EXPECT_FALSE(createLocale(testCase.baseScript).supportsHbScript(script))
                     << testCase.baseScript << " shouldn't support " << testCase.requestedScript;
         }
     }
 }
 
-TEST_F(FontLanguagesTest, basicTests) {
-    FontLanguages emptyLangs;
-    EXPECT_EQ(0u, emptyLangs.size());
+TEST(LocaleListTest, basicTests) {
+    LocaleList emptyLocales;
+    EXPECT_EQ(0u, emptyLocales.size());
 
-    FontLanguage english = createFontLanguage("en");
-    const FontLanguages& singletonLangs = createFontLanguages("en");
-    EXPECT_EQ(1u, singletonLangs.size());
-    EXPECT_EQ(english, singletonLangs[0]);
+    Locale english = createLocale("en");
+    const LocaleList& singletonLocales = createLocaleList("en");
+    EXPECT_EQ(1u, singletonLocales.size());
+    EXPECT_EQ(english, singletonLocales[0]);
 
-    FontLanguage french = createFontLanguage("fr");
-    const FontLanguages& twoLangs = createFontLanguages("en,fr");
-    EXPECT_EQ(2u, twoLangs.size());
-    EXPECT_EQ(english, twoLangs[0]);
-    EXPECT_EQ(french, twoLangs[1]);
+    Locale french = createLocale("fr");
+    const LocaleList& twoLocales = createLocaleList("en,fr");
+    EXPECT_EQ(2u, twoLocales.size());
+    EXPECT_EQ(english, twoLocales[0]);
+    EXPECT_EQ(french, twoLocales[1]);
 }
 
-TEST_F(FontLanguagesTest, unsupportedLanguageTests) {
-    const FontLanguages& oneUnsupported = createFontLanguages("abcd-example");
+TEST(LocaleListTest, unsupportedLocaleuageTests) {
+    const LocaleList& oneUnsupported = createLocaleList("abcd-example");
     EXPECT_TRUE(oneUnsupported.empty());
 
-    const FontLanguages& twoUnsupporteds = createFontLanguages("abcd-example,abcd-example");
+    const LocaleList& twoUnsupporteds = createLocaleList("abcd-example,abcd-example");
     EXPECT_TRUE(twoUnsupporteds.empty());
 
-    FontLanguage english = createFontLanguage("en");
-    const FontLanguages& firstUnsupported = createFontLanguages("abcd-example,en");
+    Locale english = createLocale("en");
+    const LocaleList& firstUnsupported = createLocaleList("abcd-example,en");
     EXPECT_EQ(1u, firstUnsupported.size());
     EXPECT_EQ(english, firstUnsupported[0]);
 
-    const FontLanguages& lastUnsupported = createFontLanguages("en,abcd-example");
+    const LocaleList& lastUnsupported = createLocaleList("en,abcd-example");
     EXPECT_EQ(1u, lastUnsupported.size());
     EXPECT_EQ(english, lastUnsupported[0]);
 }
 
-TEST_F(FontLanguagesTest, repeatedLanguageTests) {
-    FontLanguage english = createFontLanguage("en");
-    FontLanguage french = createFontLanguage("fr");
-    FontLanguage canadianFrench = createFontLanguage("fr-CA");
-    FontLanguage englishInLatn = createFontLanguage("en-Latn");
+TEST(LocaleListTest, repeatedLocaleuageTests) {
+    Locale english = createLocale("en");
+    Locale french = createLocale("fr");
+    Locale canadianFrench = createLocale("fr-CA");
+    Locale englishInLatn = createLocale("en-Latn");
     ASSERT_TRUE(english == englishInLatn);
 
-    const FontLanguages& langs = createFontLanguages("en,en-Latn");
-    EXPECT_EQ(1u, langs.size());
-    EXPECT_EQ(english, langs[0]);
+    const LocaleList& locales = createLocaleList("en,en-Latn");
+    EXPECT_EQ(1u, locales.size());
+    EXPECT_EQ(english, locales[0]);
 
-    const FontLanguages& fr = createFontLanguages("fr,fr-FR,fr-Latn-FR");
+    const LocaleList& fr = createLocaleList("fr,fr-FR,fr-Latn-FR");
     EXPECT_EQ(1u, fr.size());
     EXPECT_EQ(french, fr[0]);
 
     // ICU appends FR to fr. The third language is dropped which is same as the first language.
-    const FontLanguages& fr2 = createFontLanguages("fr,fr-CA,fr-FR");
+    const LocaleList& fr2 = createLocaleList("fr,fr-CA,fr-FR");
     EXPECT_EQ(2u, fr2.size());
     EXPECT_EQ(french, fr2[0]);
     EXPECT_EQ(canadianFrench, fr2[1]);
 
     // The order should be kept.
-    const FontLanguages& langs2 = createFontLanguages("en,fr,en-Latn");
-    EXPECT_EQ(2u, langs2.size());
-    EXPECT_EQ(english, langs2[0]);
-    EXPECT_EQ(french, langs2[1]);
+    const LocaleList& locales2 = createLocaleList("en,fr,en-Latn");
+    EXPECT_EQ(2u, locales2.size());
+    EXPECT_EQ(english, locales2[0]);
+    EXPECT_EQ(french, locales2[1]);
 }
 
-TEST_F(FontLanguagesTest, identifierTest) {
-    EXPECT_EQ(createFontLanguage("en-Latn-US"), createFontLanguage("en-Latn-US"));
-    EXPECT_EQ(createFontLanguage("zh-Hans-CN"), createFontLanguage("zh-Hans-CN"));
-    EXPECT_EQ(createFontLanguage("en-Zsye-US"), createFontLanguage("en-Zsye-US"));
+TEST(LocaleListTest, identifierTest) {
+    EXPECT_EQ(createLocale("en-Latn-US"), createLocale("en-Latn-US"));
+    EXPECT_EQ(createLocale("zh-Hans-CN"), createLocale("zh-Hans-CN"));
+    EXPECT_EQ(createLocale("en-Zsye-US"), createLocale("en-Zsye-US"));
 
-    EXPECT_NE(createFontLanguage("en-Latn-US"), createFontLanguage("en-Latn-GB"));
-    EXPECT_NE(createFontLanguage("en-Latn-US"), createFontLanguage("en-Zsye-US"));
-    EXPECT_NE(createFontLanguage("es-Latn-US"), createFontLanguage("en-Latn-US"));
-    EXPECT_NE(createFontLanguage("zh-Hant-HK"), createFontLanguage("zh-Hant-TW"));
+    EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Latn-GB"));
+    EXPECT_NE(createLocale("en-Latn-US"), createLocale("en-Zsye-US"));
+    EXPECT_NE(createLocale("es-Latn-US"), createLocale("en-Latn-US"));
+    EXPECT_NE(createLocale("zh-Hant-HK"), createLocale("zh-Hant-TW"));
 }
 
-TEST_F(FontLanguagesTest, undEmojiTests) {
-    FontLanguage emoji = createFontLanguage("und-Zsye");
-    EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, emoji.getEmojiStyle());
+TEST(LocaleListTest, undEmojiTests) {
+    Locale emoji = createLocale("und-Zsye");
+    EXPECT_EQ(EmojiStyle::EMOJI, emoji.getEmojiStyle());
 
-    FontLanguage und = createFontLanguage("und");
-    EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, und.getEmojiStyle());
+    Locale und = createLocale("und");
+    EXPECT_EQ(EmojiStyle::EMPTY, und.getEmojiStyle());
     EXPECT_FALSE(emoji == und);
 
-    FontLanguage undExample = createFontLanguage("und-example");
-    EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, undExample.getEmojiStyle());
+    Locale undExample = createLocale("und-example");
+    EXPECT_EQ(EmojiStyle::EMPTY, undExample.getEmojiStyle());
     EXPECT_FALSE(emoji == undExample);
 }
 
-TEST_F(FontLanguagesTest, subtagEmojiTest) {
+TEST(LocaleListTest, subtagEmojiTest) {
     std::string subtagEmojiStrings[] = {
-        // Duplicate subtag case.
-        "und-Latn-u-em-emoji-u-em-text",
+            // Duplicate subtag case.
+            "und-Latn-u-em-emoji-u-em-text",
 
-        // Strings that contain language.
-        "und-u-em-emoji",
-        "en-u-em-emoji",
+            // Strings that contain language.
+            "und-u-em-emoji", "en-u-em-emoji",
 
-        // Strings that contain the script.
-        "und-Jpan-u-em-emoji",
-        "en-Latn-u-em-emoji",
-        "und-Zsym-u-em-emoji",
-        "und-Zsye-u-em-emoji",
-        "en-Zsym-u-em-emoji",
-        "en-Zsye-u-em-emoji",
+            // Strings that contain the script.
+            "und-Jpan-u-em-emoji", "en-Latn-u-em-emoji", "und-Zsym-u-em-emoji",
+            "und-Zsye-u-em-emoji", "en-Zsym-u-em-emoji", "en-Zsye-u-em-emoji",
 
-        // Strings that contain the county.
-        "und-US-u-em-emoji",
-        "en-US-u-em-emoji",
-        "es-419-u-em-emoji",
-        "und-Latn-US-u-em-emoji",
-        "en-Zsym-US-u-em-emoji",
-        "en-Zsye-US-u-em-emoji",
-        "es-Zsye-419-u-em-emoji",
+            // Strings that contain the country.
+            "und-US-u-em-emoji", "en-US-u-em-emoji", "es-419-u-em-emoji", "und-Latn-US-u-em-emoji",
+            "en-Zsym-US-u-em-emoji", "en-Zsye-US-u-em-emoji", "es-Zsye-419-u-em-emoji",
+
+            // Strings that contain the variant.
+            "de-Latn-DE-1901-u-em-emoji",
     };
 
     for (auto subtagEmojiString : subtagEmojiStrings) {
         SCOPED_TRACE("Test for \"" + subtagEmojiString + "\"");
-        FontLanguage subtagEmoji = createFontLanguage(subtagEmojiString);
-        EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, subtagEmoji.getEmojiStyle());
+        Locale subtagEmoji = createLocale(subtagEmojiString);
+        EXPECT_EQ(EmojiStyle::EMOJI, subtagEmoji.getEmojiStyle());
     }
 }
 
-TEST_F(FontLanguagesTest, subtagTextTest) {
+TEST(LocaleListTest, subtagTextTest) {
     std::string subtagTextStrings[] = {
-        // Duplicate subtag case.
-        "und-Latn-u-em-text-u-em-emoji",
+            // Duplicate subtag case.
+            "und-Latn-u-em-text-u-em-emoji",
 
-        // Strings that contain language.
-        "und-u-em-text",
-        "en-u-em-text",
+            // Strings that contain language.
+            "und-u-em-text", "en-u-em-text",
 
-        // Strings that contain the script.
-        "und-Latn-u-em-text",
-        "en-Jpan-u-em-text",
-        "und-Zsym-u-em-text",
-        "und-Zsye-u-em-text",
-        "en-Zsym-u-em-text",
-        "en-Zsye-u-em-text",
+            // Strings that contain the script.
+            "und-Latn-u-em-text", "en-Jpan-u-em-text", "und-Zsym-u-em-text", "und-Zsye-u-em-text",
+            "en-Zsym-u-em-text", "en-Zsye-u-em-text",
 
-        // Strings that contain the county.
-        "und-US-u-em-text",
-        "en-US-u-em-text",
-        "es-419-u-em-text",
-        "und-Latn-US-u-em-text",
-        "en-Zsym-US-u-em-text",
-        "en-Zsye-US-u-em-text",
-        "es-Zsye-419-u-em-text",
+            // Strings that contain the country.
+            "und-US-u-em-text", "en-US-u-em-text", "es-419-u-em-text", "und-Latn-US-u-em-text",
+            "en-Zsym-US-u-em-text", "en-Zsye-US-u-em-text", "es-Zsye-419-u-em-text",
+
+            // Strings that contain the variant.
+            "de-Latn-DE-1901-u-em-text",
     };
 
     for (auto subtagTextString : subtagTextStrings) {
         SCOPED_TRACE("Test for \"" + subtagTextString + "\"");
-        FontLanguage subtagText = createFontLanguage(subtagTextString);
-        EXPECT_EQ(FontLanguage::EMSTYLE_TEXT, subtagText.getEmojiStyle());
+        Locale subtagText = createLocale(subtagTextString);
+        EXPECT_EQ(EmojiStyle::TEXT, subtagText.getEmojiStyle());
     }
 }
 
 // TODO: add more "und" language cases whose language and script are
 //       unexpectedly translated to en-Latn by ICU.
-TEST_F(FontLanguagesTest, subtagDefaultTest) {
+TEST(LocaleListTest, subtagDefaultTest) {
     std::string subtagDefaultStrings[] = {
-        // Duplicate subtag case.
-        "en-Latn-u-em-default-u-em-emoji",
-        "en-Latn-u-em-default-u-em-text",
+            // Duplicate subtag case.
+            "en-Latn-u-em-default-u-em-emoji", "en-Latn-u-em-default-u-em-text",
 
-        // Strings that contain language.
-        "und-u-em-default",
-        "en-u-em-default",
+            // Strings that contain language.
+            "und-u-em-default", "en-u-em-default",
 
-        // Strings that contain the script.
-        "en-Latn-u-em-default",
-        "en-Zsym-u-em-default",
-        "en-Zsye-u-em-default",
+            // Strings that contain the script.
+            "en-Latn-u-em-default", "en-Zsym-u-em-default", "en-Zsye-u-em-default",
 
-        // Strings that contain the county.
-        "en-US-u-em-default",
-        "en-Latn-US-u-em-default",
-        "es-Latn-419-u-em-default",
-        "en-Zsym-US-u-em-default",
-        "en-Zsye-US-u-em-default",
-        "es-Zsye-419-u-em-default",
+            // Strings that contain the country.
+            "en-US-u-em-default", "en-Latn-US-u-em-default", "es-Latn-419-u-em-default",
+            "en-Zsym-US-u-em-default", "en-Zsye-US-u-em-default", "es-Zsye-419-u-em-default",
+
+            // Strings that contain the variant.
+            "de-Latn-DE-1901-u-em-default",
     };
 
     for (auto subtagDefaultString : subtagDefaultStrings) {
         SCOPED_TRACE("Test for \"" + subtagDefaultString + "\"");
-        FontLanguage subtagDefault = createFontLanguage(subtagDefaultString);
-        EXPECT_EQ(FontLanguage::EMSTYLE_DEFAULT, subtagDefault.getEmojiStyle());
+        Locale subtagDefault = createLocale(subtagDefaultString);
+        EXPECT_EQ(EmojiStyle::DEFAULT, subtagDefault.getEmojiStyle());
     }
 }
 
-TEST_F(FontLanguagesTest, subtagEmptyTest) {
+TEST(LocaleListTest, subtagEmptyTest) {
     std::string subtagEmptyStrings[] = {
-        "und",
-        "jp",
-        "en-US",
-        "en-Latn",
-        "en-Latn-US",
-        "en-Latn-US-u-em",
-        "en-Latn-US-u-em-defaultemoji",
+            "und",
+            "jp",
+            "en-US",
+            "en-Latn",
+            "en-Latn-US",
+            "en-Latn-US-u-em",
+            "en-Latn-US-u-em-defaultemoji",
+            "de-Latn-DE-1901",
     };
 
     for (auto subtagEmptyString : subtagEmptyStrings) {
         SCOPED_TRACE("Test for \"" + subtagEmptyString + "\"");
-        FontLanguage subtagEmpty = createFontLanguage(subtagEmptyString);
-        EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, subtagEmpty.getEmojiStyle());
+        Locale subtagEmpty = createLocale(subtagEmptyString);
+        EXPECT_EQ(EmojiStyle::EMPTY, subtagEmpty.getEmojiStyle());
     }
 }
 
-TEST_F(FontLanguagesTest, registerLanguageListTest) {
-    EXPECT_EQ(0UL, FontStyle::registerLanguageList(""));
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("en"));
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("jp"));
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("en,zh-Hans"));
+TEST(LocaleListTest, registerLocaleListTest) {
+    EXPECT_EQ(0UL, registerLocaleList(""));
+    EXPECT_NE(0UL, registerLocaleList("en"));
+    EXPECT_NE(0UL, registerLocaleList("jp"));
+    EXPECT_NE(0UL, registerLocaleList("en,zh-Hans"));
 
-    EXPECT_EQ(FontStyle::registerLanguageList("en"), FontStyle::registerLanguageList("en"));
-    EXPECT_NE(FontStyle::registerLanguageList("en"), FontStyle::registerLanguageList("jp"));
+    EXPECT_EQ(registerLocaleList("en"), registerLocaleList("en"));
+    EXPECT_NE(registerLocaleList("en"), registerLocaleList("jp"));
+    EXPECT_NE(registerLocaleList("de"), registerLocaleList("de-1901"));
 
-    EXPECT_EQ(FontStyle::registerLanguageList("en,zh-Hans"),
-              FontStyle::registerLanguageList("en,zh-Hans"));
-    EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
-              FontStyle::registerLanguageList("zh-Hans,en"));
-    EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
-              FontStyle::registerLanguageList("jp"));
-    EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
-              FontStyle::registerLanguageList("en"));
-    EXPECT_NE(FontStyle::registerLanguageList("en,zh-Hans"),
-              FontStyle::registerLanguageList("en,zh-Hant"));
+    EXPECT_EQ(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hans"));
+    EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("zh-Hans,en"));
+    EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("jp"));
+    EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en"));
+    EXPECT_NE(registerLocaleList("en,zh-Hans"), registerLocaleList("en,zh-Hant"));
+    EXPECT_NE(registerLocaleList("de,de-1901"), registerLocaleList("de-1901,de"));
 }
 
 // The test font has following glyphs.
@@ -501,13 +472,12 @@
 // U+717D U+FE02 (VS3)
 // U+717D U+E0102 (VS19)
 // U+717D U+E0103 (VS20)
-const char kVsTestFont[] = kTestFontDir "VariationSelectorTest-Regular.ttf";
+const char kVsTestFont[] = "VariationSelectorTest-Regular.ttf";
 
-class FontFamilyTest : public ICUTestBase {
+class FontFamilyTest : public testing::Test {
 public:
     virtual void SetUp() override {
-        ICUTestBase::SetUp();
-        if (access(kVsTestFont, R_OK) != 0) {
+        if (access(getTestFontPath(kVsTestFont).c_str(), R_OK) != 0) {
             FAIL() << "Unable to read " << kVsTestFont << ". "
                    << "Please prepare the test data directory. "
                    << "For more details, please see how_to_run.txt.";
@@ -530,14 +500,11 @@
             EXPECT_TRUE(family->hasGlyph(codepoint, i))
                     << "Glyph for U+" << std::hex << codepoint << " U+" << i;
         }
-
     }
 }
 
 TEST_F(FontFamilyTest, hasVariationSelectorTest) {
-    std::shared_ptr<MinikinFont> minikinFont(new MinikinFontForTest(kVsTestFont));
-    std::shared_ptr<FontFamily> family(
-            new FontFamily(std::vector<Font>{ Font(minikinFont, FontStyle()) }));
+    std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont);
 
     const uint32_t kVS1 = 0xFE00;
     const uint32_t kVS2 = 0xFE01;
@@ -573,41 +540,34 @@
         const std::string fontPath;
         bool hasVSTable;
     } testCases[] = {
-        { kTestFontDir "Ja.ttf", true },
-        { kTestFontDir "ZhHant.ttf", true },
-        { kTestFontDir "ZhHans.ttf", true },
-        { kTestFontDir "Italic.ttf", false },
-        { kTestFontDir "Bold.ttf", false },
-        { kTestFontDir "BoldItalic.ttf", false },
+            {"Ja.ttf", true},      {"ZhHant.ttf", true}, {"ZhHans.ttf", true},
+            {"Italic.ttf", false}, {"Bold.ttf", false},  {"BoldItalic.ttf", false},
     };
 
     for (auto testCase : testCases) {
-        SCOPED_TRACE(testCase.hasVSTable ?
-                "Font " + testCase.fontPath + " should have a variation sequence table." :
-                "Font " + testCase.fontPath + " shouldn't have a variation sequence table.");
+        SCOPED_TRACE(testCase.hasVSTable ? "Font " + testCase.fontPath +
+                                                   " should have a variation sequence table."
+                                         : "Font " + testCase.fontPath +
+                                                   " shouldn't have a variation sequence table.");
 
-        std::shared_ptr<MinikinFont> minikinFont(
-                new MinikinFontForTest(testCase.fontPath));
-        std::shared_ptr<FontFamily> family(new FontFamily(
-                std::vector<Font>{ Font(minikinFont, FontStyle()) }));
+        std::shared_ptr<FontFamily> family = buildFontFamily(testCase.fontPath);
         EXPECT_EQ(testCase.hasVSTable, family->hasVSTable());
     }
 }
 
 TEST_F(FontFamilyTest, createFamilyWithVariationTest) {
     // This font has 'wdth' and 'wght' axes.
-    const char kMultiAxisFont[] = kTestFontDir "/MultiAxis.ttf";
-    const char kNoAxisFont[] = kTestFontDir "/Regular.ttf";
+    const char kMultiAxisFont[] = "MultiAxis.ttf";
+    const char kNoAxisFont[] = "Regular.ttf";
 
-    std::shared_ptr<FontFamily> multiAxisFamily = makeFamily(kMultiAxisFont);
-    std::shared_ptr<FontFamily> noAxisFamily = makeFamily(kNoAxisFont);
+    std::shared_ptr<FontFamily> multiAxisFamily = buildFontFamily(kMultiAxisFont);
+    std::shared_ptr<FontFamily> noAxisFamily = buildFontFamily(kNoAxisFont);
 
     {
         // Do not ceate new instance if none of variations are specified.
         EXPECT_EQ(nullptr,
-                multiAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
-        EXPECT_EQ(nullptr,
-                noAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
+                  multiAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
+        EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(std::vector<FontVariation>()));
     }
     {
         // New instance should be used for supported variation.
@@ -620,10 +580,8 @@
     }
     {
         // New instance should be used for supported variation. (multiple variations case)
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
-                { MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+                                                 {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};
         std::shared_ptr<FontFamily> newFamily(
                 multiAxisFamily->createFamilyWithVariation(variations));
         EXPECT_NE(nullptr, newFamily.get());
@@ -632,18 +590,14 @@
     }
     {
         // Do not ceate new instance if none of variations are supported.
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
         EXPECT_EQ(nullptr, multiAxisFamily->createFamilyWithVariation(variations));
         EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));
     }
     {
         // At least one axis is supported, should create new instance.
-        std::vector<FontVariation> variations = {
-                { MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f },
-                { MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f }
-        };
+        std::vector<FontVariation> variations = {{MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},
+                                                 {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};
         std::shared_ptr<FontFamily> newFamily(
                 multiAxisFamily->createFamilyWithVariation(variations));
         EXPECT_NE(nullptr, newFamily.get());
@@ -655,22 +609,20 @@
 TEST_F(FontFamilyTest, coverageTableSelectionTest) {
     // This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
     // encoding ID is 1.
-    const char kUnicodeEncoding1Font[] = kTestFontDir "UnicodeBMPOnly.ttf";
+    const char kUnicodeEncoding1Font[] = "UnicodeBMPOnly.ttf";
 
     // This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
     // encoding ID is 3.
-    const char kUnicodeEncoding3Font[] = kTestFontDir "UnicodeBMPOnly2.ttf";
+    const char kUnicodeEncoding3Font[] = "UnicodeBMPOnly2.ttf";
 
     // This font has both cmap format 4 subtable which platform ID is 0 and encoding ID is 1
     // and cmap format 14 subtable which platform ID is 0 and encoding ID is 10.
     // U+0061 is listed in both subtable but U+1F926 is only listed in latter.
-    const char kUnicodeEncoding4Font[] = kTestFontDir "UnicodeUCS4.ttf";
+    const char kUnicodeEncoding4Font[] = "UnicodeUCS4.ttf";
 
-    std::shared_ptr<FontFamily> unicodeEnc1Font = makeFamily(kUnicodeEncoding1Font);
-    std::shared_ptr<FontFamily> unicodeEnc3Font = makeFamily(kUnicodeEncoding3Font);
-    std::shared_ptr<FontFamily> unicodeEnc4Font = makeFamily(kUnicodeEncoding4Font);
-
-    android::AutoMutex _l(gMinikinLock);
+    std::shared_ptr<FontFamily> unicodeEnc1Font = buildFontFamily(kUnicodeEncoding1Font);
+    std::shared_ptr<FontFamily> unicodeEnc3Font = buildFontFamily(kUnicodeEncoding3Font);
+    std::shared_ptr<FontFamily> unicodeEnc4Font = buildFontFamily(kUnicodeEncoding4Font);
 
     EXPECT_TRUE(unicodeEnc1Font->hasGlyph(0x0061, 0));
     EXPECT_TRUE(unicodeEnc3Font->hasGlyph(0x0061, 0));
@@ -679,4 +631,117 @@
     EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x1F926, 0));
 }
 
+const char* slantToString(FontStyle::Slant slant) {
+    if (slant == FontStyle::Slant::ITALIC) {
+        return "ITALIC";
+    } else {
+        return "UPRIGHT";
+    }
+}
+
+std::string fontStyleToString(const FontStyle& style) {
+    char buf[64] = {};
+    snprintf(buf, sizeof(buf), "FontStyle(weight=%d, slant=%s)", style.weight(),
+             slantToString(style.slant()));
+    return buf;
+}
+
+TEST_F(FontFamilyTest, closestMatch) {
+    constexpr char kTestFont[] = "Ascii.ttf";
+
+    constexpr FontStyle::Weight THIN = FontStyle::Weight::THIN;
+    constexpr FontStyle::Weight LIGHT = FontStyle::Weight::LIGHT;
+    constexpr FontStyle::Weight NORMAL = FontStyle::Weight::NORMAL;
+    constexpr FontStyle::Weight MEDIUM = FontStyle::Weight::MEDIUM;
+    constexpr FontStyle::Weight BOLD = FontStyle::Weight::BOLD;
+    constexpr FontStyle::Weight BLACK = FontStyle::Weight::BLACK;
+
+    constexpr FontStyle::Slant UPRIGHT = FontStyle::Slant::UPRIGHT;
+    constexpr FontStyle::Slant ITALIC = FontStyle::Slant::ITALIC;
+
+    const std::vector<FontStyle> STANDARD_SET = {
+            FontStyle(NORMAL, UPRIGHT),  // 0
+            FontStyle(BOLD, UPRIGHT),    // 1
+            FontStyle(NORMAL, ITALIC),   // 2
+            FontStyle(BOLD, ITALIC),     // 3
+    };
+
+    const std::vector<FontStyle> FULL_SET = {
+            FontStyle(THIN, UPRIGHT),    // 0
+            FontStyle(LIGHT, UPRIGHT),   // 1
+            FontStyle(NORMAL, UPRIGHT),  // 2
+            FontStyle(MEDIUM, UPRIGHT),  // 3
+            FontStyle(BOLD, UPRIGHT),    // 4
+            FontStyle(BLACK, UPRIGHT),   // 5
+            FontStyle(THIN, ITALIC),     // 6
+            FontStyle(LIGHT, ITALIC),    // 7
+            FontStyle(NORMAL, ITALIC),   // 8
+            FontStyle(MEDIUM, ITALIC),   // 9
+            FontStyle(BOLD, ITALIC),     // 10
+            FontStyle(BLACK, ITALIC),    // 11
+    };
+    struct TestCase {
+        FontStyle wantedStyle;
+        std::vector<FontStyle> familyStyles;
+        size_t expectedIndex;
+    } testCases[] = {
+            {FontStyle(), {FontStyle()}, 0},
+
+            // Exact matches
+            {FontStyle(BOLD), {FontStyle(NORMAL), FontStyle(BOLD)}, 1},
+            {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(BOLD)}, 1},
+            {FontStyle(LIGHT), {FontStyle(NORMAL), FontStyle(LIGHT)}, 1},
+            {FontStyle(LIGHT), {FontStyle(BOLD), FontStyle(LIGHT)}, 1},
+            {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(LIGHT)}, 0},
+            {FontStyle(NORMAL), {FontStyle(NORMAL), FontStyle(BOLD)}, 0},
+            {FontStyle(LIGHT), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 0},
+            {FontStyle(NORMAL), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 1},
+            {FontStyle(BOLD), {FontStyle(LIGHT), FontStyle(NORMAL), FontStyle(BOLD)}, 2},
+
+            {FontStyle(UPRIGHT), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 0},
+            {FontStyle(ITALIC), {FontStyle(UPRIGHT), FontStyle(ITALIC)}, 1},
+
+            {FontStyle(NORMAL, UPRIGHT), STANDARD_SET, 0},
+            {FontStyle(BOLD, UPRIGHT), STANDARD_SET, 1},
+            {FontStyle(NORMAL, ITALIC), STANDARD_SET, 2},
+            {FontStyle(BOLD, ITALIC), STANDARD_SET, 3},
+
+            {FontStyle(NORMAL, UPRIGHT), FULL_SET, 2},
+            {FontStyle(BOLD, UPRIGHT), FULL_SET, 4},
+            {FontStyle(NORMAL, ITALIC), FULL_SET, 8},
+            {FontStyle(BOLD, ITALIC), FULL_SET, 10},
+
+            // TODO: Add fallback expectations. (b/68814338)
+    };
+
+    for (const TestCase& testCase : testCases) {
+        std::vector<std::shared_ptr<MinikinFont>> dummyFonts;
+        std::vector<Font> fonts;
+        for (auto familyStyle : testCase.familyStyles) {
+            std::shared_ptr<MinikinFont> dummyFont(
+                    new FreeTypeMinikinFontForTest(getTestFontPath(kTestFont)));
+            dummyFonts.push_back(dummyFont);
+            fonts.push_back(Font::Builder(dummyFont).setStyle(familyStyle).build());
+        }
+
+        FontFamily family(std::move(fonts));
+        FakedFont closest = family.getClosestMatch(testCase.wantedStyle);
+
+        size_t idx = dummyFonts.size();
+        for (size_t i = 0; i < dummyFonts.size(); i++) {
+            if (dummyFonts[i].get() == closest.font->typeface().get()) {
+                idx = i;
+                break;
+            }
+        }
+        ASSERT_NE(idx, dummyFonts.size()) << "The selected font is unknown.";
+        EXPECT_EQ(testCase.expectedIndex, idx)
+                << "Input Style: " << fontStyleToString(testCase.wantedStyle) << std::endl
+                << "Actual Families' Style: " << fontStyleToString(testCase.familyStyles[idx])
+                << std::endl
+                << "Expected Families' Style: "
+                << fontStyleToString(testCase.familyStyles[testCase.expectedIndex]) << std::endl;
+    }
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/FontLanguageListCacheTest.cpp b/tests/unittest/FontLanguageListCacheTest.cpp
index 81d84a8..e957cfc 100644
--- a/tests/unittest/FontLanguageListCacheTest.cpp
+++ b/tests/unittest/FontLanguageListCacheTest.cpp
@@ -14,60 +14,51 @@
  * limitations under the License.
  */
 
+#include "minikin/FontFamily.h"
+
 #include <gtest/gtest.h>
 
-#include <minikin/FontFamily.h>
+#include "minikin/LocaleList.h"
 
-#include "FontLanguageListCache.h"
-#include "ICUTestBase.h"
+#include "LocaleListCache.h"
 #include "MinikinInternal.h"
 
 namespace minikin {
 
-typedef ICUTestBase FontLanguageListCacheTest;
+TEST(LocaleListCacheTest, getId) {
+    EXPECT_NE(0UL, registerLocaleList("en"));
+    EXPECT_NE(0UL, registerLocaleList("jp"));
+    EXPECT_NE(0UL, registerLocaleList("en,zh-Hans"));
 
-TEST_F(FontLanguageListCacheTest, getId) {
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("en"));
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("jp"));
-    EXPECT_NE(0UL, FontStyle::registerLanguageList("en,zh-Hans"));
+    EXPECT_EQ(0UL, LocaleListCache::getId(""));
 
-    android::AutoMutex _l(gMinikinLock);
-    EXPECT_EQ(0UL, FontLanguageListCache::getId(""));
+    EXPECT_EQ(LocaleListCache::getId("en"), LocaleListCache::getId("en"));
+    EXPECT_NE(LocaleListCache::getId("en"), LocaleListCache::getId("jp"));
 
-    EXPECT_EQ(FontLanguageListCache::getId("en"), FontLanguageListCache::getId("en"));
-    EXPECT_NE(FontLanguageListCache::getId("en"), FontLanguageListCache::getId("jp"));
-
-    EXPECT_EQ(FontLanguageListCache::getId("en,zh-Hans"),
-              FontLanguageListCache::getId("en,zh-Hans"));
-    EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
-              FontLanguageListCache::getId("zh-Hans,en"));
-    EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
-              FontLanguageListCache::getId("jp"));
-    EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
-              FontLanguageListCache::getId("en"));
-    EXPECT_NE(FontLanguageListCache::getId("en,zh-Hans"),
-              FontLanguageListCache::getId("en,zh-Hant"));
+    EXPECT_EQ(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en,zh-Hans"));
+    EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("zh-Hans,en"));
+    EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("jp"));
+    EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en"));
+    EXPECT_NE(LocaleListCache::getId("en,zh-Hans"), LocaleListCache::getId("en,zh-Hant"));
 }
 
-TEST_F(FontLanguageListCacheTest, getById) {
-    android::AutoMutex _l(gMinikinLock);
-    uint32_t enLangId = FontLanguageListCache::getId("en");
-    uint32_t jpLangId = FontLanguageListCache::getId("jp");
-    FontLanguage english = FontLanguageListCache::getById(enLangId)[0];
-    FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];
+TEST(LocaleListCacheTest, getById) {
+    uint32_t enLangId = LocaleListCache::getId("en");
+    uint32_t jpLangId = LocaleListCache::getId("jp");
+    Locale english = LocaleListCache::getById(enLangId)[0];
+    Locale japanese = LocaleListCache::getById(jpLangId)[0];
 
-    const FontLanguages& defLangs = FontLanguageListCache::getById(0);
+    const LocaleList& defLangs = LocaleListCache::getById(0);
     EXPECT_TRUE(defLangs.empty());
 
-    const FontLanguages& langs = FontLanguageListCache::getById(FontLanguageListCache::getId("en"));
-    ASSERT_EQ(1UL, langs.size());
-    EXPECT_EQ(english, langs[0]);
+    const LocaleList& locales = LocaleListCache::getById(LocaleListCache::getId("en"));
+    ASSERT_EQ(1UL, locales.size());
+    EXPECT_EQ(english, locales[0]);
 
-    const FontLanguages& langs2 =
-            FontLanguageListCache::getById(FontLanguageListCache::getId("en,jp"));
-    ASSERT_EQ(2UL, langs2.size());
-    EXPECT_EQ(english, langs2[0]);
-    EXPECT_EQ(japanese, langs2[1]);
+    const LocaleList& locales2 = LocaleListCache::getById(LocaleListCache::getId("en,jp"));
+    ASSERT_EQ(2UL, locales2.size());
+    EXPECT_EQ(english, locales2[0]);
+    EXPECT_EQ(japanese, locales2[1]);
 }
 
 }  // namespace minikin
diff --git a/tests/unittest/GraphemeBreakTests.cpp b/tests/unittest/GraphemeBreakTests.cpp
index 6720df6..362ae10 100644
--- a/tests/unittest/GraphemeBreakTests.cpp
+++ b/tests/unittest/GraphemeBreakTests.cpp
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
+#include "minikin/GraphemeBreak.h"
+
+#include <vector>
+
 #include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-#include <minikin/GraphemeBreak.h>
+
+#include "UnicodeUtils.h"
 
 namespace minikin {
 
@@ -44,10 +48,10 @@
     // tests for invalid UTF-16
     EXPECT_TRUE(IsBreak("U+D800 | U+D800"));  // two leading surrogates
     EXPECT_TRUE(IsBreak("U+DC00 | U+DC00"));  // two trailing surrogates
-    EXPECT_TRUE(IsBreak("'a' | U+D800"));  // lonely leading surrogate
-    EXPECT_TRUE(IsBreak("U+DC00 | 'a'"));  // lonely trailing surrogate
-    EXPECT_TRUE(IsBreak("U+D800 | 'a'"));  // leading surrogate followed by non-surrogate
-    EXPECT_TRUE(IsBreak("'a' | U+DC00"));  // non-surrogate followed by trailing surrogate
+    EXPECT_TRUE(IsBreak("'a' | U+D800"));     // lonely leading surrogate
+    EXPECT_TRUE(IsBreak("U+DC00 | 'a'"));     // lonely trailing surrogate
+    EXPECT_TRUE(IsBreak("U+D800 | 'a'"));     // leading surrogate followed by non-surrogate
+    EXPECT_TRUE(IsBreak("'a' | U+DC00"));     // non-surrogate followed by trailing surrogate
 }
 
 TEST(GraphemeBreak, rules) {
@@ -93,15 +97,15 @@
 
     // Rule GB12 and Rule GB13, Regional_Indicator x Regional_Indicator
     EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8"));
-    EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
-    EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8")); // Regional indicator pair (flag)
-    EXPECT_FALSE(IsBreak("U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8")); // Regional indicator pair (flag)
+    EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8"));   // Regional indicator pair (flag)
+    EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8"));  // Regional indicator pair (flag)
+    EXPECT_FALSE(IsBreak("U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8"));  // Regional indicator pair (flag)
 
-    EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA"));  // Regional indicator pair (flag)
+    EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | U+1F1FA"));   // Regional indicator pair (flag)
     EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8 U+1F1FA"));  // Regional indicator pair (flag)
     // Same case as the two above, knowing that the first two characters ligate, which is what
     // would typically happen.
-    const float firstPairLigated[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0}; // Two entries per codepoint
+    const float firstPairLigated[] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0};  // Two entries per codepoint
     EXPECT_TRUE(IsBreakWithAdvances(firstPairLigated, "U+1F1FA U+1F1F8 | U+1F1FA"));
     EXPECT_FALSE(IsBreakWithAdvances(firstPairLigated, "U+1F1FA | U+1F1F8 U+1F1FA"));
     // Repeat the tests, But now the font doesn't have a ligature for the first two characters,
@@ -111,7 +115,7 @@
     EXPECT_FALSE(IsBreakWithAdvances(secondPairLigated, "U+1F1FA U+1F1F8 | U+1F1FA"));
     EXPECT_TRUE(IsBreakWithAdvances(secondPairLigated, "U+1F1FA | U+1F1F8 U+1F1FA"));
 
-    EXPECT_TRUE(IsBreak("'a' U+1F1FA U+1F1F8 | U+1F1FA"));  // Regional indicator pair (flag)
+    EXPECT_TRUE(IsBreak("'a' U+1F1FA U+1F1F8 | U+1F1FA"));   // Regional indicator pair (flag)
     EXPECT_FALSE(IsBreak("'a' U+1F1FA | U+1F1F8 U+1F1FA"));  // Regional indicator pair (flag)
 
     EXPECT_TRUE(
@@ -131,9 +135,9 @@
 
     // Rule GB999, Any ÷ Any
     EXPECT_TRUE(IsBreak("'a' | 'b'"));
-    EXPECT_TRUE(IsBreak("'f' | 'i'"));  // probable ligature
-    EXPECT_TRUE(IsBreak("U+0644 | U+0627"));  // probable ligature, lam + alef
-    EXPECT_TRUE(IsBreak("U+4E00 | U+4E00"));  // CJK ideographs
+    EXPECT_TRUE(IsBreak("'f' | 'i'"));              // probable ligature
+    EXPECT_TRUE(IsBreak("U+0644 | U+0627"));        // probable ligature, lam + alef
+    EXPECT_TRUE(IsBreak("U+4E00 | U+4E00"));        // CJK ideographs
     EXPECT_TRUE(IsBreak("'a' | U+1F1FA U+1F1F8"));  // Regional indicator pair (flag)
     EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | 'a'"));  // Regional indicator pair (flag)
 
@@ -168,10 +172,10 @@
 
 TEST(GraphemeBreak, tailoring) {
     // control characters that we interpret as "extend"
-    EXPECT_FALSE(IsBreak("'a' | U+00AD"));  // soft hyphen
-    EXPECT_FALSE(IsBreak("'a' | U+200B"));  // zwsp
-    EXPECT_FALSE(IsBreak("'a' | U+200E"));  // lrm
-    EXPECT_FALSE(IsBreak("'a' | U+202A"));  // lre
+    EXPECT_FALSE(IsBreak("'a' | U+00AD"));   // soft hyphen
+    EXPECT_FALSE(IsBreak("'a' | U+200B"));   // zwsp
+    EXPECT_FALSE(IsBreak("'a' | U+200E"));   // lrm
+    EXPECT_FALSE(IsBreak("'a' | U+202A"));   // lre
     EXPECT_FALSE(IsBreak("'a' | U+E0041"));  // tag character
 
     // UTC-approved characters for the Prepend class
@@ -183,32 +187,32 @@
     EXPECT_FALSE(IsBreak("U+0915 | U+094D U+0915"));  // Devanagari ka+virama+ka
     EXPECT_FALSE(IsBreak("U+0915 U+094D | U+0915"));  // Devanagari ka+virama+ka
     EXPECT_FALSE(IsBreak("U+0E01 | U+0E3A U+0E01"));  // thai phinthu = pure killer
-    EXPECT_TRUE(IsBreak("U+0E01 U+0E3A | U+0E01"));  // thai phinthu = pure killer
+    EXPECT_TRUE(IsBreak("U+0E01 U+0E3A | U+0E01"));   // thai phinthu = pure killer
 
     // Repetition of above tests, but with a given advances array that implies everything
     // became just one cluster.
     const float conjoined[] = {1.0, 0.0, 0.0};
     EXPECT_FALSE(IsBreakWithAdvances(conjoined,
-            "U+0915 | U+094D U+0915"));  // Devanagari ka+virama+ka
+                                     "U+0915 | U+094D U+0915"));  // Devanagari ka+virama+ka
     EXPECT_FALSE(IsBreakWithAdvances(conjoined,
-            "U+0915 U+094D | U+0915"));  // Devanagari ka+virama+ka
+                                     "U+0915 U+094D | U+0915"));  // Devanagari ka+virama+ka
     EXPECT_FALSE(IsBreakWithAdvances(conjoined,
-            "U+0E01 | U+0E3A U+0E01"));  // thai phinthu = pure killer
+                                     "U+0E01 | U+0E3A U+0E01"));  // thai phinthu = pure killer
     EXPECT_TRUE(IsBreakWithAdvances(conjoined,
-            "U+0E01 U+0E3A | U+0E01"));  // thai phinthu = pure killer
+                                    "U+0E01 U+0E3A | U+0E01"));  // thai phinthu = pure killer
 
     // Repetition of above tests, but with a given advances array that the virama did not
     // form a cluster with the following consonant. The difference is that there is now
     // a grapheme break after the virama in ka+virama+ka.
     const float separate[] = {1.0, 0.0, 1.0};
     EXPECT_FALSE(IsBreakWithAdvances(separate,
-            "U+0915 | U+094D U+0915"));  // Devanagari ka+virama+ka
+                                     "U+0915 | U+094D U+0915"));  // Devanagari ka+virama+ka
     EXPECT_TRUE(IsBreakWithAdvances(separate,
-            "U+0915 U+094D | U+0915"));  // Devanagari ka+virama+ka
+                                    "U+0915 U+094D | U+0915"));  // Devanagari ka+virama+ka
     EXPECT_FALSE(IsBreakWithAdvances(separate,
-            "U+0E01 | U+0E3A U+0E01"));  // thai phinthu = pure killer
+                                     "U+0E01 | U+0E3A U+0E01"));  // thai phinthu = pure killer
     EXPECT_TRUE(IsBreakWithAdvances(separate,
-            "U+0E01 U+0E3A | U+0E01"));  // thai phinthu = pure killer
+                                    "U+0E01 U+0E3A | U+0E01"));  // thai phinthu = pure killer
 
     // suppress grapheme breaks in zwj emoji sequences
     EXPECT_FALSE(IsBreak("U+1F469 U+200D | U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468"));
@@ -230,8 +234,8 @@
 }
 
 TEST(GraphemeBreak, emojiModifiers) {
-    EXPECT_FALSE(IsBreak("U+261D | U+1F3FB"));  // white up pointing index + modifier
-    EXPECT_FALSE(IsBreak("U+270C | U+1F3FB"));  // victory hand + modifier
+    EXPECT_FALSE(IsBreak("U+261D | U+1F3FB"));   // white up pointing index + modifier
+    EXPECT_FALSE(IsBreak("U+270C | U+1F3FB"));   // victory hand + modifier
     EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FB"));  // boy + modifier
     EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FC"));  // boy + modifier
     EXPECT_FALSE(IsBreak("U+1F466 | U+1F3FD"));  // boy + modifier
@@ -277,10 +281,10 @@
     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+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
+    EXPECT_TRUE(IsBreak("U+1F3FB | U+1F3FB"));        // modifier + modifier
 
     // rat is not an emoji modifer
     EXPECT_TRUE(IsBreak("U+1F466 | U+1F400"));  // boy + rat
@@ -307,7 +311,7 @@
 }
 
 TEST(GraphemeBreak, offsets) {
-    uint16_t string[] = { 0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301 };
+    uint16_t string[] = {0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301};
     EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 2));
     EXPECT_FALSE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 3));
     EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 4));
diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp
new file mode 100644
index 0000000..ad5824d
--- /dev/null
+++ b/tests/unittest/GreedyLineBreakerTest.cpp
@@ -0,0 +1,842 @@
+/*
+ * Copyright (C) 2017 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 <memory>
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+
+#include "FileUtils.h"
+#include "FontTestUtils.h"
+#include "GreedyLineBreaker.h"
+#include "HyphenatorMap.h"
+#include "LineBreakerTestHelper.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+namespace {
+
+using line_breaker_test_helper::ConstantRun;
+using line_breaker_test_helper::LineBreakExpectation;
+using line_breaker_test_helper::RectangleLineWidth;
+using line_breaker_test_helper::sameLineBreak;
+using line_breaker_test_helper::toString;
+
+class GreedyLineBreakerTest : public testing::Test {
+public:
+    GreedyLineBreakerTest() {}
+
+    virtual ~GreedyLineBreakerTest() {}
+
+    virtual void SetUp() override {
+        mHyphenationPattern = readWholeFile("/system/usr/hyphen-data/hyph-en-us.hyb");
+        Hyphenator* hyphenator = Hyphenator::loadBinary(
+                mHyphenationPattern.data(), 2 /* min prefix */, 2 /* min suffix */, "en-US");
+        HyphenatorMap::add("en-US", hyphenator);
+        HyphenatorMap::add("pl", Hyphenator::loadBinary(nullptr, 0, 0, "pl"));
+    }
+
+    virtual void TearDown() override { HyphenatorMap::clear(); }
+
+protected:
+    LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
+                                float charWidth, float lineWidth) {
+        return doLineBreak(textBuffer, doHyphenation, charWidth, "en-US", lineWidth);
+    }
+
+    LineBreakResult doLineBreak(const U16StringPiece& textBuffer, bool doHyphenation,
+                                float charWidth, 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 */);
+        RectangleLineWidth rectangleLineWidth(lineWidth);
+        TabStops tabStops(nullptr, 0, 10);
+        return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops,
+                               doHyphenation);
+    }
+
+private:
+    std::vector<uint8_t> mHyphenationPattern;
+};
+
+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.");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    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;
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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.");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+    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;
+        std::vector<LineBreakExpectation> expect = {
+                {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 27 * CHAR_WIDTH;
+        std::vector<LineBreakExpectation> expect = {
+                {"Hyphenation is hyphenation.", 27 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 26 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 10 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, NO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    constexpr StartHyphenEdit START_HYPHEN = StartHyphenEdit::INSERT_HYPHEN;
+
+    // Note that disable clang-format everywhere since aligned expectation is more readable.
+    {
+        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        std::vector<LineBreakExpectation> expect = {
+                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "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;
+        std::vector<LineBreakExpectation> expect = {
+                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "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;
+        // 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},
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, "pl", LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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 StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    {
+        const auto textBuf = utf8ToUtf16("");
+        std::vector<LineBreakExpectation> expect = {};
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        const auto textBuf = utf8ToUtf16("A");
+        std::vector<LineBreakExpectation> expect = {
+                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        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},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testZeroWidthCharacter) {
+    constexpr float CHAR_WIDTH = 0.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 = 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},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        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},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testLocaleSwitchTest) {
+    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;
+    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},
+        };
+
+        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 */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 0);
+
+        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);
+    }
+    {
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        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 */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 0);
+
+        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);
+    }
+}
+
+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;
+        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},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        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},
+        };
+        const auto actual = doLineBreak(textBuf, DO_HYPHEN, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(GreedyLineBreakerTest, testLocaleSwitch_InEmailOrUrl) {
+    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;
+    {
+        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},
+        };
+
+        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 */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 0);
+
+        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);
+    }
+    {
+        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},
+        };
+
+        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 */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, 0);
+
+        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);
+    }
+}
+
+// b/68669534
+TEST_F(GreedyLineBreakerTest, CrashFix_Space_Tab) {
+    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 = 5 * CHAR_WIDTH;
+        const auto textBuf = utf8ToUtf16("a \tb");
+        std::vector<LineBreakExpectation> expect = {
+                {"a \tb", 4 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        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 */);
+        RectangleLineWidth rectangleLineWidth(LINE_WIDTH);
+        TabStops tabStops(nullptr, 0, CHAR_WIDTH);
+
+        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);
+    }
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/unittest/HbFontCacheTest.cpp b/tests/unittest/HbFontCacheTest.cpp
deleted file mode 100644
index a5581a2..0000000
--- a/tests/unittest/HbFontCacheTest.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2016 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 "HbFontCache.h"
-
-#include <android/log.h>
-#include <gtest/gtest.h>
-#include <utils/Mutex.h>
-
-#include <memory>
-
-#include <hb.h>
-
-#include "MinikinInternal.h"
-#include "MinikinFontForTest.h"
-#include <minikin/MinikinFont.h>
-
-namespace minikin {
-
-class HbFontCacheTest : public testing::Test {
-public:
-    virtual void TearDown() {
-        android::AutoMutex _l(gMinikinLock);
-        purgeHbFontCacheLocked();
-    }
-};
-
-TEST_F(HbFontCacheTest, getHbFontLockedTest) {
-    std::shared_ptr<MinikinFontForTest> fontA(
-            new MinikinFontForTest(kTestFontDir "Regular.ttf"));
-
-    std::shared_ptr<MinikinFontForTest> fontB(
-            new MinikinFontForTest(kTestFontDir "Bold.ttf"));
-
-    std::shared_ptr<MinikinFontForTest> fontC(
-            new MinikinFontForTest(kTestFontDir "BoldItalic.ttf"));
-
-    android::AutoMutex _l(gMinikinLock);
-    // Never return NULL.
-    EXPECT_NE(nullptr, getHbFontLocked(fontA.get()));
-    EXPECT_NE(nullptr, getHbFontLocked(fontB.get()));
-    EXPECT_NE(nullptr, getHbFontLocked(fontC.get()));
-
-    EXPECT_NE(nullptr, getHbFontLocked(nullptr));
-
-    // Must return same object if same font object is passed.
-    EXPECT_EQ(getHbFontLocked(fontA.get()), getHbFontLocked(fontA.get()));
-    EXPECT_EQ(getHbFontLocked(fontB.get()), getHbFontLocked(fontB.get()));
-    EXPECT_EQ(getHbFontLocked(fontC.get()), getHbFontLocked(fontC.get()));
-
-    // Different object must be returned if the passed minikinFont has different ID.
-    EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontB.get()));
-    EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontC.get()));
-}
-
-TEST_F(HbFontCacheTest, purgeCacheTest) {
-    std::shared_ptr<MinikinFontForTest> minikinFont(
-            new MinikinFontForTest(kTestFontDir "Regular.ttf"));
-
-    android::AutoMutex _l(gMinikinLock);
-    hb_font_t* font = getHbFontLocked(minikinFont.get());
-    ASSERT_NE(nullptr, font);
-
-    // Set user data to identify the font object.
-    hb_user_data_key_t key;
-    void* data = (void*)0xdeadbeef;
-    hb_font_set_user_data(font, &key, data, NULL, false);
-    ASSERT_EQ(data, hb_font_get_user_data(font, &key));
-
-    purgeHbFontCacheLocked();
-
-    // By checking user data, confirm that the object after purge is different from previously
-    // created one. Do not compare the returned pointer here since memory allocator may assign
-    // same region for new object.
-    font = getHbFontLocked(minikinFont.get());
-    EXPECT_EQ(nullptr, hb_font_get_user_data(font, &key));
-}
-
-}  // namespace minikin
diff --git a/tests/unittest/HyphenatorMapTest.cpp b/tests/unittest/HyphenatorMapTest.cpp
new file mode 100644
index 0000000..b793fe0
--- /dev/null
+++ b/tests/unittest/HyphenatorMapTest.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 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 "HyphenatorMap.h"
+
+#include <gtest/gtest.h>
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+namespace {
+
+// Constants used for testing. The address does not need a valid one.
+const Hyphenator* FAKE_ADDRESS = reinterpret_cast<const Hyphenator*>(1);
+const Hyphenator* AS_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* BG_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* BN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* CU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* CY_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_1901_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_1996_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* DE_CH_1901_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EN_GB_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EN_US_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ES_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ET_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* EU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* FR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* GA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* GU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HI_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HU_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* HY_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* KN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* ML_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* MN_CYRL_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* MR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* NB_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* NN_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* OR_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* PA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* PT_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* SL_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TA_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TE_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* TK_HYPHENATOR = FAKE_ADDRESS++;
+const Hyphenator* UND_ETHI_HYPHENATOR = FAKE_ADDRESS++;
+
+class TestableHyphenatorMap : public HyphenatorMap {
+public:
+    TestableHyphenatorMap() : HyphenatorMap() {}
+
+    using HyphenatorMap::addAliasInternal;
+    using HyphenatorMap::addInternal;
+    using HyphenatorMap::lookupInternal;
+};
+
+class HyphenatorMapTest : public testing::Test {
+protected:
+    virtual void SetUp() override {
+        // Following settings are copied from Hyphenator.java.
+        mMap.addInternal("as", AS_HYPHENATOR);
+        mMap.addInternal("bg", BG_HYPHENATOR);
+        mMap.addInternal("bn", BN_HYPHENATOR);
+        mMap.addInternal("cu", CU_HYPHENATOR);
+        mMap.addInternal("cy", CY_HYPHENATOR);
+        mMap.addInternal("da", DA_HYPHENATOR);
+        mMap.addInternal("de-1901", DE_1901_HYPHENATOR);
+        mMap.addInternal("de-1996", DE_1996_HYPHENATOR);
+        mMap.addInternal("de-CH-1901", DE_CH_1901_HYPHENATOR);
+        mMap.addInternal("en-GB", EN_GB_HYPHENATOR);
+        mMap.addInternal("en-US", EN_US_HYPHENATOR);
+        mMap.addInternal("es", ES_HYPHENATOR);
+        mMap.addInternal("et", ET_HYPHENATOR);
+        mMap.addInternal("eu", EU_HYPHENATOR);
+        mMap.addInternal("fr", FR_HYPHENATOR);
+        mMap.addInternal("ga", GA_HYPHENATOR);
+        mMap.addInternal("gu", GU_HYPHENATOR);
+        mMap.addInternal("hi", HI_HYPHENATOR);
+        mMap.addInternal("hr", HR_HYPHENATOR);
+        mMap.addInternal("hu", HU_HYPHENATOR);
+        mMap.addInternal("hy", HY_HYPHENATOR);
+        mMap.addInternal("kn", KN_HYPHENATOR);
+        mMap.addInternal("ml", ML_HYPHENATOR);
+        mMap.addInternal("mn-Cyrl", MN_CYRL_HYPHENATOR);
+        mMap.addInternal("mr", MR_HYPHENATOR);
+        mMap.addInternal("nb", NB_HYPHENATOR);
+        mMap.addInternal("nn", NN_HYPHENATOR);
+        mMap.addInternal("or", OR_HYPHENATOR);
+        mMap.addInternal("pa", PA_HYPHENATOR);
+        mMap.addInternal("pt", PT_HYPHENATOR);
+        mMap.addInternal("sl", SL_HYPHENATOR);
+        mMap.addInternal("ta", TA_HYPHENATOR);
+        mMap.addInternal("te", TE_HYPHENATOR);
+        mMap.addInternal("tk", TK_HYPHENATOR);
+        mMap.addInternal("und-Ethi", UND_ETHI_HYPHENATOR);
+
+        mMap.addAliasInternal("en", "en-GB");
+        mMap.addAliasInternal("en-AS", "en-US");
+        mMap.addAliasInternal("en-GU", "en-US");
+        mMap.addAliasInternal("en-MH", "en-US");
+        mMap.addAliasInternal("en-MP", "en-US");
+        mMap.addAliasInternal("en-PR", "en-US");
+        mMap.addAliasInternal("en-UM", "en-US");
+        mMap.addAliasInternal("en-VI", "en-US");
+        mMap.addAliasInternal("de-LI-1901", "de-CH-1901");
+        mMap.addAliasInternal("de", "de-1996");
+        mMap.addAliasInternal("no", "nb");
+        mMap.addAliasInternal("mn", "mn-Cyrl");
+        // am for und-Ethi is removed for testing purposes.
+        mMap.addAliasInternal("byn", "und-Ethi");
+        mMap.addAliasInternal("gez", "und-Ethi");
+        mMap.addAliasInternal("ti", "und-Ethi");
+        mMap.addAliasInternal("wal", "und-Ethi");
+    }
+
+    const Locale& getLocale(const std::string& localeStr) {
+        // In production, we reconstruct the LocaleList from the locale list ID.
+        // So, do it here too.
+        const uint32_t id = LocaleListCache::getId(localeStr);
+        const LocaleList& locales = LocaleListCache::getById(id);
+        MINIKIN_ASSERT(locales.size() == 1, "The input must be a single locale");
+        return locales[0];
+    }
+
+    const Hyphenator* lookup(const std::string& localeStr) {
+        return mMap.lookupInternal(getLocale(localeStr));
+    }
+
+private:
+    TestableHyphenatorMap mMap;
+};
+
+TEST_F(HyphenatorMapTest, exactMatch) {
+    EXPECT_EQ(AS_HYPHENATOR, lookup("as"));
+    EXPECT_EQ(BG_HYPHENATOR, lookup("bg"));
+    EXPECT_EQ(BN_HYPHENATOR, lookup("bn"));
+    EXPECT_EQ(CU_HYPHENATOR, lookup("cu"));
+    EXPECT_EQ(CY_HYPHENATOR, lookup("cy"));
+    EXPECT_EQ(DA_HYPHENATOR, lookup("da"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-1901"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-1996"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-CH-1901"));
+    EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-GB"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-US"));
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es"));
+    EXPECT_EQ(ET_HYPHENATOR, lookup("et"));
+    EXPECT_EQ(EU_HYPHENATOR, lookup("eu"));
+    EXPECT_EQ(FR_HYPHENATOR, lookup("fr"));
+    EXPECT_EQ(GA_HYPHENATOR, lookup("ga"));
+    EXPECT_EQ(GU_HYPHENATOR, lookup("gu"));
+    EXPECT_EQ(HI_HYPHENATOR, lookup("hi"));
+    EXPECT_EQ(HR_HYPHENATOR, lookup("hr"));
+    EXPECT_EQ(HU_HYPHENATOR, lookup("hu"));
+    EXPECT_EQ(HY_HYPHENATOR, lookup("hy"));
+    EXPECT_EQ(KN_HYPHENATOR, lookup("kn"));
+    EXPECT_EQ(ML_HYPHENATOR, lookup("ml"));
+    EXPECT_EQ(MN_CYRL_HYPHENATOR, lookup("mn-Cyrl"));
+    EXPECT_EQ(MR_HYPHENATOR, lookup("mr"));
+    EXPECT_EQ(NB_HYPHENATOR, lookup("nb"));
+    EXPECT_EQ(NN_HYPHENATOR, lookup("nn"));
+    EXPECT_EQ(OR_HYPHENATOR, lookup("or"));
+    EXPECT_EQ(PA_HYPHENATOR, lookup("pa"));
+    EXPECT_EQ(PT_HYPHENATOR, lookup("pt"));
+    EXPECT_EQ(SL_HYPHENATOR, lookup("sl"));
+    EXPECT_EQ(TA_HYPHENATOR, lookup("ta"));
+    EXPECT_EQ(TE_HYPHENATOR, lookup("te"));
+    EXPECT_EQ(TK_HYPHENATOR, lookup("tk"));
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("und-Ethi"));
+}
+
+TEST_F(HyphenatorMapTest, aliasMatch) {
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-AS"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-GU"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-MH"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-MP"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-PR"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-UM"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-VI"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-LI-1901"));
+    EXPECT_EQ(NB_HYPHENATOR, lookup("no"));
+    EXPECT_EQ(MN_CYRL_HYPHENATOR, lookup("mn"));
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("byn"));
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("gez"));
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("ti"));
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("wal"));
+    // Amharic is tested in fallbackTest_scriptFallback
+}
+
+TEST_F(HyphenatorMapTest, IgnoreScript) {
+    // Script should be ignored until the final script-only matching rule.
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Latn-US"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Zsye-US"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Zsym-US"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Jpan-US"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Hans-US"));
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en-Ethi-US"));
+
+    EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-Zsye-AU"));
+    EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-Zsye-GB"));
+}
+
+TEST_F(HyphenatorMapTest, languageFallback) {
+    EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-AU"));
+    EXPECT_EQ(EN_GB_HYPHENATOR, lookup("en-NZ"));
+
+    // "en" is expanded to en-Latn-US. So this is equivalent to "en-Latn-US" test case.
+    // This expansion also happens in production.
+    EXPECT_EQ(EN_US_HYPHENATOR, lookup("en"));
+}
+
+TEST_F(HyphenatorMapTest, GermanFallback) {
+    // German in general
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-1901"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-1996"));
+
+    // German in Germany
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-DE"));
+
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-DE-1901"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-DE-1901"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-DE-1901-u-em-emoji"));
+
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-DE-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-DE-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-DE-1996-u-em-emoji"));
+
+    // German in Austria
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-AT"));
+
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-AT-1901"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-AT-1901"));
+    EXPECT_EQ(DE_1901_HYPHENATOR, lookup("de-Latn-AT-1901-u-em-emoji"));
+
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-AT-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-AT-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-AT-1996-u-em-emoji"));
+
+    // German in Switzerland
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-CH"));
+
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-CH-1901"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-CH-1901"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-CH-1901-u-em-emoji"));
+
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-CH-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-CH-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-CH-1996-u-em-emoji"));
+
+    // German in Liechtenstein
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-LI"));
+
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-LI-1901"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-LI-1901"));
+    EXPECT_EQ(DE_CH_1901_HYPHENATOR, lookup("de-Latn-LI-1901-u-em-emoji"));
+
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-LI-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-LI-1996"));
+    EXPECT_EQ(DE_1996_HYPHENATOR, lookup("de-Latn-LI-1996-u-em-emoji"));
+}
+
+TEST_F(HyphenatorMapTest, fallbackTest_LanguageFallback) {
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es-ES"));
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es-AR"));
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es-BO"));
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es-CL"));
+
+    // Spanish in Great Britain
+    EXPECT_EQ(ES_HYPHENATOR, lookup("es-GB"));
+}
+
+TEST_F(HyphenatorMapTest, fallbackTest_ScriptFallback) {
+    EXPECT_EQ(UND_ETHI_HYPHENATOR, lookup("am"));
+}
+
+TEST_F(HyphenatorMapTest, neverReturnNullptrTest) {
+    EXPECT_NE(nullptr, lookup("und"));
+    EXPECT_NE(nullptr, lookup("ja"));
+    EXPECT_NE(nullptr, lookup("ja-JP"));
+}
+
+TEST_F(HyphenatorMapTest, CyrlScriptFallback) {
+    // mn-Cryl should not match with ru-Cyrl and und-Cyrl
+    EXPECT_NE(MN_CYRL_HYPHENATOR, lookup("ru-Cyrl"));
+    EXPECT_NE(MN_CYRL_HYPHENATOR, lookup("und-Cyrl"));
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/unittest/HyphenatorTest.cpp b/tests/unittest/HyphenatorTest.cpp
index ecd58a2..14c3ea3 100644
--- a/tests/unittest/HyphenatorTest.cpp
+++ b/tests/unittest/HyphenatorTest.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+#include "minikin/Hyphenator.h"
+
 #include <gtest/gtest.h>
 
-#include "ICUTestBase.h"
-#include <minikin/Hyphenator.h>
-#include <FileUtils.h>
+#include "FileUtils.h"
 
 #ifndef NELEM
 #define NELEM(x) ((sizeof(x) / sizeof((x)[0])))
@@ -29,12 +29,6 @@
 const char* usHyph = "/system/usr/hyphen-data/hyph-en-us.hyb";
 const char* malayalamHyph = "/system/usr/hyphen-data/hyph-ml.hyb";
 
-typedef ICUTestBase HyphenatorTest;
-
-const icu::Locale catalanLocale("ca", "ES", nullptr, nullptr);
-const icu::Locale polishLocale("pl", "PL", nullptr, nullptr);
-const icu::Locale& usLocale = icu::Locale::getUS();
-
 const uint16_t HYPHEN_MINUS = 0x002D;
 const uint16_t SOFT_HYPHEN = 0x00AD;
 const uint16_t MIDDLE_DOT = 0x00B7;
@@ -50,12 +44,13 @@
 const uint16_t EN_DASH = 0x2013;
 
 // Simple test for US English. This tests "table", which happens to be the in the exceptions list.
-TEST_F(HyphenatorTest, usEnglishAutomaticHyphenation) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(usHyph).data(), 2, 3);
+TEST(HyphenatorTest, usEnglishAutomaticHyphenation) {
+    std::vector<uint8_t> patternData = readWholeFile(usHyph);
+    Hyphenator* hyphenator = Hyphenator::loadBinary(patternData.data(), 2, 3, "en");
     const uint16_t word[] = {'t', 'a', 'b', 'l', 'e'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 5, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)5, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -64,12 +59,12 @@
 }
 
 // Catalan l·l should break as l-/l
-TEST_F(HyphenatorTest, catalanMiddleDot) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, catalanMiddleDot) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "ca");
     const uint16_t word[] = {'l', 'l', MIDDLE_DOT, 'l', 'l'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);
-    EXPECT_EQ((size_t) 5, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)5, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -78,36 +73,36 @@
 }
 
 // Catalan l·l should not break if the word is too short.
-TEST_F(HyphenatorTest, catalanMiddleDotShortWord) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, catalanMiddleDotShortWord) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "ca");
     const uint16_t word[] = {'l', MIDDLE_DOT, 'l'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
 }
 
 // If we break on a hyphen in Polish, the hyphen should be repeated on the next line.
-TEST_F(HyphenatorTest, polishHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
     const uint16_t word[] = {'x', HYPHEN, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, result[2]);
 }
 
 // If the language is Polish but the script is not Latin, don't use Polish rules for hyphenation.
-TEST_F(HyphenatorTest, polishHyphenButNonLatinWord) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishHyphenButNonLatinWord) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
     const uint16_t word[] = {GREEK_LOWER_ALPHA, HYPHEN, GREEK_LOWER_ALPHA};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -115,60 +110,73 @@
 
 // Polish en dash doesn't repeat on next line (as far as we know), but just provides a break
 // opportunity.
-TEST_F(HyphenatorTest, polishEnDash) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, polishEnDash) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "pl");
     const uint16_t word[] = {'x', EN_DASH, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
 }
 
+// If we break on a hyphen in Slovenian, the hyphen should be repeated on the next line. (Same as
+// Polish.)
+TEST(HyphenatorTest, slovenianHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "sl");
+    const uint16_t word[] = {'x', HYPHEN, 'y'};
+    std::vector<HyphenationType> result;
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
+    EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
+    EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
+    EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, result[2]);
+}
+
 // In Latin script text, soft hyphens should insert a visible hyphen if broken at.
-TEST_F(HyphenatorTest, latinSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, latinSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {'x', SOFT_HYPHEN, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
 }
 
 // Soft hyphens at the beginning of a word are not useful in linebreaking.
-TEST_F(HyphenatorTest, latinSoftHyphenStartingTheWord) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, latinSoftHyphenStartingTheWord) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {SOFT_HYPHEN, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 2, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)2, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
 }
 
 // In Malayalam script text, soft hyphens should not insert a visible hyphen if broken at.
-TEST_F(HyphenatorTest, malayalamSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, malayalamSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {MALAYALAM_KA, SOFT_HYPHEN, MALAYALAM_KA};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
 }
 
 // In automatically hyphenated Malayalam script text, we should not insert a visible hyphen.
-TEST_F(HyphenatorTest, malayalamAutomaticHyphenation) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(readWholeFile(malayalamHyph).data(), 2, 2);
-    const uint16_t word[] = {
-            MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA};
+TEST(HyphenatorTest, malayalamAutomaticHyphenation) {
+    std::vector<uint8_t> patternData = readWholeFile(malayalamHyph);
+    Hyphenator* hyphenator = Hyphenator::loadBinary(patternData.data(), 2, 2, "en");
+    const uint16_t word[] = {MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 5, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)5, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -177,12 +185,12 @@
 }
 
 // In Armenian script text, soft hyphens should insert an Armenian hyphen if broken at.
-TEST_F(HyphenatorTest, aremenianSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, aremenianSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARMENIAN_AYB, SOFT_HYPHEN, ARMENIAN_AYB};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN, result[2]);
@@ -190,12 +198,12 @@
 
 // In Hebrew script text, soft hyphens should insert a normal hyphen if broken at, for now.
 // We may need to change this to maqaf later.
-TEST_F(HyphenatorTest, hebrewSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hebrewSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {HEBREW_ALEF, SOFT_HYPHEN, HEBREW_ALEF};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -203,12 +211,12 @@
 
 // Soft hyphen between two Arabic letters that join should keep the joining
 // behavior when broken across lines.
-TEST_F(HyphenatorTest, arabicSoftHyphenConnecting) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenConnecting) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARABIC_BEH, SOFT_HYPHEN, ARABIC_BEH};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ, result[2]);
@@ -216,24 +224,24 @@
 
 // Arabic letters may be joining on one side, but if it's the wrong side, we
 // should use the normal hyphen.
-TEST_F(HyphenatorTest, arabicSoftHyphenNonConnecting) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenNonConnecting) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARABIC_ALEF, SOFT_HYPHEN, ARABIC_BEH};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
 }
 
 // Skip transparent characters until you find a non-transparent one.
-TEST_F(HyphenatorTest, arabicSoftHyphenSkipTransparents) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenSkipTransparents) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY, ARABIC_BEH};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 5, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)5, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -243,12 +251,12 @@
 
 // Skip transparent characters until you find a non-transparent one. If we get to one end without
 // finding anything, we are still non-joining.
-TEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtEnd) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenTransparentsAtEnd) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 4, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)4, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);
@@ -257,12 +265,12 @@
 
 // Skip transparent characters until you find a non-transparent one. If we get to one end without
 // finding anything, we are still non-joining.
-TEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtStart) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, arabicSoftHyphenTransparentsAtStart) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY, ARABIC_BEH};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 4, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)4, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);
@@ -270,12 +278,12 @@
 }
 
 // In Unified Canadian Aboriginal script (UCAS) text, soft hyphens should insert a UCAS hyphen.
-TEST_F(HyphenatorTest, ucasSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, ucasSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {UCAS_E, SOFT_HYPHEN, UCAS_E};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);
@@ -283,36 +291,36 @@
 
 // Presently, soft hyphen looks at the character after it to determine hyphenation type. This is a
 // little arbitrary, but let's test it anyway.
-TEST_F(HyphenatorTest, mixedScriptSoftHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, mixedScriptSoftHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {'a', SOFT_HYPHEN, UCAS_E};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);
 }
 
 // Hard hyphens provide a breaking opportunity with nothing extra inserted.
-TEST_F(HyphenatorTest, hardHyphen) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hardHyphen) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {'x', HYPHEN, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
 }
 
 // Hyphen-minuses also provide a breaking opportunity with nothing extra inserted.
-TEST_F(HyphenatorTest, hyphenMinus) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, hyphenMinus) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {'x', HYPHEN_MINUS, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 3, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)3, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
     EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);
@@ -320,15 +328,14 @@
 
 // If the word starts with a hard hyphen or hyphen-minus, it doesn't make sense to break
 // it at that point.
-TEST_F(HyphenatorTest, startingHyphenMinus) {
-    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);
+TEST(HyphenatorTest, startingHyphenMinus) {
+    Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2, "en");
     const uint16_t word[] = {HYPHEN_MINUS, 'y'};
     std::vector<HyphenationType> result;
-    hyphenator->hyphenate(&result, word, NELEM(word), usLocale);
-    EXPECT_EQ((size_t) 2, result.size());
+    hyphenator->hyphenate(word, &result);
+    EXPECT_EQ((size_t)2, result.size());
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);
     EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);
 }
 
 }  // namespace minikin
-
diff --git a/tests/unittest/ICUTestBase.h b/tests/unittest/ICUEnvironment.h
similarity index 61%
rename from tests/unittest/ICUTestBase.h
rename to tests/unittest/ICUEnvironment.h
index f915cf8..0dfb3f7 100644
--- a/tests/unittest/ICUTestBase.h
+++ b/tests/unittest/ICUEnvironment.h
@@ -14,41 +14,53 @@
  * limitations under the License.
  */
 
-#ifndef MINIKIN_TEST_ICU_TEST_BASE_H
-#define MINIKIN_TEST_ICU_TEST_BASE_H
+#ifndef MINIKIN_TEST_ICU_ENVIRONMENT_H
+#define MINIKIN_TEST_ICU_ENVIRONMENT_H
 
+// low level file access for mapping ICU data
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <cutils/log.h>
 #include <gtest/gtest.h>
 #include <unicode/uclean.h>
 #include <unicode/udata.h>
 
-// low level file access for mapping ICU data
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-
 namespace minikin {
 
-class ICUTestBase : public testing::Test {
-protected:
+class ICUEnvironment : public testing::Environment {
+public:
+    ICUEnvironment() : testing::Environment(), mData(nullptr), mSize(0) {}
+
+    void* mData;
+    size_t mSize;
+
     virtual void SetUp() override {
         const char* fn = "/system/usr/icu/" U_ICUDATA_NAME ".dat";
         int fd = open(fn, O_RDONLY);
-        ASSERT_NE(-1, fd);
+        LOG_ALWAYS_FATAL_IF(fd == -1);
         struct stat sb;
-        ASSERT_EQ(0, fstat(fd, &sb));
-        void* data = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+        LOG_ALWAYS_FATAL_IF(fstat(fd, &sb) != 0);
+
+        mSize = sb.st_size;
+        void* mData = mmap(nullptr, mSize, PROT_READ, MAP_SHARED, fd, 0);
+        close(fd);
 
         UErrorCode errorCode = U_ZERO_ERROR;
-        udata_setCommonData(data, &errorCode);
-        ASSERT_TRUE(U_SUCCESS(errorCode));
+        udata_setCommonData(mData, &errorCode);
+        LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));
+
+        errorCode = U_ZERO_ERROR;
         u_init(&errorCode);
-        ASSERT_TRUE(U_SUCCESS(errorCode));
+        LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));
     }
 
     virtual void TearDown() override {
         u_cleanup();
+        munmap(mData, mSize);
     }
 };
 
 }  // namespace minikin
-#endif  //  MINIKIN_TEST_ICU_TEST_BASE_H
+#endif  //  MINIKIN_TEST_ICU_ENVIRONMENT_H
diff --git a/tests/unittest/LayoutCacheTest.cpp b/tests/unittest/LayoutCacheTest.cpp
new file mode 100644
index 0000000..b7ad0ba
--- /dev/null
+++ b/tests/unittest/LayoutCacheTest.cpp
@@ -0,0 +1,275 @@
+/*
+ * 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/Layout.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/LayoutCache.h"
+
+#include "FontTestUtils.h"
+#include "LocaleListCache.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+class TestableLayoutCache : public LayoutCache {
+public:
+    TestableLayoutCache(uint32_t maxEntries) : LayoutCache(maxEntries) {}
+};
+
+class LayoutCapture {
+public:
+    LayoutCapture() {}
+
+    void operator()(const Layout& layout) { mLayout = &layout; }
+
+    const Layout* get() const { return mLayout; }
+
+private:
+    const Layout* mLayout;
+};
+
+TEST(LayoutCacheTest, cacheHitTest) {
+    auto text = utf8ToUtf16("android");
+    Range range(0, text.size());
+    MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+    TestableLayoutCache layoutCache(10);
+
+    LayoutCapture layout1;
+    layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT, layout1);
+
+    LayoutCapture layout2;
+    layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT, layout2);
+
+    EXPECT_EQ(layout1.get(), layout2.get());
+}
+
+TEST(LayoutCacheTest, cacheMissTest) {
+    auto text1 = utf8ToUtf16("android");
+    auto text2 = utf8ToUtf16("ANDROID");
+    MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+    TestableLayoutCache layoutCache(10);
+
+    LayoutCapture layout1;
+    LayoutCapture layout2;
+
+    {
+        SCOPED_TRACE("Different text");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different range");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text1, Range(1, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different text");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different direction");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, true /* RTL */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different start hyphenation");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::INSERT_HYPHEN, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different end hyphen");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::INSERT_HYPHEN, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different collection");
+        MinikinPaint paint1(buildFontCollection("Ascii.ttf"));
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(buildFontCollection("Emoji.ttf"));
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different size");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.size = 10.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.size = 20.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different scale X");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.scaleX = 1.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.scaleX = 2.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different skew X");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.skewX = 1.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.skewX = 2.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different letter spacing");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.letterSpacing = 0.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.letterSpacing = 1.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different word spacing");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.wordSpacing = 0.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.wordSpacing = 1.0f;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different paint flags");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.paintFlags = 0;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.paintFlags = LinearTextFlag;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different locale list ID");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.localeListId = LocaleListCache::getId("en-US");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.localeListId = LocaleListCache::getId("ja-JP");
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different family variant");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.familyVariant = FontFamily::Variant::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;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+    {
+        SCOPED_TRACE("Different font feature settings");
+        auto collection = buildFontCollection("Ascii.ttf");
+        MinikinPaint paint1(collection);
+        paint1.fontFeatureSettings = "";
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout1);
+        MinikinPaint paint2(collection);
+        paint2.fontFeatureSettings = "'liga' on";
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+        EXPECT_NE(layout1.get(), layout2.get());
+    }
+}
+
+TEST(LayoutCacheTest, cacheOverflowTest) {
+    auto text = utf8ToUtf16("android");
+    Range range(0, text.size());
+    MinikinPaint paint(buildFontCollection("Ascii.ttf"));
+
+    TestableLayoutCache layoutCache(5);
+
+    LayoutCapture layout1;
+    layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT, layout1);
+
+    for (char c = 'a'; c <= 'z'; c++) {
+        auto text1 = utf8ToUtf16(std::string(c, 10));
+        LayoutCapture layout2;
+        layoutCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */,
+                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, layout2);
+    }
+
+    LayoutCapture layout3;
+    layoutCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT,
+                            EndHyphenEdit::NO_EDIT, layout3);
+    EXPECT_NE(layout1.get(), layout3.get());
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index f89ac9e..21109cb 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
+#include "minikin/Layout.h"
+
 #include <gtest/gtest.h>
 
-#include "ICUTestBase.h"
 #include "minikin/FontCollection.h"
-#include "minikin/Layout.h"
-#include "../util/FontTestUtils.h"
-#include "../util/UnicodeUtils.h"
+#include "minikin/LayoutPieces.h"
 
-const char* SYSTEM_FONT_PATH = "/system/fonts/";
-const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
 
 namespace minikin {
 
@@ -45,26 +44,41 @@
     }
 }
 
-class LayoutTest : public ICUTestBase {
+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 {
 protected:
-    LayoutTest() : mCollection(nullptr) {
-    }
+    LayoutTest() : mCollection(nullptr) {}
 
     virtual ~LayoutTest() {}
 
-    virtual void SetUp() override {
-        mCollection = std::shared_ptr<FontCollection>(
-                getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));
-    }
+    virtual void SetUp() override { mCollection = buildFontCollection("Ascii.ttf"); }
 
-    virtual void TearDown() override {
-    }
+    virtual void TearDown() override {}
 
     std::shared_ptr<FontCollection> mCollection;
 };
 
 TEST_F(LayoutTest, doLayoutTest) {
-    MinikinPaint paint;
+    MinikinPaint paint(mCollection);
+    paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
     const size_t kMaxAdvanceLength = 32;
     float advances[kMaxAdvanceLength];
@@ -77,14 +91,15 @@
     {
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -96,14 +111,15 @@
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(90.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -115,14 +131,15 @@
     {
         SCOPED_TRACE("three words");
         text = utf8ToUtf16("three words test");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(160.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -134,14 +151,15 @@
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(110.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -153,7 +171,8 @@
 }
 
 TEST_F(LayoutTest, doLayoutTest_wordSpacing) {
-    MinikinPaint paint;
+    MinikinPaint paint(mCollection);
+    paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
     const size_t kMaxAdvanceLength = 32;
     float advances[kMaxAdvanceLength];
@@ -168,14 +187,15 @@
     {
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -187,14 +207,15 @@
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(95.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         EXPECT_EQ(UNTOUCHED_MARKER, advances[text.size()]);
@@ -210,14 +231,15 @@
     {
         SCOPED_TRACE("three words test");
         text = utf8ToUtf16("three words test");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(170.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -231,14 +253,15 @@
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(120.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -252,7 +275,8 @@
 }
 
 TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) {
-    MinikinPaint paint;
+    MinikinPaint paint(mCollection);
+    paint.size = 10.0f;  // make 1em = 10px
     MinikinRect rect;
     const size_t kMaxAdvanceLength = 32;
     float advances[kMaxAdvanceLength];
@@ -267,14 +291,15 @@
     {
         SCOPED_TRACE("one word");
         text = utf8ToUtf16("oneword");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(70.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -286,14 +311,15 @@
     {
         SCOPED_TRACE("two words");
         text = utf8ToUtf16("two words");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(85.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -306,14 +332,15 @@
     {
         SCOPED_TRACE("three words");
         text = utf8ToUtf16("three word test");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(140.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -327,14 +354,15 @@
     {
         SCOPED_TRACE("two spaces");
         text = utf8ToUtf16("two  spaces");
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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(0.0f, rect.mTop);
+        EXPECT_EQ(10.0f, rect.mTop);
         EXPECT_EQ(100.0f, rect.mRight);
-        EXPECT_EQ(10.0f, rect.mBottom);
+        EXPECT_EQ(0.0f, rect.mBottom);
         resetAdvances(advances, kMaxAdvanceLength);
         layout.getAdvances(advances);
         expectedValues.resize(text.size());
@@ -349,17 +377,18 @@
 
 // Test that a forced-RTL layout correctly mirros a forced-LTR layout.
 TEST_F(LayoutTest, doLayoutTest_rtlTest) {
-    MinikinPaint paint;
+    MinikinPaint paint(mCollection);
 
     std::vector<uint16_t> text = parseUnicodeString("'a' 'b' U+3042 U+3043 'c' 'd'");
+    Range range(0, text.size());
 
     Layout ltrLayout;
-    ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_LTR, FontStyle(),
-            paint, mCollection);
+    ltrLayout.doLayout(text, range, Bidi::FORCE_LTR, paint, StartHyphenEdit::NO_EDIT,
+                       EndHyphenEdit::NO_EDIT);
 
     Layout rtlLayout;
-    rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Force_RTL, FontStyle(),
-            paint, mCollection);
+    rtlLayout.doLayout(text, range, Bidi::FORCE_RTL, paint, StartHyphenEdit::NO_EDIT,
+                       EndHyphenEdit::NO_EDIT);
 
     ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());
     ASSERT_EQ(6u, ltrLayout.nGlyphs());
@@ -373,21 +402,22 @@
 
 // Test that single-run RTL layouts of LTR-only text is laid out identical to an LTR layout.
 TEST_F(LayoutTest, singleRunBidiTest) {
-    MinikinPaint paint;
+    MinikinPaint paint(mCollection);
 
     std::vector<uint16_t> text = parseUnicodeString("'1' '2' '3'");
+    Range range(0, text.size());
 
     Layout ltrLayout;
-    ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(),
-            paint, mCollection);
+    ltrLayout.doLayout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT,
+                       EndHyphenEdit::NO_EDIT);
 
     Layout rtlLayout;
-    rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_RTL, FontStyle(),
-            paint, mCollection);
+    rtlLayout.doLayout(text, range, Bidi::RTL, paint, StartHyphenEdit::NO_EDIT,
+                       EndHyphenEdit::NO_EDIT);
 
     Layout defaultRtlLayout;
-    defaultRtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_Default_RTL,
-            FontStyle(), paint, mCollection);
+    defaultRtlLayout.doLayout(text, range, Bidi::DEFAULT_RTL, paint, StartHyphenEdit::NO_EDIT,
+                              EndHyphenEdit::NO_EDIT);
 
     const size_t nGlyphs = ltrLayout.nGlyphs();
     ASSERT_EQ(3u, nGlyphs);
@@ -404,6 +434,8 @@
 }
 
 TEST_F(LayoutTest, hyphenationTest) {
+    MinikinPaint paint(mCollection);
+    paint.size = 10.0f;  // make 1em = 10px
     Layout layout;
     std::vector<uint16_t> text;
 
@@ -411,50 +443,174 @@
     {
         SCOPED_TRACE("one word with no hyphen edit");
         text = utf8ToUtf16("oneword");
-        MinikinPaint paint;
-        paint.hyphenEdit = HyphenEdit::NO_EDIT;
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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");
-        MinikinPaint paint;
-        paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_END;
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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");
-        MinikinPaint paint;
-        paint.hyphenEdit = HyphenEdit::REPLACE_WITH_HYPHEN_AT_END;
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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");
-        MinikinPaint paint;
-        paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_START;
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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");
-        MinikinPaint paint;
-        paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_START | HyphenEdit::INSERT_HYPHEN_AT_END;
-        layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR, FontStyle(), paint,
-                mCollection);
+        Range range(0, text.size());
+        layout.doLayout(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
+    // U+002E (.): 10em
+    // U+0043 (C): 100em
+    // U+0049 (I): 1em
+    // U+004C (L): 50em
+    // U+0056 (V): 5em
+    // U+0058 (X): 10em
+    // U+005F (_): 0em
+    // U+FFFD (invalid surrogate will be replaced to this): 7em
+    // U+10331 (\uD800\uDF31): 10em
+    auto fc = buildFontCollection("LayoutTestFont.ttf");
+    {
+        MinikinPaint paint(fc);
+        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));
+        ASSERT_EQ(1u, advances.size());
+        EXPECT_EQ(1.0f, advances[0]);
+    }
+    {
+        MinikinPaint paint(fc);
+        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));
+        ASSERT_EQ(2u, advances.size());
+        EXPECT_EQ(1.0f, advances[0]);
+        EXPECT_EQ(5.0f, advances[1]);
+    }
+    {
+        MinikinPaint paint(fc);
+        std::vector<uint16_t> text = utf8ToUtf16("IVX");
+        std::vector<float> advances(text.size());
+        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));
+        ASSERT_EQ(3u, advances.size());
+        EXPECT_EQ(1.0f, advances[0]);
+        EXPECT_EQ(5.0f, advances[1]);
+        EXPECT_EQ(10.0f, advances[2]);
+    }
+}
+
+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 e7e6c27..4c18cce 100644
--- a/tests/unittest/LayoutUtilsTest.cpp
+++ b/tests/unittest/LayoutUtilsTest.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-
 #include "LayoutUtils.h"
 
+#include <gtest/gtest.h>
+
+#include "UnicodeUtils.h"
+
 namespace minikin {
 
 void ExpectNextWordBreakForCache(size_t offset_in, const char* query_str) {
@@ -28,9 +29,8 @@
     size_t size = 0U;
 
     ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
-    EXPECT_EQ(expected_breakpoint,
-              getNextWordBreakForCache(buf, offset_in, size))
-        << "Expected position is [" << query_str << "] from offset " << offset_in;
+    EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(buf, offset_in, size))
+            << "Expected position is [" << query_str << "] from offset " << offset_in;
 }
 
 void ExpectPrevWordBreakForCache(size_t offset_in, const char* query_str) {
@@ -40,9 +40,8 @@
     size_t size = 0U;
 
     ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);
-    EXPECT_EQ(expected_breakpoint,
-              getPrevWordBreakForCache(buf, offset_in, size))
-        << "Expected position is [" << query_str << "] from offset " << offset_in;
+    EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(buf, offset_in, size))
+            << "Expected position is [" << query_str << "] from offset " << offset_in;
 }
 
 TEST(WordBreakTest, goNextWordBreakTest) {
@@ -89,8 +88,7 @@
     ExpectNextWordBreakForCache(3, "U+4E00   U+4E00   U+4E00   U+4E00 | U+4E00");
     ExpectNextWordBreakForCache(4, "U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |");
     ExpectNextWordBreakForCache(5, "U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |");
-    ExpectNextWordBreakForCache(1000,
-                             "U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |");
+    ExpectNextWordBreakForCache(1000, "U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |");
 
     ExpectNextWordBreakForCache(0, "U+4E00 | U+4E8C   U+4E09   U+56DB   U+4E94");
     ExpectNextWordBreakForCache(1, "U+4E00   U+4E8C | U+4E09   U+56DB   U+4E94");
@@ -98,8 +96,7 @@
     ExpectNextWordBreakForCache(3, "U+4E00   U+4E8C   U+4E09   U+56DB | U+4E94");
     ExpectNextWordBreakForCache(4, "U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |");
     ExpectNextWordBreakForCache(5, "U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |");
-    ExpectNextWordBreakForCache(1000,
-                             "U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |");
+    ExpectNextWordBreakForCache(1000, "U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |");
 
     ExpectNextWordBreakForCache(0, "U+4E00 'a' 'b' | U+2000 'c' U+4E00");
     ExpectNextWordBreakForCache(1, "U+4E00 'a' 'b' | U+2000 'c' U+4E00");
@@ -246,8 +243,7 @@
     ExpectNextWordBreakForCache(4, "U+845B U+E0100 U+E0100 | U+845B");
     ExpectNextWordBreakForCache(5, "U+845B U+E0100 U+E0100 U+845B |");
     ExpectNextWordBreakForCache(6, "U+845B U+E0100 U+E0100 U+845B |");
-    ExpectNextWordBreakForCache(1000,
-                             "U+845B U+E0100 U+E0100 U+845B |");
+    ExpectNextWordBreakForCache(1000, "U+845B U+E0100 U+E0100 U+845B |");
 
     // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)
     ExpectNextWordBreakForCache(0, "U+845B U+FE00 U+E0100 | U+845B");
@@ -477,8 +473,7 @@
     ExpectPrevWordBreakForCache(4, "| U+845B U+E0100 U+E0100 U+845B");
     ExpectPrevWordBreakForCache(5, "| U+845B U+E0100 U+E0100 U+845B");
     ExpectPrevWordBreakForCache(6, "U+845B U+E0100 U+E0100 | U+845B");
-    ExpectPrevWordBreakForCache(1000,
-                             "U+845B U+E0100 U+E0100 | U+845B");
+    ExpectPrevWordBreakForCache(1000, "U+845B U+E0100 U+E0100 | U+845B");
 
     // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)
     ExpectPrevWordBreakForCache(0, "| U+845B U+FE00 U+E0100 U+845B");
diff --git a/tests/unittest/LineBreakerTest.cpp b/tests/unittest/LineBreakerTest.cpp
deleted file mode 100644
index 4b18c8c..0000000
--- a/tests/unittest/LineBreakerTest.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2017 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 "ICUTestBase.h"
-#include <minikin/LineBreaker.h>
-#include <unicode/locid.h>
-
-namespace minikin {
-
-typedef ICUTestBase LineBreakerTest;
-
-TEST_F(LineBreakerTest, setLocales) {
-    {
-        LineBreaker lineBreaker;
-        Hyphenator hyphenator;
-        std::vector<Hyphenator*> hyphenators;
-        hyphenators.push_back(&hyphenator);
-        lineBreaker.setLocales("en-US", hyphenators);
-        EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
-        EXPECT_EQ(&hyphenator, lineBreaker.mHyphenator);
-    }
-    {
-        LineBreaker lineBreaker;
-        Hyphenator hyphenator1, hyphenator2;
-        std::vector<Hyphenator*> hyphenators;
-        hyphenators.push_back(&hyphenator1);
-        hyphenators.push_back(&hyphenator2);
-        lineBreaker.setLocales("fr-FR,en-US", hyphenators);
-        EXPECT_EQ(icu::Locale::getFrance(), lineBreaker.mLocale);
-        EXPECT_EQ(&hyphenator1, lineBreaker.mHyphenator);
-    }
-    {
-        LineBreaker lineBreaker;
-        std::vector<Hyphenator*> hyphenators;
-        lineBreaker.setLocales("", hyphenators);
-        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
-        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
-    }
-    {
-        LineBreaker lineBreaker;
-        std::vector<Hyphenator*> hyphenators;
-        Hyphenator hyphenator;
-        hyphenators.push_back(&hyphenator);
-        lineBreaker.setLocales("THISISABOGUSLANGUAGE", hyphenators);
-        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
-        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
-    }
-    {
-        LineBreaker lineBreaker;
-        Hyphenator hyphenator1, hyphenator2;
-        std::vector<Hyphenator*> hyphenators;
-        hyphenators.push_back(&hyphenator1);
-        hyphenators.push_back(&hyphenator2);
-        lineBreaker.setLocales("THISISABOGUSLANGUAGE,en-US", hyphenators);
-        EXPECT_EQ(icu::Locale::getUS(), lineBreaker.mLocale);
-        EXPECT_EQ(&hyphenator2, lineBreaker.mHyphenator);
-    }
-    {
-        LineBreaker lineBreaker;
-        Hyphenator hyphenator1, hyphenator2;
-        std::vector<Hyphenator*> hyphenators;
-        hyphenators.push_back(&hyphenator1);
-        hyphenators.push_back(&hyphenator2);
-        lineBreaker.setLocales("THISISABOGUSLANGUAGE,ANOTHERBOGUSLANGUAGE", hyphenators);
-        EXPECT_EQ(icu::Locale::getRoot(), lineBreaker.mLocale);
-        EXPECT_EQ(nullptr, lineBreaker.mHyphenator);
-    }
-}
-
-}  // namespace minikin
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
new file mode 100644
index 0000000..70d6521
--- /dev/null
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -0,0 +1,183 @@
+/*
+ * 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/Hyphenator.h"
+#include "minikin/LineBreaker.h"
+
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+namespace line_breaker_test_helper {
+
+class RectangleLineWidth : public LineWidth {
+public:
+    RectangleLineWidth(float width) : mWidth(width) {}
+    virtual ~RectangleLineWidth() {}
+
+    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;
+};
+
+// 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) {
+        mLocaleListId = LocaleListCache::getId(lang);
+    }
+
+    virtual bool isRtl() const override { return false; }
+    virtual bool canHyphenate() const override { return true; }
+    virtual uint32_t getLocaleListId() const { return mLocaleListId; }
+
+    virtual void getMetrics(const U16StringPiece&, float* advances, MinikinExtent*,
+                            LayoutPieces*) const {
+        std::fill(advances, advances + mRange.getLength(), mWidth);
+    }
+
+    virtual std::pair<float, MinikinRect> getBounds(const U16StringPiece& /* text */,
+                                                    const Range& /* range */,
+                                                    const LayoutPieces& /* pieces */) const {
+        return std::make_pair(mWidth, MinikinRect());
+    }
+
+    virtual const MinikinPaint* getPaint() const { return &mPaint; }
+
+    virtual float measureHyphenPiece(const U16StringPiece&, const Range& range,
+                                     StartHyphenEdit start, EndHyphenEdit end, float*,
+                                     LayoutPieces*) const {
+        uint32_t extraCharForHyphen = 0;
+        if (isInsertion(start)) {
+            extraCharForHyphen++;
+        }
+        if (isInsertion(end)) {
+            extraCharForHyphen++;
+        }
+        return mWidth * (range.getLength() + extraCharForHyphen);
+    }
+
+private:
+    MinikinPaint mPaint;
+    uint32_t mLocaleListId;
+    float mWidth;
+};
+
+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;
+};
+
+static bool sameLineBreak(const std::vector<LineBreakExpectation>& expected,
+                          const LineBreakResult& actual) {
+    if (expected.size() != actual.breakPoints.size()) {
+        return false;
+    }
+
+    uint32_t breakOffset = 0;
+    for (uint32_t i = 0; i < expected.size(); ++i) {
+        std::vector<uint16_t> u16Str = utf8ToUtf16(expected[i].mLineContent);
+
+        // The expected string contains auto inserted hyphen. Remove it for computing offset.
+        uint32_t lineLength = u16Str.size();
+        if (isInsertion(expected[i].mStartEdit)) {
+            if (u16Str[0] != '-') {
+                return false;
+            }
+            --lineLength;
+        }
+        if (isInsertion(expected[i].mEndEdit)) {
+            if (u16Str.back() != '-') {
+                return false;
+            }
+            --lineLength;
+        }
+        breakOffset += lineLength;
+
+        if (breakOffset != static_cast<uint32_t>(actual.breakPoints[i])) {
+            return false;
+        }
+        if (expected[i].mWidth != actual.widths[i]) {
+            return false;
+        }
+        HyphenEdit edit = static_cast<HyphenEdit>(actual.flags[i] & 0xFF);
+        if (expected[i].mStartEdit != startHyphenEdit(edit)) {
+            return false;
+        }
+        if (expected[i].mEndEdit != endHyphenEdit(edit)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Make debug string.
+static std::string toString(const std::vector<LineBreakExpectation>& lines) {
+    std::string out;
+    for (uint32_t i = 0; i < lines.size(); ++i) {
+        const LineBreakExpectation& line = lines[i];
+
+        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());
+        out += lineMsg;
+    }
+    return out;
+}
+
+// Make debug string.
+static std::string toString(const U16StringPiece& textBuf, const LineBreakResult& lines) {
+    std::string out;
+    for (uint32_t i = 0; i < lines.breakPoints.size(); ++i) {
+        const Range textRange(i == 0 ? 0 : lines.breakPoints[i - 1], lines.breakPoints[i]);
+        const HyphenEdit edit = static_cast<HyphenEdit>(lines.flags[i] & 0xFF);
+
+        const StartHyphenEdit startEdit = startHyphenEdit(edit);
+        const EndHyphenEdit endEdit = endHyphenEdit(edit);
+        std::string hyphenatedStr = utf16ToUtf8(textBuf.substr(textRange));
+
+        if (isInsertion(startEdit)) {
+            hyphenatedStr.insert(0, "-");
+        }
+        if (isInsertion(endEdit)) {
+            hyphenatedStr.push_back('-');
+        }
+        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());
+        out += lineMsg;
+    }
+    return out;
+}
+
+}  // namespace line_breaker_test_helper
+}  // namespace minikin
diff --git a/tests/unittest/LocaleListTest.cpp b/tests/unittest/LocaleListTest.cpp
new file mode 100644
index 0000000..8056185
--- /dev/null
+++ b/tests/unittest/LocaleListTest.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 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/Hyphenator.h"
+
+#include "GreedyLineBreaker.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+
+namespace minikin {
+namespace {
+
+const LocaleList& getLocaleList(const std::string& localeStr) {
+    return LocaleListCache::getById(LocaleListCache::getId(localeStr));
+}
+
+TEST(LocaleListTest, basicTest) {
+    std::string langTag = "en-US";
+    EXPECT_EQ(1U, getLocaleList(langTag).size());
+    EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+    langTag = "en-US,fr-FR";
+    EXPECT_EQ(2U, getLocaleList(langTag).size());
+    EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+    EXPECT_EQ("fr-Latn-FR", getLocaleList(langTag)[1].getString());
+
+    langTag = "en-US,en-US,fr-FR";
+    EXPECT_EQ(2U, getLocaleList(langTag).size());
+    EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+    EXPECT_EQ("fr-Latn-FR", getLocaleList(langTag)[1].getString());
+
+    EXPECT_EQ(0U, getLocaleList("").size());
+}
+
+TEST(LocaleListTest, bogusLanguageTest) {
+    std::string langTag = "en-US,THISISBOGUSLANGUAGE";
+    EXPECT_EQ(1U, getLocaleList(langTag).size());
+    EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+    langTag = "THISISBOGUSLANGUAGE,en-US";
+    EXPECT_EQ(1U, getLocaleList(langTag).size());
+    EXPECT_EQ("en-Latn-US", getLocaleList(langTag)[0].getString());
+
+    langTag = "THISISBOGUSLANGUAGE,THISISANOTHERBOGUSLANGUAGE";
+    EXPECT_EQ(0U, getLocaleList(langTag).size());
+}
+
+}  // namespace
+}  // namespace minikin
diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp
new file mode 100644
index 0000000..e8ed408
--- /dev/null
+++ b/tests/unittest/MeasuredTextTest.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 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/MeasuredText.h"
+
+#include <gtest/gtest.h>
+
+#include "minikin/LineBreaker.h"
+
+#include "FontTestUtils.h"
+#include "UnicodeUtils.h"
+
+namespace minikin {
+
+constexpr float CHAR_WIDTH = 10.0;  // Mock implementation always returns 10.0 for advance.
+
+TEST(MeasuredTextTest, RunTests) {
+    constexpr uint32_t CHAR_COUNT = 6;
+    constexpr float REPLACEMENT_WIDTH = 20.0f;
+    auto font = buildFontCollection("Ascii.ttf");
+
+    MeasuredTextBuilder builder;
+
+    MinikinPaint paint1(font);
+    paint1.size = 10.0f;  // make 1em = 10px
+    builder.addStyleRun(0, 2, std::move(paint1), false /* is RTL */);
+    builder.addReplacementRun(2, 4, REPLACEMENT_WIDTH, 0 /* locale list id */);
+    MinikinPaint paint2(font);
+    paint2.size = 10.0f;  // make 1em = 10px
+    builder.addStyleRun(4, 6, std::move(paint2), false /* is RTL */);
+
+    std::vector<uint16_t> text(CHAR_COUNT, 'a');
+
+    std::unique_ptr<MeasuredText> measuredText =
+            builder.build(text, true /* compute hyphenation */, false /* compute full layout */);
+
+    ASSERT_TRUE(measuredText);
+
+    // ReplacementRun assigns all width to the first character and leave zeros others.
+    std::vector<float> expectedWidths = {CHAR_WIDTH, CHAR_WIDTH, REPLACEMENT_WIDTH,
+                                         0,          CHAR_WIDTH, CHAR_WIDTH};
+
+    EXPECT_EQ(expectedWidths, measuredText->widths);
+}
+
+}  // namespace minikin
diff --git a/tests/unittest/MeasurementTests.cpp b/tests/unittest/MeasurementTests.cpp
index 7fedecb..b5a85c7 100644
--- a/tests/unittest/MeasurementTests.cpp
+++ b/tests/unittest/MeasurementTests.cpp
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
+#include "minikin/Measurement.h"
+
 #include <gtest/gtest.h>
-#include <UnicodeUtils.h>
-#include <minikin/Measurement.h>
+
+#include "UnicodeUtils.h"
 
 namespace minikin {
 
diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp
new file mode 100644
index 0000000..a4479ad
--- /dev/null
+++ b/tests/unittest/OptimalLineBreakerTest.cpp
@@ -0,0 +1,1155 @@
+/*
+ * 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 <memory>
+
+#include <gtest/gtest.h>
+
+#include "minikin/Hyphenator.h"
+
+#include "FileUtils.h"
+#include "FontTestUtils.h"
+#include "HyphenatorMap.h"
+#include "LineBreakerTestHelper.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
+#include "OptimalLineBreaker.h"
+#include "UnicodeUtils.h"
+#include "WordBreaker.h"
+
+namespace minikin {
+namespace {
+
+using line_breaker_test_helper::ConstantRun;
+using line_breaker_test_helper::LineBreakExpectation;
+using line_breaker_test_helper::RectangleLineWidth;
+using line_breaker_test_helper::sameLineBreak;
+using line_breaker_test_helper::toString;
+
+class OptimalLineBreakerTest : public testing::Test {
+public:
+    OptimalLineBreakerTest() {}
+
+    virtual ~OptimalLineBreakerTest() {}
+
+    virtual void SetUp() override {
+        mHyphenationPattern = readWholeFile("/system/usr/hyphen-data/hyph-en-us.hyb");
+        Hyphenator* hyphenator = Hyphenator::loadBinary(
+                mHyphenationPattern.data(), 2 /* min prefix */, 2 /* min suffix */, "en-US");
+        HyphenatorMap::add("en-US", hyphenator);
+        HyphenatorMap::add("pl", Hyphenator::loadBinary(nullptr, 0, 0, "pl"));
+    }
+
+    virtual void TearDown() override { HyphenatorMap::clear(); }
+
+protected:
+    LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
+                                HyphenationFrequency frequency, float charWidth, float lineWidth) {
+        return doLineBreak(textBuffer, strategy, frequency, charWidth, "en-US", lineWidth);
+    }
+
+    LineBreakResult doLineBreak(const U16StringPiece& textBuffer, BreakStrategy strategy,
+                                HyphenationFrequency frequency, float charWidth,
+                                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 */);
+        return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth);
+    }
+
+    LineBreakResult doLineBreak(const U16StringPiece& textBuffer, const MeasuredText& measuredText,
+                                BreakStrategy strategy, HyphenationFrequency frequency,
+                                float lineWidth) {
+        RectangleLineWidth rectangleLineWidth(lineWidth);
+        return breakLineOptimal(textBuffer, measuredText, rectangleLineWidth, strategy, frequency,
+                                false /* justified */);
+    }
+
+private:
+    std::vector<uint8_t> mHyphenationPattern;
+};
+
+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;
+    constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text.");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    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;
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 23 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 17 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 16 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 15 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 13 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 12 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 9 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 8 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 7 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 6 * CHAR_WIDTH;
+        // 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 },
+                // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 5 * CHAR_WIDTH;
+        // 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 },
+                // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 4 * CHAR_WIDTH;
+        // 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 },
+                // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+                // 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 },
+                // 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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 },
+                // 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 3 * CHAR_WIDTH;
+        // 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 },
+                // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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 },
+                // 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 },
+                // 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, 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},
+                // 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},
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 2 * CHAR_WIDTH;
+        // 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 },
+                // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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 },
+                // 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 },
+                // 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 },
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 1 * CHAR_WIDTH;
+        // 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 },
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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".
+    const std::vector<uint16_t> textBuf = utf8ToUtf16("czerwono-niebieska");
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    constexpr StartHyphenEdit START_HYPHEN = StartHyphenEdit::INSERT_HYPHEN;
+
+    // Note that disable clang-format everywhere since aligned expectation is more readable.
+    {
+        constexpr float LINE_WIDTH = 1000 * CHAR_WIDTH;
+        std::vector<LineBreakExpectation> expect = {
+                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "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;
+        std::vector<LineBreakExpectation> expect = {
+                {"czerwono-niebieska", 18 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "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;
+        // 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},
+        };
+        // clang-format on
+
+        const auto actual = doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, "pl",
+                                        LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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 StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    {
+        const auto textBuf = utf8ToUtf16("");
+        std::vector<LineBreakExpectation> expect = {};
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        const auto textBuf = utf8ToUtf16("A");
+        std::vector<LineBreakExpectation> expect = {
+                {"A", 1 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        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},
+        };
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testZeroWidthCharacter) {
+    constexpr float CHAR_WIDTH = 0.0;
+    constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+    constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        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},
+        };
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        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},
+        };
+        const auto actual =
+                doLineBreak(textBuf, HIGH_QUALITY, NORMAL_HYPHENATION, CHAR_WIDTH, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testLocaleSwitchTest) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+    constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    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},
+        };
+
+        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 */);
+
+        const auto actual =
+                doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        std::vector<LineBreakExpectation> expect = {
+                {"This is an example text.", 24 * CHAR_WIDTH, NO_START_HYPHEN, NO_END_HYPHEN},
+        };
+
+        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 */);
+        const auto actual =
+                doLineBreak(textBuf, *measuredText, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+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;
+    constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+    {
+        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        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},
+        };
+        // clang-format on
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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},
+        };
+        // clang-format on
+        actual = doLineBreak(textBuf, BALANCED, NO_HYPHENATION, CHAR_WIDTH, 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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+        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},
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, HIGH_QUALITY, NO_HYPHENATION, CHAR_WIDTH, 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);
+        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);
+        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);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+}
+
+TEST_F(OptimalLineBreakerTest, testLocaleSwitch_InEmailOrUrl) {
+    constexpr float CHAR_WIDTH = 10.0;
+    constexpr BreakStrategy HIGH_QUALITY = BreakStrategy::HighQuality;
+    constexpr BreakStrategy BALANCED = BreakStrategy::Balanced;
+    constexpr HyphenationFrequency NO_HYPHENATION = HyphenationFrequency::None;
+    constexpr HyphenationFrequency NORMAL_HYPHENATION = HyphenationFrequency::Normal;
+
+    constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+    constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+    constexpr float LINE_WIDTH = 24 * CHAR_WIDTH;
+    {
+        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 */);
+
+        // 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},
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, *measured, 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},
+        };
+        // clang-format on
+
+        actual = doLineBreak(textBuf, *measured, BALANCED, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, *measured, BALANCED, NORMAL_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+    }
+    {
+        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 */);
+
+        // 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},
+        };
+        // clang-format on
+
+        auto actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, *measured, HIGH_QUALITY, NORMAL_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, *measured, BALANCED, NO_HYPHENATION, LINE_WIDTH);
+        EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+                                                   << " vs " << std::endl
+                                                   << toString(textBuf, actual);
+        actual = doLineBreak(textBuf, *measured, BALANCED, NORMAL_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/SparseBitSetTest.cpp b/tests/unittest/SparseBitSetTest.cpp
index 39c9e1b..03a14d9 100644
--- a/tests/unittest/SparseBitSetTest.cpp
+++ b/tests/unittest/SparseBitSetTest.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
+#include "minikin/SparseBitSet.h"
+
 #include <random>
 
 #include <gtest/gtest.h>
-#include <minikin/SparseBitSet.h>
 
 namespace minikin {
 
@@ -27,7 +28,7 @@
     std::mt19937 mt;  // Fix seeds to be able to reproduce the result.
     std::uniform_int_distribution<uint16_t> distribution(1, 512);
 
-    std::vector<uint32_t> range { distribution(mt) };
+    std::vector<uint32_t> range{distribution(mt)};
     for (size_t i = 1; i < kTestRangeNum * 2; ++i) {
         range.push_back((range.back() - 1) + distribution(mt));
     }
diff --git a/tests/unittest/StringPieceTest.cpp b/tests/unittest/StringPieceTest.cpp
new file mode 100644
index 0000000..f70fd3a
--- /dev/null
+++ b/tests/unittest/StringPieceTest.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2017 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 "StringPiece.h"
+
+#include <gtest/gtest.h>
+
+namespace minikin {
+
+TEST(StringPieceTest, basics) {
+    {
+        StringPiece s(nullptr);
+        EXPECT_EQ(nullptr, s.data());
+        EXPECT_EQ(0u, s.size());
+        EXPECT_TRUE(s.empty());
+    }
+    {
+        StringPiece s("");
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(0u, s.size());
+        EXPECT_TRUE(s.empty());
+    }
+    {
+        StringPiece s("", 0);
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(0u, s.size());
+        EXPECT_TRUE(s.empty());
+    }
+    {
+        StringPiece s("abcde");
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(5u, s.size());
+        EXPECT_FALSE(s.empty());
+        EXPECT_EQ("abcde", s);
+        EXPECT_NE("abc", s);
+    }
+    {
+        StringPiece s("abcde", 5);
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(5u, s.size());
+        EXPECT_FALSE(s.empty());
+        EXPECT_EQ("abcde", s);
+        EXPECT_NE("abc", s);
+    }
+    {
+        StringPiece s("abcde", 3);
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(3u, s.size());
+        EXPECT_FALSE(s.empty());
+        EXPECT_EQ("abc", s);
+        EXPECT_NE("abcde", s);
+    }
+    {
+        const char* kText = "abcde";
+        StringPiece s(kText + 2, 3);
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(3u, s.size());
+        EXPECT_FALSE(s.empty());
+        EXPECT_EQ("cde", s);
+        EXPECT_NE("abcde", s);
+    }
+    {
+        const char* kText = "abcde";
+        StringPiece s(kText + 2);
+        EXPECT_NE(nullptr, s.data());
+        EXPECT_EQ(3u, s.size());
+        EXPECT_FALSE(s.empty());
+        EXPECT_EQ("cde", s);
+        EXPECT_NE("abcde", s);
+    }
+}
+
+TEST(StringPieceTest, substr) {
+    StringPiece s("abcde");
+    EXPECT_EQ("", s.substr(0, 0));
+    EXPECT_EQ("a", s.substr(0, 1));
+    EXPECT_EQ("abc", s.substr(0, 3));
+    EXPECT_EQ("cde", s.substr(2, 3));
+    EXPECT_EQ("", s.substr(2, 0));
+    EXPECT_EQ("", s.substr(5, 0));
+}
+
+TEST(StringPieceTest, find) {
+    StringPiece s("mississippi");
+    EXPECT_EQ(1u, s.find(0, 'i'));
+    EXPECT_EQ(1u, s.find(1, 'i'));
+    EXPECT_EQ(4u, s.find(2, 'i'));
+    EXPECT_EQ(4u, s.find(3, 'i'));
+    EXPECT_EQ(4u, s.find(4, 'i'));
+    EXPECT_EQ(7u, s.find(5, 'i'));
+    EXPECT_EQ(7u, s.find(6, 'i'));
+    EXPECT_EQ(7u, s.find(7, 'i'));
+    EXPECT_EQ(10u, s.find(8, 'i'));
+    EXPECT_EQ(10u, s.find(9, 'i'));
+    EXPECT_EQ(10u, s.find(10, 'i'));
+    EXPECT_EQ(11u, s.find(11, 'i'));
+
+    EXPECT_EQ(11u, s.find(12, 'i'));  // search index is out of bounds.
+}
+
+TEST(StringPieceTest, find_empty) {
+    StringPiece s("");
+    EXPECT_EQ(0u, s.find(0, 'a'));
+}
+
+TEST(SplitIteratorTest, split) {
+    {
+        StringPiece s("");
+        SplitIterator it(s, ',');
+        EXPECT_FALSE(it.hasNext());
+    }
+    {
+        StringPiece s("abcde");
+        SplitIterator it(s, ',');
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("abcde", it.next());
+        EXPECT_FALSE(it.hasNext());
+    }
+    {
+        StringPiece s("a,bb,ccc,dddd,eeeee");
+        SplitIterator it(s, ',');
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("a", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("bb", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("ccc", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("dddd", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("eeeee", it.next());
+        EXPECT_FALSE(it.hasNext());
+    }
+    {
+        StringPiece s(",,,,");
+        SplitIterator it(s, ',');
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_FALSE(it.hasNext());
+    }
+    {
+        StringPiece s(",a,,b,");
+        SplitIterator it(s, ',');
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("a", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("b", it.next());
+        EXPECT_TRUE(it.hasNext());
+        EXPECT_EQ("", it.next());
+        EXPECT_FALSE(it.hasNext());
+    }
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/MinikinFont.cpp b/tests/unittest/TestMain.cpp
similarity index 66%
rename from libs/minikin/MinikinFont.cpp
rename to tests/unittest/TestMain.cpp
index 6bf6a4a..6680fbd 100644
--- a/libs/minikin/MinikinFont.cpp
+++ b/tests/unittest/TestMain.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
-#include <minikin/MinikinFont.h>
-#include "HbFontCache.h"
-#include "MinikinInternal.h"
+#include "ICUEnvironment.h"
 
-namespace minikin {
+#include <gtest/gtest.h>
 
-MinikinFont::~MinikinFont() {
-    android::AutoMutex _l(gMinikinLock);
-    purgeHbFontLocked(this);
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ::testing::AddGlobalTestEnvironment(new minikin::ICUEnvironment);
+    return RUN_ALL_TESTS();
 }
-
-}  // namespace minikin
diff --git a/tests/unittest/UnicodeUtilsTest.cpp b/tests/unittest/UnicodeUtilsTest.cpp
index 9932723..9bdd07b 100644
--- a/tests/unittest/UnicodeUtilsTest.cpp
+++ b/tests/unittest/UnicodeUtilsTest.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
-
 #include "UnicodeUtils.h"
 
+#include <gtest/gtest.h>
+
 namespace minikin {
 
 TEST(UnicodeUtils, parse) {
@@ -34,4 +34,4 @@
     EXPECT_EQ(buf[3], 'a');
 }
 
-} // namespace minikin
+}  // namespace minikin
diff --git a/tests/unittest/WordBreakerTests.cpp b/tests/unittest/WordBreakerTests.cpp
index 13e0420..394c64e 100644
--- a/tests/unittest/WordBreakerTests.cpp
+++ b/tests/unittest/WordBreakerTests.cpp
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Minikin"
+#include "WordBreaker.h"
 
-#include <android/log.h>
+#include <cstdio>
+
 #include <gtest/gtest.h>
-
-#include "ICUTestBase.h"
-#include "UnicodeUtils.h"
-#include <minikin/WordBreaker.h>
 #include <unicode/locid.h>
 #include <unicode/uclean.h>
 #include <unicode/udata.h>
 
+#include "UnicodeUtils.h"
+
 #ifndef NELEM
 #define NELEM(x) ((sizeof(x) / sizeof((x)[0])))
 #endif
@@ -34,100 +33,95 @@
 
 namespace minikin {
 
-typedef ICUTestBase WordBreakerTest;
-
-TEST_F(WordBreakerTest, basic) {
-    uint16_t buf[] = {'h', 'e', 'l', 'l' ,'o', ' ', 'w', 'o', 'r', 'l', 'd'};
+TEST(WordBreakerTest, basic) {
+    uint16_t buf[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(6, breaker.next());  // after "hello "
-    EXPECT_EQ(0, breaker.wordStart());  // "hello"
+    EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), 0));  // after "hello "
+    EXPECT_EQ(0, breaker.wordStart());                              // "hello"
     EXPECT_EQ(5, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ(6, breaker.current());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(6, breaker.wordStart());  // "world"
+    EXPECT_EQ(6, breaker.wordStart());               // "world"
     EXPECT_EQ(11, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ(11, breaker.current());
 }
 
-TEST_F(WordBreakerTest, softHyphen) {
-    uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l' ,'o', ' ', 'w', 'o', 'r', 'l', 'd'};
+TEST(WordBreakerTest, softHyphen) {
+    uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(7, breaker.next());  // after "hel{SOFT HYPHEN}lo "
+    // after "hel{SOFT HYPHEN}lo "
+    EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());  // "hel{SOFT HYPHEN}lo"
     EXPECT_EQ(6, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(7, breaker.wordStart());  // "world"
+    EXPECT_EQ(7, breaker.wordStart());               // "world"
     EXPECT_EQ(12, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, hardHyphen) {
+TEST(WordBreakerTest, hardHyphen) {
     // Hyphens should not allow breaks anymore.
     uint16_t buf[] = {'s', 'u', 'g', 'a', 'r', '-', 'f', 'r', 'e', 'e'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());
+    EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, postfixAndPrefix) {
-    uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5}; // US¢ JP¥
+TEST(WordBreakerTest, postfixAndPrefix) {
+    uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5};  // US¢ JP¥
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
 
-    EXPECT_EQ(4, breaker.next());  // after CENT SIGN
-    EXPECT_EQ(0, breaker.wordStart());  // "US¢"
+    EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0));  // after CENT SIGN
+    EXPECT_EQ(0, breaker.wordStart());                              // "US¢"
     EXPECT_EQ(3, breaker.wordEnd());
 
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end of string
-    EXPECT_EQ(4, breaker.wordStart());  // "JP¥"
+    EXPECT_EQ(4, breaker.wordStart());               // "JP¥"
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, myanmarKinzi) {
+TEST(WordBreakerTest, myanmarKinzi) {
     uint16_t buf[] = {0x1004, 0x103A, 0x1039, 0x1000, 0x102C};  // NGA, ASAT, VIRAMA, KA, UU
     WordBreaker breaker;
     icu::Locale burmese("my");
-    breaker.setLocale(burmese);
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
 
-    EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end of string
+    // end of string
+    EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, zwjEmojiSequences) {
+TEST(WordBreakerTest, zwjEmojiSequences) {
     uint16_t buf[] = {
-        // man + zwj + heart + zwj + man
-        UTF16(0x1F468), 0x200D, 0x2764, 0x200D, UTF16(0x1F468),
-        // woman + zwj + heart + zwj + kiss mark + zwj + woman
-        UTF16(0x1F469), 0x200D, 0x2764, 0x200D, UTF16(0x1F48B), 0x200D, UTF16(0x1F469),
-        // eye + zwj + left speech bubble
-        UTF16(0x1F441), 0x200D, UTF16(0x1F5E8),
-        // CAT FACE + zwj + BUST IN SILHOUETTE
-        UTF16(0x1F431), 0x200D, UTF16(0x1F464),
+            // man + zwj + heart + zwj + man
+            UTF16(0x1F468), 0x200D, 0x2764, 0x200D, UTF16(0x1F468),
+            // woman + zwj + heart + zwj + kiss mark + zwj + woman
+            UTF16(0x1F469), 0x200D, 0x2764, 0x200D, UTF16(0x1F48B), 0x200D, UTF16(0x1F469),
+            // eye + zwj + left speech bubble
+            UTF16(0x1F441), 0x200D, UTF16(0x1F5E8),
+            // CAT FACE + zwj + BUST IN SILHOUETTE
+            UTF16(0x1F431), 0x200D, UTF16(0x1F464),
     };
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(7, breaker.next());  // after man + zwj + heart + zwj + man
+    // after man + zwj + heart + zwj + man
+    EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ(7, breaker.wordEnd());
     EXPECT_EQ(17, breaker.next());  // after woman + zwj + heart + zwj + woman
@@ -141,16 +135,17 @@
     EXPECT_EQ(27, breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, emojiWithModifier) {
+TEST(WordBreakerTest, emojiWithModifier) {
     uint16_t buf[] = {
-        UTF16(0x1F466), UTF16(0x1F3FB),  // boy + type 1-2 fitzpatrick modifier
-        0x270C, 0xFE0F, UTF16(0x1F3FF)  // victory hand + emoji style + type 6 fitzpatrick modifier
+            UTF16(0x1F466), UTF16(0x1F3FB),  // boy + type 1-2 fitzpatrick modifier
+            0x270C, 0xFE0F,
+            UTF16(0x1F3FF)  // victory hand + emoji style + type 6 fitzpatrick modifier
     };
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(4, breaker.next());  // after boy + type 1-2 fitzpatrick modifier
+    // after boy + type 1-2 fitzpatrick modifier
+    EXPECT_EQ(4, breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ(4, breaker.wordEnd());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
@@ -158,23 +153,22 @@
     EXPECT_EQ(8, breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, unicode10Emoji) {
+TEST(WordBreakerTest, unicode10Emoji) {
     // Should break between emojis.
     uint16_t buf[] = {
-        // SLED + SLED
-        UTF16(0x1F6F7), UTF16(0x1F6F7),
-        // SLED + VS15 + SLED
-        UTF16(0x1F6F7), 0xFE0E, UTF16(0x1F6F7),
-        // WHITE SMILING FACE + SLED
-        0x263A, UTF16(0x1F6F7),
-        // WHITE SMILING FACE + VS16 + SLED
-        0x263A, 0xFE0F, UTF16(0x1F6F7),
+            // SLED + SLED
+            UTF16(0x1F6F7), UTF16(0x1F6F7),
+            // SLED + VS15 + SLED
+            UTF16(0x1F6F7), 0xFE0E, UTF16(0x1F6F7),
+            // WHITE SMILING FACE + SLED
+            0x263A, UTF16(0x1F6F7),
+            // WHITE SMILING FACE + VS16 + SLED
+            0x263A, 0xFE0F, UTF16(0x1F6F7),
     };
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getEnglish());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(2, breaker.next());
+    EXPECT_EQ(2, breaker.followingWithLocale(Locale("en"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ(2, breaker.wordEnd());
 
@@ -207,7 +201,7 @@
     EXPECT_EQ(16, breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, flagsSequenceSingleFlag) {
+TEST(WordBreakerTest, flagsSequenceSingleFlag) {
     const std::string kFlag = "U+1F3F4";
     const std::string flags = kFlag + " " + kFlag;
 
@@ -219,10 +213,10 @@
     ParseUnicode(buf, BUF_SIZE, flags.c_str(), &size, nullptr);
 
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, size);
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(kFlagLength, breaker.next());  // end of the first flag
+    // end of the first flag
+    EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ(kFlagLength, breaker.wordEnd());
     EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());
@@ -230,7 +224,7 @@
     EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, flagsSequence) {
+TEST(WordBreakerTest, flagsSequence) {
     // U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F is emoji tag sequence for the flag
     // of Scotland.
     const std::string kFlagSequence = "U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F";
@@ -244,10 +238,10 @@
     ParseUnicode(buf, BUF_SIZE, flagSequence.c_str(), &size, nullptr);
 
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, size);
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(kFlagLength, breaker.next());  // end of the first flag sequence
+    // end of the first flag sequence
+    EXPECT_EQ(kFlagLength, breaker.followingWithLocale(Locale("en-US"), 0));
     EXPECT_EQ(0, breaker.wordStart());
     EXPECT_EQ(kFlagLength, breaker.wordEnd());
     EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());
@@ -255,50 +249,47 @@
     EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, punct) {
-    uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l' ,'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
-        '!', '!'};
+TEST(WordBreakerTest, punct) {
+    uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l', 'o', ',',
+                      ' ',    'w',    'o', 'r', 'l', 'd', '!', '!'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(9, breaker.next());  // after "¡¡hello, "
-    EXPECT_EQ(2, breaker.wordStart());  // "hello"
+    EXPECT_EQ(9, breaker.followingWithLocale(Locale("en-US"), 0));  // after "¡¡hello, "
+    EXPECT_EQ(2, breaker.wordStart());                              // "hello"
     EXPECT_EQ(7, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(9, breaker.wordStart());  // "world"
+    EXPECT_EQ(9, breaker.wordStart());               // "world"
     EXPECT_EQ(14, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, email) {
-    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
-        ' ', 'x'};
+TEST(WordBreakerTest, email) {
+    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p',
+                      'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(11, breaker.next());  // after "foo@example"
+    EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0));  // after "foo@example"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(16, breaker.next());  // after ".com "
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(16, breaker.wordStart());  // "x"
+    EXPECT_EQ(16, breaker.wordStart());              // "x"
     EXPECT_EQ(17, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, mailto) {
-    uint16_t buf[] = {'m', 'a', 'i', 'l', 't', 'o', ':', 'f', 'o', 'o', '@',
-        'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
+TEST(WordBreakerTest, mailto) {
+    uint16_t buf[] = {'m', 'a', 'i', 'l', 't', 'o', ':', 'f', 'o', 'o', '@', 'e',
+                      'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(7, breaker.next());  // after "mailto:"
+    EXPECT_EQ(7, breaker.followingWithLocale(Locale("en-US"), 0));  // after "mailto:"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(18, breaker.next());  // after "foo@example"
@@ -308,78 +299,74 @@
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(23, breaker.wordStart());  // "x"
+    EXPECT_EQ(23, breaker.wordStart());              // "x"
     EXPECT_EQ(24, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
 // The current logic always places a line break after a detected email address or URL
 // and an immediately following non-ASCII character.
-TEST_F(WordBreakerTest, emailNonAscii) {
-    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
-        0x4E00};
+TEST(WordBreakerTest, emailNonAscii) {
+    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm',
+                      'p', 'l', 'e', '.', 'c', 'o', 'm', 0x4E00};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(11, breaker.next());  // after "foo@example"
+    EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0));  // after "foo@example"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(15, breaker.next());  // after ".com"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(15, breaker.wordStart());  // "一"
+    EXPECT_EQ(15, breaker.wordStart());              // "一"
     EXPECT_EQ(16, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, emailCombining) {
-    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
-        0x0303, ' ', 'x'};
+TEST(WordBreakerTest, emailCombining) {
+    uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a',    'm', 'p',
+                      'l', 'e', '.', 'c', 'o', 'm', 0x0303, ' ', 'x'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(11, breaker.next());  // after "foo@example"
+    EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 0));  // after "foo@example"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(17, breaker.next());  // after ".com̃ "
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(17, breaker.wordStart());  // "x"
+    EXPECT_EQ(17, breaker.wordStart());              // "x"
     EXPECT_EQ(18, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, lonelyAt) {
+TEST(WordBreakerTest, lonelyAt) {
     uint16_t buf[] = {'a', ' ', '@', ' ', 'b'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(2, breaker.next());  // after "a "
-    EXPECT_EQ(0, breaker.wordStart());  // "a"
+    EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0));  // after "a "
+    EXPECT_EQ(0, breaker.wordStart());                              // "a"
     EXPECT_EQ(1, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ(4, breaker.next());  // after "@ "
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(4, breaker.wordStart());  // "b"
+    EXPECT_EQ(4, breaker.wordStart());               // "b"
     EXPECT_EQ(5, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, url) {
-    uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
-        '.', 'c', 'o', 'm', ' ', 'x'};
+TEST(WordBreakerTest, url) {
+    uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a',
+                      'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(5, breaker.next());  // after "http:"
+    EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0));  // after "http:"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(7, breaker.next());  // after "//"
@@ -392,20 +379,20 @@
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
     EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
-    EXPECT_EQ(19, breaker.wordStart());  // "x"
+    EXPECT_EQ(19, breaker.wordStart());              // "x"
     EXPECT_EQ(20, breaker.wordEnd());
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
 // Breaks according to section 14.12 of Chicago Manual of Style, *URLs or DOIs and line breaks*
-TEST_F(WordBreakerTest, urlBreakChars) {
-    uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/', '~', 'c', ',', 'd',
-        '-', 'e', '?', 'f', '=', 'g', '&', 'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'};
+TEST(WordBreakerTest, urlBreakChars) {
+    uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/',
+                      '~', 'c', ',', 'd', '-', 'e', '?', 'f', '=', 'g', '&',
+                      'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(5, breaker.next());  // after "http:"
+    EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0));  // after "http:"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(1, breaker.breakBadness());
     EXPECT_EQ(7, breaker.next());  // after "//"
@@ -458,13 +445,12 @@
     EXPECT_EQ(0, breaker.breakBadness());
 }
 
-TEST_F(WordBreakerTest, urlNoHyphenBreak) {
+TEST(WordBreakerTest, urlNoHyphenBreak) {
     uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '-', '/', 'b'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(5, breaker.next());  // after "http:"
+    EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0));  // after "http:"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(7, breaker.next());  // after "//"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
@@ -474,13 +460,12 @@
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, urlEndsWithSlash) {
+TEST(WordBreakerTest, urlEndsWithSlash) {
     uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '/'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ(5, breaker.next());  // after "http:"
+    EXPECT_EQ(5, breaker.followingWithLocale(Locale("en-US"), 0));  // after "http:"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
     EXPECT_EQ(7, breaker.next());  // after "//"
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
@@ -490,14 +475,166 @@
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
 }
 
-TEST_F(WordBreakerTest, emailStartsWithSlash) {
+TEST(WordBreakerTest, emailStartsWithSlash) {
     uint16_t buf[] = {'/', 'a', '@', 'b'};
     WordBreaker breaker;
-    breaker.setLocale(icu::Locale::getUS());
     breaker.setText(buf, NELEM(buf));
     EXPECT_EQ(0, breaker.current());
-    EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end
+    EXPECT_EQ((ssize_t)NELEM(buf), breaker.followingWithLocale(Locale("en-US"), 0));  // end
     EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
 }
 
+TEST(WordBreakerTest, setLocaleInsideUrl) {
+    std::vector<uint16_t> buf = utf8ToUtf16("Hello http://abc/d.html World");
+    WordBreaker breaker;
+    breaker.setText(buf.data(), buf.size());
+    EXPECT_EQ(0, breaker.current());
+    EXPECT_EQ(6, breaker.followingWithLocale(Locale("en-US"), 0));  // after "Hello "
+    EXPECT_EQ(0, breaker.wordStart());
+    EXPECT_EQ(5, breaker.wordEnd());
+
+    EXPECT_EQ(6, breaker.current());
+    EXPECT_EQ(11, breaker.next());  // after "http:"
+
+    // Restart from middle point of the URL. It should return the same previous break point.
+    EXPECT_EQ(11, breaker.followingWithLocale(Locale("en-US"), 6));  // after "http:"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+    EXPECT_EQ(13, breaker.next());  // after "//"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+    // Restart from middle point of the URL. It should return the same previous break point.
+    EXPECT_EQ(13, breaker.followingWithLocale(Locale("en-US"), 12));  // after "//"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+    EXPECT_EQ(16, breaker.next());  // after "abc"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+    EXPECT_EQ(18, breaker.next());  // after "/d"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+    EXPECT_EQ(24, breaker.next());  // after ".html"
+    EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());
+
+    EXPECT_EQ(29, breaker.next());  // after "World"
+    EXPECT_EQ(24, breaker.wordStart());
+    EXPECT_EQ(29, breaker.wordEnd());
+}
+
+// b/68669534
+TEST(WordBreakerTest, spaceAfterSpace) {
+    const std::vector<uint16_t> SPACES = {
+            '\t',    // TAB
+            0x1680,  // OGHAM SPACE MARK
+            0x3000,  // IDEOGRAPHIC SPACE
+    };
+
+    constexpr uint16_t CHAR_SPACE = 0x0020;
+
+    for (uint16_t sp : SPACES) {
+        char msg[64] = {};
+        snprintf(msg, sizeof(msg), "Test Space: U+%04X", sp);
+        SCOPED_TRACE(msg);
+
+        std::vector<uint16_t> buf = {'a', CHAR_SPACE, sp, 'b'};
+        WordBreaker breaker;
+        breaker.setText(buf.data(), buf.size());
+
+        EXPECT_EQ(0, breaker.current());
+        EXPECT_EQ(2, breaker.followingWithLocale(Locale("en-US"), 0));  // after "a "
+        EXPECT_EQ(0, breaker.wordStart());
+        EXPECT_EQ(1, breaker.wordEnd());
+
+        EXPECT_EQ(2, breaker.current());
+        EXPECT_EQ(3, breaker.next());  // after CHAR_SPACE character.
+        EXPECT_EQ(2, breaker.wordStart());
+        EXPECT_EQ(2, breaker.wordEnd());
+
+        EXPECT_EQ(3, breaker.current());
+        EXPECT_EQ(4, breaker.next());  // after sp character.
+        EXPECT_EQ(3, breaker.wordStart());
+        EXPECT_EQ(4, breaker.wordEnd());
+    }
+}
+
+class TestableICULineBreakerPoolImpl : public ICULineBreakerPoolImpl {
+public:
+    TestableICULineBreakerPoolImpl() : ICULineBreakerPoolImpl() {}
+
+    using ICULineBreakerPoolImpl::getPoolSize;
+    using ICULineBreakerPoolImpl::MAX_POOL_SIZE;
+};
+
+TEST(WordBreakerTest, LineBreakerPool_acquire_without_release) {
+    TestableICULineBreakerPoolImpl pool;
+
+    const Locale enUS("en-Latn-US");
+    const Locale frFR("fr-Latn-FR");
+
+    // All following three breakers must be the different instances.
+    ICULineBreakerPool::Slot enUSBreaker = pool.acquire(enUS);
+    ICULineBreakerPool::Slot enUSBreaker2 = pool.acquire(enUS);
+    ICULineBreakerPool::Slot frFRBreaker = pool.acquire(frFR);
+
+    EXPECT_NE(nullptr, enUSBreaker.breaker.get());
+    EXPECT_NE(nullptr, enUSBreaker2.breaker.get());
+    EXPECT_NE(nullptr, frFRBreaker.breaker.get());
+
+    EXPECT_NE(enUSBreaker.breaker.get(), enUSBreaker2.breaker.get());
+    EXPECT_NE(enUSBreaker.breaker.get(), frFRBreaker.breaker.get());
+    EXPECT_NE(enUSBreaker2.breaker.get(), frFRBreaker.breaker.get());
+
+    EXPECT_EQ(enUSBreaker.localeId, enUSBreaker2.localeId);
+    EXPECT_NE(enUSBreaker.localeId, frFRBreaker.localeId);
+    EXPECT_NE(enUSBreaker2.localeId, frFRBreaker.localeId);
+}
+
+TEST(WordBreakerTest, LineBreakerPool_acquire_with_release) {
+    TestableICULineBreakerPoolImpl pool;
+
+    const Locale enUS("en-Latn-US");
+    const Locale frFR("fr-Latn-FR");
+
+    // All following three breakers must be the different instances.
+    ICULineBreakerPool::Slot enUSBreaker = pool.acquire(enUS);
+
+    uint64_t enUSBreakerLocaleId = enUSBreaker.localeId;
+    icu::BreakIterator* enUSBreakerPtr = enUSBreaker.breaker.get();
+
+    pool.release(std::move(enUSBreaker));
+    EXPECT_EQ(nullptr, enUSBreaker.breaker.get());
+
+    // acquire must return a different instance if the locale is different.
+    ICULineBreakerPool::Slot frFRBreaker = pool.acquire(frFR);
+    EXPECT_NE(enUSBreakerPtr, frFRBreaker.breaker.get());
+    EXPECT_NE(enUSBreakerLocaleId, frFRBreaker.localeId);
+
+    // acquire must return the same instance as released before if the locale is the same.
+    ICULineBreakerPool::Slot enUSBreaker2 = pool.acquire(enUS);
+    EXPECT_EQ(enUSBreakerPtr, enUSBreaker2.breaker.get());
+    EXPECT_EQ(enUSBreakerLocaleId, enUSBreaker2.localeId);
+}
+
+TEST(WordBreakerTest, LineBreakerPool_exceeds_pool_size) {
+    const size_t MAX_POOL_SIZE = TestableICULineBreakerPoolImpl::MAX_POOL_SIZE;
+    TestableICULineBreakerPoolImpl pool;
+
+    const Locale enUS("en-Latn-US");
+
+    ICULineBreakerPool::Slot slots[MAX_POOL_SIZE * 2];
+
+    // Make pool full.
+    for (size_t i = 0; i < MAX_POOL_SIZE * 2; i++) {
+        slots[i] = pool.acquire(enUS);
+        EXPECT_EQ(0U, pool.getPoolSize());
+    }
+
+    for (size_t i = 0; i < MAX_POOL_SIZE; i++) {
+        pool.release(std::move(slots[i]));
+        EXPECT_EQ(i + 1, pool.getPoolSize());
+    }
+
+    for (size_t i = MAX_POOL_SIZE; i < MAX_POOL_SIZE * 2; i++) {
+        pool.release(std::move(slots[i]));
+        EXPECT_EQ(MAX_POOL_SIZE, pool.getPoolSize());
+    }
+}
+
 }  // namespace minikin
diff --git a/tests/util/Android.bp b/tests/util/Android.bp
index 50a2cd7..561643b 100644
--- a/tests/util/Android.bp
+++ b/tests/util/Android.bp
@@ -3,9 +3,11 @@
     srcs: [
         "FileUtils.cpp",
         "FontTestUtils.cpp",
-        "MinikinFontForTest.cpp",
+        "FreeTypeMinikinFontForTest.cpp",
+        "PathUtils.cpp",
         "UnicodeUtils.cpp",
     ],
+    cflags: ["-Wall", "-Werror"],
     export_include_dirs: ["."],
     shared_libs: ["libxml2"],
     static_libs: ["libminikin"],
diff --git a/tests/util/FileUtils.cpp b/tests/util/FileUtils.cpp
index 68cc45c..76a7b9a 100644
--- a/tests/util/FileUtils.cpp
+++ b/tests/util/FileUtils.cpp
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-#include <cutils/log.h>
+#include "FileUtils.h"
 
-#include <stdio.h>
 #include <sys/stat.h>
-
+#include <cstdio>
 #include <string>
 #include <vector>
 
+#include <cutils/log.h>
+
 std::vector<uint8_t> readWholeFile(const std::string& filePath) {
     FILE* fp = fopen(filePath.c_str(), "r");
     LOG_ALWAYS_FATAL_IF(fp == nullptr);
diff --git a/tests/util/FileUtils.h b/tests/util/FileUtils.h
index 1e66d1b..3aabc0d 100644
--- a/tests/util/FileUtils.h
+++ b/tests/util/FileUtils.h
@@ -14,5 +14,7 @@
  * limitations under the License.
  */
 
-std::vector<uint8_t> readWholeFile(const std::string& filePath);
+#include <string>
+#include <vector>
 
+std::vector<uint8_t> readWholeFile(const std::string& filePath);
diff --git a/tests/util/FontTestUtils.cpp b/tests/util/FontTestUtils.cpp
index 13360d4..8aaac3d 100644
--- a/tests/util/FontTestUtils.cpp
+++ b/tests/util/FontTestUtils.cpp
@@ -17,19 +17,38 @@
 #define LOG_TAG "Minikin"
 
 #include <libxml/tree.h>
+#include <log/log.h>
 #include <unistd.h>
 
-#include <log/log.h>
+#include "minikin/FontCollection.h"
+#include "minikin/FontFamily.h"
+#include "minikin/LocaleList.h"
 
-#include "FontLanguage.h"
-#include "MinikinFontForTest.h"
-#include <minikin/FontCollection.h>
-#include <minikin/FontFamily.h>
+#include "FontTestUtils.h"
+#include "FreeTypeMinikinFontForTest.h"
+#include "LocaleListCache.h"
+#include "MinikinInternal.h"
 
 namespace minikin {
 
-std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir, const char* fontXml) {
-    xmlDoc* doc = xmlReadFile(fontXml, NULL, 0);
+namespace {
+std::string xmlTrim(const std::string& in) {
+    if (in.empty()) {
+        return in;
+    }
+    const char XML_SPACES[] = "\u0020\u000D\u000A\u0009";
+    const size_t start = in.find_first_not_of(XML_SPACES);  // inclusive
+    const size_t end = in.find_last_not_of(XML_SPACES);     // inclusive
+    MINIKIN_ASSERT(start != std::string::npos, "Not a valid file name \"%s\"", in.c_str());
+    MINIKIN_ASSERT(end != std::string::npos, "Not a valid file name \"%s\"", in.c_str());
+    return in.substr(start, end - start + 1 /* +1 since end is inclusive */);
+}
+
+}  // namespace
+
+std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& fontDir,
+                                                         const std::string& xmlPath) {
+    xmlDoc* doc = xmlReadFile(xmlPath.c_str(), NULL, 0);
     xmlNode* familySet = xmlDocGetRootElement(doc);
 
     std::vector<std::shared_ptr<FontFamily>> families;
@@ -39,12 +58,12 @@
         }
 
         xmlChar* variantXmlch = xmlGetProp(familyNode, (const xmlChar*)"variant");
-        int variant = VARIANT_DEFAULT;
+        FontFamily::Variant variant = FontFamily::Variant::DEFAULT;
         if (variantXmlch) {
             if (xmlStrcmp(variantXmlch, (const xmlChar*)"elegant") == 0) {
-                variant = VARIANT_ELEGANT;
+                variant = FontFamily::Variant::ELEGANT;
             } else if (xmlStrcmp(variantXmlch, (const xmlChar*)"compact") == 0) {
-                variant = VARIANT_COMPACT;
+                variant = FontFamily::Variant::COMPACT;
             }
         }
 
@@ -54,28 +73,33 @@
                 continue;
             }
 
-            int weight = atoi((const char*)(xmlGetProp(fontNode, (const xmlChar*)"weight"))) / 100;
-            bool italic = xmlStrcmp(
-                    xmlGetProp(fontNode, (const xmlChar*)"style"), (const xmlChar*)"italic") == 0;
+            uint16_t weight = atoi((const char*)(xmlGetProp(fontNode, (const xmlChar*)"weight")));
+            FontStyle::Slant italic = static_cast<FontStyle::Slant>(
+                    xmlStrcmp(xmlGetProp(fontNode, (const xmlChar*)"style"),
+                              (const xmlChar*)"italic") == 0);
             xmlChar* index = xmlGetProp(familyNode, (const xmlChar*)"index");
 
             xmlChar* fontFileName = xmlNodeListGetString(doc, fontNode->xmlChildrenNode, 1);
-            std::string fontPath = fontDir + std::string((const char*)fontFileName);
+            const std::string fontPath = xmlTrim(fontDir + std::string((const char*)fontFileName));
             xmlFree(fontFileName);
 
+            // TODO: Support font variation axis.
+
             if (access(fontPath.c_str(), R_OK) != 0) {
                 ALOGW("%s is not found.", fontPath.c_str());
                 continue;
             }
 
+            FontStyle style(weight, italic);
             if (index == nullptr) {
                 std::shared_ptr<MinikinFont> minikinFont =
-                        std::make_shared<MinikinFontForTest>(fontPath);
-                fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));
+                        std::make_shared<FreeTypeMinikinFontForTest>(fontPath);
+                fonts.push_back(Font::Builder(minikinFont).setStyle(style).build());
             } else {
                 std::shared_ptr<MinikinFont> minikinFont =
-                        std::make_shared<MinikinFontForTest>(fontPath, atoi((const char*)index));
-                fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));
+                        std::make_shared<FreeTypeMinikinFontForTest>(fontPath,
+                                                                     atoi((const char*)index));
+                fonts.push_back(Font::Builder(minikinFont).setStyle(style).build());
             }
         }
 
@@ -84,8 +108,7 @@
         if (lang == nullptr) {
             family = std::make_shared<FontFamily>(variant, std::move(fonts));
         } else {
-            uint32_t langId = FontStyle::registerLanguageList(
-                    std::string((const char*)lang, xmlStrlen(lang)));
+            uint32_t langId = registerLocaleList(std::string((const char*)lang, xmlStrlen(lang)));
             family = std::make_shared<FontFamily>(langId, variant, std::move(fonts));
         }
         families.push_back(family);
@@ -93,8 +116,24 @@
     xmlFreeDoc(doc);
     return families;
 }
-std::shared_ptr<FontCollection> getFontCollection(const char* fontDir, const char* fontXml) {
-    return std::make_shared<FontCollection>(getFontFamilies(fontDir, fontXml));
+
+std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath) {
+    return std::make_shared<FontCollection>(buildFontFamily(filePath));
+}
+
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath) {
+    auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath));
+    std::vector<Font> fonts;
+    fonts.push_back(Font::Builder(font).build());
+    return std::make_shared<FontFamily>(std::move(fonts));
+}
+
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang) {
+    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));
 }
 
 }  // namespace minikin
diff --git a/tests/util/FontTestUtils.h b/tests/util/FontTestUtils.h
index dd5e586..ba85093 100644
--- a/tests/util/FontTestUtils.h
+++ b/tests/util/FontTestUtils.h
@@ -17,30 +17,49 @@
 #ifndef MINIKIN_FONT_TEST_UTILS_H
 #define MINIKIN_FONT_TEST_UTILS_H
 
-#include <minikin/FontCollection.h>
-
 #include <memory>
 
+#include "minikin/FontCollection.h"
+
+#include "PathUtils.h"
+
 namespace minikin {
 
 /**
  * Returns list of FontFamily from installed fonts.
  *
  * This function reads an XML file and makes font families.
- *
- * Caller must unref the returned pointer.
  */
-std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir, const char* fontXml);
+std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& fontDir,
+                                                         const std::string& xmlAbsPath);
 
 /**
  * Returns FontCollection from installed fonts.
  *
  * This function reads an XML file and makes font families and collections of them.
- * MinikinFontForTest is used for FontFamily creation.
- *
- * Caller must unref the returned pointer.
+ * The XML path and font files are needed to be in the test data directory.
  */
-std::shared_ptr<FontCollection> getFontCollection(const char* fontDir, const char* fontXml);
+inline std::shared_ptr<FontCollection> buildFontCollectionFromXml(const std::string& xmlPath) {
+    return std::make_shared<FontCollection>(
+            getFontFamilies(getTestDataDir(), getTestDataDir() + xmlPath));
+}
+
+/**
+ * Build new FontCollection from single file.
+ * The font file needs to be in the test data directory.
+ */
+std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath);
+
+/**
+ * Build new FontFamily from single file.
+ * The font file needs to be in the test data directory.
+ */
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath);
+
+/**
+ * Build new FontFamily from single file with locale.
+ */
+std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang);
 
 }  // namespace minikin
 #endif  // MINIKIN_FONT_TEST_UTILS_H
diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp
new file mode 100644
index 0000000..7f70772
--- /dev/null
+++ b/tests/util/FreeTypeMinikinFontForTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include "FreeTypeMinikinFontForTest.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <string>
+
+#include <ft2build.h>
+#include <log/log.h>
+#include FT_OUTLINE_H
+
+#include "minikin/MinikinFont.h"
+
+namespace minikin {
+namespace {
+
+static int uniqueId = 0;
+
+constexpr FT_Int32 LOAD_FLAG =
+        FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
+
+constexpr float FTPosToFloat(FT_Pos x) {
+    return x / 64.0;
+}
+
+constexpr FT_F26Dot6 FTFloatToF26Dot6(float x) {
+    return static_cast<FT_F26Dot6>(x * 64);
+}
+
+void loadGlyphOrDie(uint32_t glyphId, float size, FT_Face face) {
+    const FT_F26Dot6 scale = FTFloatToF26Dot6(size);
+    LOG_ALWAYS_FATAL_IF(FT_Set_Char_Size(face, scale, scale, 72 /* dpi */, 72 /* dpi */),
+                        "Failed to set character size.");
+    LOG_ALWAYS_FATAL_IF(FT_Load_Glyph(face, glyphId, LOAD_FLAG), "Failed to load glyph");
+    LOG_ALWAYS_FATAL_IF(face->glyph->format != FT_GLYPH_FORMAT_OUTLINE,
+                        "Only outline font is supported.");
+}
+
+}  // namespace
+
+FreeTypeMinikinFontForTest::FreeTypeMinikinFontForTest(const std::string& font_path, int index)
+        : MinikinFont(uniqueId++), mFontPath(font_path), mFontIndex(index) {
+    int fd = open(font_path.c_str(), O_RDONLY);
+    LOG_ALWAYS_FATAL_IF(fd == -1, "Open failed: %s", font_path.c_str());
+    struct stat st = {};
+    LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);
+    mFontSize = st.st_size;
+    mFontData = mmap(NULL, mFontSize, PROT_READ, MAP_SHARED, fd, 0);
+    LOG_ALWAYS_FATAL_IF(mFontData == nullptr);
+    close(fd);
+
+    LOG_ALWAYS_FATAL_IF(FT_Init_FreeType(&mFtLibrary), "Failed to initialize FreeType");
+
+    FT_Open_Args args;
+    args.flags = FT_OPEN_MEMORY;
+    args.memory_base = static_cast<const FT_Byte*>(mFontData);
+    args.memory_size = mFontSize;
+    LOG_ALWAYS_FATAL_IF(FT_Open_Face(mFtLibrary, &args, index, &mFtFace), "Failed to open FT_Face");
+}
+
+FreeTypeMinikinFontForTest::~FreeTypeMinikinFontForTest() {
+    FT_Done_Face(mFtFace);
+    FT_Done_FreeType(mFtLibrary);
+    munmap(mFontData, mFontSize);
+}
+
+float FreeTypeMinikinFontForTest::GetHorizontalAdvance(uint32_t glyphId, const MinikinPaint& paint,
+                                                       const FontFakery& /* fakery */) const {
+    loadGlyphOrDie(glyphId, paint.size, mFtFace);
+    return FTPosToFloat(mFtFace->glyph->advance.x);
+}
+
+void FreeTypeMinikinFontForTest::GetBounds(MinikinRect* bounds, uint32_t glyphId,
+                                           const MinikinPaint& paint,
+                                           const FontFakery& /* fakery */) const {
+    loadGlyphOrDie(glyphId, paint.size, mFtFace);
+
+    FT_BBox bbox;
+    FT_Outline_Get_CBox(&mFtFace->glyph->outline, &bbox);
+
+    bounds->mLeft = FTPosToFloat(bbox.xMin);
+    bounds->mTop = FTPosToFloat(bbox.yMax);
+    bounds->mRight = FTPosToFloat(bbox.xMax);
+    bounds->mBottom = FTPosToFloat(bbox.yMin);
+}
+
+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;
+}
+
+}  // namespace minikin
diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h
new file mode 100644
index 0000000..d63b2bf
--- /dev/null
+++ b/tests/util/FreeTypeMinikinFontForTest.h
@@ -0,0 +1,66 @@
+/*
+ * 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_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
+#define MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
+
+#include "minikin/MinikinFont.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "minikin/Macros.h"
+
+namespace minikin {
+
+class FreeTypeMinikinFontForTest : public MinikinFont {
+public:
+    FreeTypeMinikinFontForTest(const std::string& font_path, int index);
+    FreeTypeMinikinFontForTest(const std::string& font_path)
+            : FreeTypeMinikinFontForTest(font_path, 0) {}
+    virtual ~FreeTypeMinikinFontForTest();
+
+    // MinikinFont overrides.
+    float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint& paint,
+                               const FontFakery& fakery) const override;
+    void GetBounds(MinikinRect* bounds, uint32_t glyph_id, const MinikinPaint& paint,
+                   const FontFakery& fakery) const override;
+    void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint,
+                       const FontFakery& fakery) const override;
+
+    const std::string& fontPath() const { return mFontPath; }
+
+    const void* GetFontData() const { return mFontData; }
+    size_t GetFontSize() const { return mFontSize; }
+    int GetFontIndex() const { return mFontIndex; }
+    const std::vector<minikin::FontVariation>& GetAxes() const { return mAxes; }
+
+private:
+    const std::string mFontPath;
+    const int mFontIndex;
+    void* mFontData;
+    size_t mFontSize;
+    std::vector<minikin::FontVariation> mAxes;
+
+    FT_Library mFtLibrary;
+    FT_Face mFtFace;
+
+    MINIKIN_PREVENT_COPY_AND_ASSIGN(FreeTypeMinikinFontForTest);
+};
+
+}  // namespace minikin
+
+#endif  // MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H
diff --git a/tests/util/MinikinFontForTest.cpp b/tests/util/MinikinFontForTest.cpp
deleted file mode 100644
index 723e86a..0000000
--- a/tests/util/MinikinFontForTest.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Minikin"
-
-#include "MinikinFontForTest.h"
-
-#include <minikin/MinikinFont.h>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <string>
-
-#include <log/log.h>
-
-namespace minikin {
-
-static int uniqueId = 0;  // TODO: make thread safe if necessary.
-
-MinikinFontForTest::MinikinFontForTest(const std::string& font_path, int index,
-        const std::vector<FontVariation>& variations) :
-        MinikinFont(uniqueId++),
-        mFontPath(font_path),
-        mVariations(variations),
-        mFontIndex(index) {
-    int fd = open(font_path.c_str(), O_RDONLY);
-    LOG_ALWAYS_FATAL_IF(fd == -1);
-    struct stat st = {};
-    LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);
-    mFontSize = st.st_size;
-    mFontData = mmap(NULL, mFontSize, PROT_READ, MAP_SHARED, fd, 0);
-    LOG_ALWAYS_FATAL_IF(mFontData == nullptr);
-    close(fd);
-}
-
-MinikinFontForTest::~MinikinFontForTest() {
-    munmap(mFontData, mFontSize);
-}
-
-float MinikinFontForTest::GetHorizontalAdvance(uint32_t /* glyph_id */,
-        const MinikinPaint& /* paint */) const {
-    // TODO: Make mock value configurable if necessary.
-    return 10.0f;
-}
-
-void MinikinFontForTest::GetBounds(MinikinRect* bounds, uint32_t /* glyph_id */,
-        const MinikinPaint& /* paint */) const {
-    // TODO: Make mock values configurable if necessary.
-    bounds->mLeft = 0.0f;
-    bounds->mTop = 0.0f;
-    bounds->mRight = 10.0f;
-    bounds->mBottom = 10.0f;
-}
-
-std::shared_ptr<MinikinFont> MinikinFontForTest::createFontWithVariation(
-        const std::vector<FontVariation>& variations) const {
-    return std::shared_ptr<MinikinFont>(new MinikinFontForTest(mFontPath, mFontIndex, variations));
-}
-
-}  // namespace minikin
diff --git a/tests/util/MinikinFontForTest.h b/tests/util/MinikinFontForTest.h
deleted file mode 100644
index 6e230e1..0000000
--- a/tests/util/MinikinFontForTest.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
-#define MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
-
-#include <minikin/MinikinFont.h>
-
-class SkTypeface;
-
-namespace minikin {
-
-class MinikinFontForTest : public MinikinFont {
-public:
-    MinikinFontForTest(const std::string& font_path, int index,
-            const std::vector<FontVariation>& variations);
-    MinikinFontForTest(const std::string& font_path, int index)
-            : MinikinFontForTest(font_path, index, std::vector<FontVariation>()) {}
-    MinikinFontForTest(const std::string& font_path) : MinikinFontForTest(font_path, 0) {}
-    virtual ~MinikinFontForTest();
-
-    // MinikinFont overrides.
-    float GetHorizontalAdvance(uint32_t glyph_id, const MinikinPaint &paint) const;
-    void GetBounds(MinikinRect* bounds, uint32_t glyph_id,
-            const MinikinPaint& paint) const;
-
-    const std::string& fontPath() const { return mFontPath; }
-
-    const void* GetFontData() const { return mFontData; }
-    size_t GetFontSize() const { return mFontSize; }
-    int GetFontIndex() const { return mFontIndex; }
-    const std::vector<minikin::FontVariation>& GetAxes() const { return mVariations; }
-    std::shared_ptr<MinikinFont> createFontWithVariation(
-            const std::vector<FontVariation>& variations) const;
-private:
-    MinikinFontForTest() = delete;
-    MinikinFontForTest(const MinikinFontForTest&) = delete;
-    MinikinFontForTest& operator=(MinikinFontForTest&) = delete;
-
-    const std::string mFontPath;
-    const std::vector<FontVariation> mVariations;
-    const int mFontIndex;
-    void* mFontData;
-    size_t mFontSize;
-};
-
-}  // namespace minikin
-
-#endif  // MINIKIN_TEST_MINIKIN_FONT_FOR_TEST_H
diff --git a/tests/util/PathUtils.cpp b/tests/util/PathUtils.cpp
new file mode 100644
index 0000000..b08ff4b
--- /dev/null
+++ b/tests/util/PathUtils.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 "PathUtils.h"
+
+#include <cutils/log.h>
+#include <libgen.h>
+#include <unistd.h>
+
+namespace minikin {
+
+const char* SELF_EXE_PATH = "/proc/self/exe";
+
+std::string getDirname(const std::string& path) {
+    const char* result = dirname(path.c_str());
+    LOG_ALWAYS_FATAL_IF(result == nullptr, "dirname failed.");
+    return std::string(result);
+}
+
+std::string getBasename(const std::string& path) {
+    const char* result = basename(path.c_str());
+    LOG_ALWAYS_FATAL_IF(result == nullptr, "basename failed.");
+    return std::string(result);
+}
+
+std::string getTestDataDir() {
+    char buf[PATH_MAX] = {};
+    LOG_ALWAYS_FATAL_IF(readlink(SELF_EXE_PATH, buf, PATH_MAX) == -1, "readlink failed.");
+    return getDirname(buf) + "/data/";
+}
+
+}  // namespace minikin
diff --git a/tests/util/PathUtils.h b/tests/util/PathUtils.h
new file mode 100644
index 0000000..c757b84
--- /dev/null
+++ b/tests/util/PathUtils.h
@@ -0,0 +1,36 @@
+/*
+ * 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 TEST_UTILS_PATH_UTILS_H
+#define TEST_UTILS_PATH_UTILS_H
+
+#include <string>
+
+namespace minikin {
+
+std::string getDirname(const std::string& path);
+std::string getBasename(const std::string& path);
+
+// Returns test data directory.
+std::string getTestDataDir();
+
+inline std::string getTestFontPath(const std::string& fontFilePath) {
+    return getTestDataDir() + fontFilePath;
+}
+
+}  // namespace minikin
+
+#endif  // TEST_UTILS_PATH_UTILS_H
diff --git a/tests/util/UnicodeUtils.cpp b/tests/util/UnicodeUtils.cpp
index e66ff93..25716a7 100644
--- a/tests/util/UnicodeUtils.cpp
+++ b/tests/util/UnicodeUtils.cpp
@@ -14,73 +14,76 @@
  * limitations under the License.
  */
 
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#include <cutils/log.h>
 #include <unicode/utf.h>
 #include <unicode/utf8.h>
-#include <cstdlib>
-#include <cutils/log.h>
-#include <vector>
-#include <string>
+
+#include "minikin/U16StringPiece.h"
 
 namespace minikin {
 
 // src is of the form "U+1F431 | 'h' 'i'". Position of "|" gets saved to offset if non-null.
 // Size is returned in an out parameter because gtest needs a void return for ASSERT to work.
 void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size,
-        size_t* offset) {
+                  size_t* offset) {
     size_t input_ix = 0;
     size_t output_ix = 0;
     bool seen_offset = false;
 
     while (src[input_ix] != 0) {
         switch (src[input_ix]) {
-        case '\'':
-            // single ASCII char
-            LOG_ALWAYS_FATAL_IF(static_cast<uint8_t>(src[input_ix]) >= 0x80);
-            input_ix++;
-            LOG_ALWAYS_FATAL_IF(src[input_ix] == 0);
-            LOG_ALWAYS_FATAL_IF(output_ix >= buf_size);
-            buf[output_ix++] = (uint16_t)src[input_ix++];
-            LOG_ALWAYS_FATAL_IF(src[input_ix] != '\'');
-            input_ix++;
-            break;
-        case 'u':
-        case 'U': {
-            // Unicode codepoint in hex syntax
-            input_ix++;
-            LOG_ALWAYS_FATAL_IF(src[input_ix] != '+');
-            input_ix++;
-            char* endptr = (char*)src + input_ix;
-            unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16);
-            size_t num_hex_digits = endptr - (src + input_ix);
+            case '\'':
+                // single ASCII char
+                LOG_ALWAYS_FATAL_IF(static_cast<uint8_t>(src[input_ix]) >= 0x80);
+                input_ix++;
+                LOG_ALWAYS_FATAL_IF(src[input_ix] == 0);
+                LOG_ALWAYS_FATAL_IF(output_ix >= buf_size);
+                buf[output_ix++] = (uint16_t)src[input_ix++];
+                LOG_ALWAYS_FATAL_IF(src[input_ix] != '\'');
+                input_ix++;
+                break;
+            case 'u':
+            case 'U': {
+                // Unicode codepoint in hex syntax
+                input_ix++;
+                LOG_ALWAYS_FATAL_IF(src[input_ix] != '+');
+                input_ix++;
+                char* endptr = (char*)src + input_ix;
+                unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16);
+                size_t num_hex_digits = endptr - (src + input_ix);
 
-            // also triggers on invalid number syntax, digits = 0
-            LOG_ALWAYS_FATAL_IF(num_hex_digits < 4u);
-            LOG_ALWAYS_FATAL_IF(num_hex_digits > 6u);
-            LOG_ALWAYS_FATAL_IF(codepoint > 0x10FFFFu);
-            input_ix += num_hex_digits;
-            if (U16_LENGTH(codepoint) == 1) {
-                LOG_ALWAYS_FATAL_IF(output_ix + 1 > buf_size);
-                buf[output_ix++] = codepoint;
-            } else {
-                // UTF-16 encoding
-                LOG_ALWAYS_FATAL_IF(output_ix + 2 > buf_size);
-                buf[output_ix++] = U16_LEAD(codepoint);
-                buf[output_ix++] = U16_TRAIL(codepoint);
+                // also triggers on invalid number syntax, digits = 0
+                LOG_ALWAYS_FATAL_IF(num_hex_digits < 4u);
+                LOG_ALWAYS_FATAL_IF(num_hex_digits > 6u);
+                LOG_ALWAYS_FATAL_IF(codepoint > 0x10FFFFu);
+                input_ix += num_hex_digits;
+                if (U16_LENGTH(codepoint) == 1) {
+                    LOG_ALWAYS_FATAL_IF(output_ix + 1 > buf_size);
+                    buf[output_ix++] = codepoint;
+                } else {
+                    // UTF-16 encoding
+                    LOG_ALWAYS_FATAL_IF(output_ix + 2 > buf_size);
+                    buf[output_ix++] = U16_LEAD(codepoint);
+                    buf[output_ix++] = U16_TRAIL(codepoint);
+                }
+                break;
             }
-            break;
-        }
-        case ' ':
-            input_ix++;
-            break;
-        case '|':
-            LOG_ALWAYS_FATAL_IF(seen_offset);
-            LOG_ALWAYS_FATAL_IF(offset == nullptr);
-            *offset = output_ix;
-            seen_offset = true;
-            input_ix++;
-            break;
-        default:
-            LOG_ALWAYS_FATAL("Unexpected Character");
+            case ' ':
+                input_ix++;
+                break;
+            case '|':
+                LOG_ALWAYS_FATAL_IF(seen_offset);
+                LOG_ALWAYS_FATAL_IF(offset == nullptr);
+                *offset = output_ix;
+                seen_offset = true;
+                input_ix++;
+                break;
+            default:
+                LOG_ALWAYS_FATAL("Unexpected Character");
         }
     }
     LOG_ALWAYS_FATAL_IF(result_size == nullptr);
@@ -116,4 +119,23 @@
     return result;
 }
 
+std::string utf16ToUtf8(const U16StringPiece& u16String) {
+    const uint32_t textLength = u16String.size();
+    uint32_t i = 0;
+    uint32_t c = 0;
+
+    std::string out;
+    out.reserve(textLength * 4);
+
+    while (i < textLength) {
+        U16_NEXT(u16String.data(), i, textLength, c);
+
+        char buf[U8_MAX_LENGTH] = {};
+        uint32_t outIndex = 0;
+        U8_APPEND_UNSAFE(buf, outIndex, c);
+        out.append(buf, outIndex);
+    }
+    return out;
+}
+
 }  // namespace minikin
diff --git a/tests/util/UnicodeUtils.h b/tests/util/UnicodeUtils.h
index 6ce2fcb..af9d056 100644
--- a/tests/util/UnicodeUtils.h
+++ b/tests/util/UnicodeUtils.h
@@ -14,15 +14,22 @@
  * limitations under the License.
  */
 
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "minikin/U16StringPiece.h"
+
 namespace minikin {
 
 void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size,
-        size_t* offset);
+                  size_t* offset);
 
 std::vector<uint16_t> parseUnicodeStringWithOffset(const std::string& in, size_t* offset);
 std::vector<uint16_t> parseUnicodeString(const std::string& in);
 
 // Converts UTF-8 to UTF-16.
 std::vector<uint16_t> utf8ToUtf16(const std::string& text);
+std::string utf16ToUtf8(const U16StringPiece& u16String);
 
 }  // namespace minikin