Merge 25Q1 (ab/12770256) to aosp-main-future

Bug: 385190204
Merged-In: I00a1b3862c3a11d56808330bdfd4ab87fcbe0792
Change-Id: I1ef580be20ede8800514a5f419d2e1c15780cfda
diff --git a/include/minikin/Constants.h b/include/minikin/Constants.h
index eacac07..708d277 100644
--- a/include/minikin/Constants.h
+++ b/include/minikin/Constants.h
@@ -36,6 +36,7 @@
 // Axis tags
 const uint32_t TAG_wght = MakeTag('w', 'g', 'h', 't');
 const uint32_t TAG_ital = MakeTag('i', 't', 'a', 'l');
+const uint32_t TAG_slnt = MakeTag('s', 'l', 'n', 't');
 
 }  // namespace minikin
 
diff --git a/include/minikin/Debug.h b/include/minikin/Debug.h
index a08b234..223cc80 100644
--- a/include/minikin/Debug.h
+++ b/include/minikin/Debug.h
@@ -20,6 +20,8 @@
 #include <string>
 #include <vector>
 
+#include "minikin/FontVariation.h"
+
 namespace minikin {
 
 struct Point;
@@ -27,6 +29,7 @@
 struct MinikinExtent;
 struct MinikinPaint;
 struct FontFeature;
+class FontStyle;
 class Range;
 class U16StringPiece;
 class LayoutPiece;
@@ -44,6 +47,8 @@
 std::string toString(const MinikinPaint& paint);
 std::string toString(const FontFeature& feature);
 std::string toString(const std::vector<FontFeature>& features);
+std::string toString(const FontStyle& style);
+std::string toString(const VariationSettings& varSettings);
 
 }  // namespace debug
 
diff --git a/include/minikin/Font.h b/include/minikin/Font.h
index 0329cb4..22e9217 100644
--- a/include/minikin/Font.h
+++ b/include/minikin/Font.h
@@ -18,6 +18,7 @@
 #define MINIKIN_FONT_H
 
 #include <gtest/gtest_prod.h>
+#include <utils/LruCache.h>
 
 #include <atomic>
 #include <functional>
@@ -38,8 +39,6 @@
 
 namespace minikin {
 
-class Font;
-
 // Represents a single font file.
 class Font {
 public:
@@ -89,7 +88,7 @@
     void writeTo(BufferWriter* writer) const;
 
     // Create font instance with axes override.
-    Font(const std::shared_ptr<Font>& parent, const std::vector<FontVariation>& axes);
+    Font(const std::shared_ptr<Font>& parent, const VariationSettings& axes);
 
     Font(Font&& o) noexcept;
     Font& operator=(Font&& o) noexcept;
@@ -105,7 +104,10 @@
     // Returns an adjusted hb_font_t instance and MinikinFont instance.
     // Passing -1 each means do not override the current variation settings.
     HbFontUniquePtr getAdjustedFont(int wght, int ital) const;
-    const std::shared_ptr<MinikinFont>& getAdjustedTypeface(int wght, int ital) const;
+    std::shared_ptr<MinikinFont> getAdjustedTypeface(int wght, int ital) const;
+
+    HbFontUniquePtr getAdjustedFont(const VariationSettings& varSettings) const;
+    std::shared_ptr<MinikinFont> getAdjustedTypeface(const VariationSettings& varSettings) const;
 
     BufferReader typefaceMetadataReader() const { return mTypefaceMetadataReader; }
 
@@ -122,17 +124,31 @@
     class ExternalRefs {
     public:
         ExternalRefs(std::shared_ptr<MinikinFont>&& typeface, HbFontUniquePtr&& baseFont)
-                : mTypeface(std::move(typeface)), mBaseFont(std::move(baseFont)) {}
+                : mTypeface(std::move(typeface)),
+                  mBaseFont(std::move(baseFont)),
+                  mVarTypefaceCache2(16),
+                  mVarFontCache2(16) {}
 
         std::shared_ptr<MinikinFont> mTypeface;
         HbFontUniquePtr mBaseFont;
 
+        // TODO: remove wght/ital only adjusted typeface pool once redesign typeface flag
+        //       is removed.
         const std::shared_ptr<MinikinFont>& getAdjustedTypeface(int wght, int ital) const;
         HbFontUniquePtr getAdjustedFont(int wght, int ital) const;
         mutable std::mutex mMutex;
         mutable std::map<uint16_t, std::shared_ptr<MinikinFont>> mVarTypefaceCache
                 GUARDED_BY(mMutex);
         mutable std::map<uint16_t, HbFontUniquePtr> mVarFontCache GUARDED_BY(mMutex);
+
+        std::shared_ptr<MinikinFont> getAdjustedTypeface(const VariationSettings& varSettings,
+                                                         const FVarTable& fvarTable) const;
+        HbFontUniquePtr getAdjustedFont(const VariationSettings& varSettings,
+                                        const FVarTable& fvarTable) const;
+        mutable android::LruCache<VariationSettings, std::shared_ptr<MinikinFont>>
+                mVarTypefaceCache2 GUARDED_BY(mMutex);
+        mutable android::LruCache<VariationSettings, HbFontUniquePtr*> mVarFontCache2
+                GUARDED_BY(mMutex);
     };
 
     // Use Builder instead.
@@ -183,13 +199,8 @@
     }
     inline bool operator!=(const FakedFont& o) const { return !(*this == o); }
 
-    HbFontUniquePtr hbFont() const {
-        return font->getAdjustedFont(fakery.wghtAdjustment(), fakery.italAdjustment());
-    }
-
-    const std::shared_ptr<MinikinFont>& typeface() const {
-        return font->getAdjustedTypeface(fakery.wghtAdjustment(), fakery.italAdjustment());
-    }
+    HbFontUniquePtr hbFont() const;
+    std::shared_ptr<MinikinFont> typeface() const;
 
     // ownership is the enclosing FontCollection
     // FakedFont will be stored in the LayoutCache. It is not a good idea too keep font instance
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index 18635d2..d85b794 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -155,7 +155,11 @@
         int end;
     };
 
-    FakedFont getBestFont(U16StringPiece textBuf, const Run& run, FontStyle style);
+    FakedFont getBestFont(U16StringPiece textBuf, const Run& run, FontStyle style,
+                          const VariationSettings& variationSettings);
+    FakedFont getBestFont(U16StringPiece textBuf, const Run& run, FontStyle style) {
+        return getBestFont(textBuf, run, style, VariationSettings());
+    }
 
     // Perform the itemization until given max runs.
     std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId,
@@ -180,7 +184,7 @@
     // 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);
+            const VariationSettings& variations);
     // Creates new FontCollection that uses the specified families as top families and
     // families from this FontCollection as fallback.
     std::shared_ptr<FontCollection> createCollectionWithFamilies(
diff --git a/include/minikin/FontFakery.h b/include/minikin/FontFakery.h
index b98cfa8..d37a84a 100644
--- a/include/minikin/FontFakery.h
+++ b/include/minikin/FontFakery.h
@@ -17,6 +17,8 @@
 #ifndef MINIKIN_FONT_FAKERY_H
 #define MINIKIN_FONT_FAKERY_H
 
+#include "minikin/FVarTable.h"
+#include "minikin/FontStyle.h"
 #include "minikin/FontVariation.h"
 
 namespace minikin {
@@ -98,6 +100,29 @@
     const VariationSettings mVariationSettings;
 };
 
+// Merge font variation settings along with font style and returns FontFakery.
+//
+// The param baseVS is a base variation settings. It comes from font instance.
+// The param targetVS is a target variation settings. It is came from Paint settings.
+// The param baseStyle is a base font style. It is came from font instance.
+// The param targetStyle is a target font style. It is came from Paint settings.
+//
+// The basic concept of the merge strategy is use target variation settings as the first priority,
+// then use the target style second, then use the base variation settings finally.
+//
+// It works like as follows:
+// Step 1. The target font style is translated to the variation settings based on the axis
+//         availability. For example, if the font support `wght` axis, the 700 of the font weight
+//         in the target font style is translated to `wght` 700.
+// Step 2. Merge the derived variation settings and target variation settings. If there is a common
+//         tag, the value of the target variation settings is used.
+// Step 3. Merge the base variation settings and the derived variation settings in Step 2. If there
+//         is a common tag, the value of the target variation settings is used.
+//
+// The fake bold and fake italic of the FontFakery is resolved based on the font capabilities.
+FontFakery merge(const FVarTable& fvar, const VariationSettings& baseVS,
+                 const VariationSettings& targetVS, FontStyle baseStyle, FontStyle targetStyle);
+
 }  // namespace minikin
 
 #endif  // MINIKIN_FONT_FAKERY_H
diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h
index 29f81cf..b3d2839 100644
--- a/include/minikin/FontFamily.h
+++ b/include/minikin/FontFamily.h
@@ -49,7 +49,7 @@
 
     // Create FontFamily with axes override.
     static std::shared_ptr<FontFamily> create(const std::shared_ptr<FontFamily>& parent,
-                                              const std::vector<FontVariation>& axesOverride);
+                                              const VariationSettings& axesOverride);
 
     FontFamily(FontFamily&&) = default;
     FontFamily& operator=(FontFamily&&) = default;
@@ -58,7 +58,10 @@
     static void writeVector(BufferWriter* writer,
                             const std::vector<std::shared_ptr<FontFamily>>& families);
 
-    FakedFont getClosestMatch(FontStyle style) const;
+    FakedFont getClosestMatch(FontStyle style, const VariationSettings& axes) const;
+    FakedFont getClosestMatch(FontStyle style) const {
+        return getClosestMatch(style, VariationSettings());
+    }
     FakedFont getVariationFamilyAdjustment(FontStyle style) const;
 
     uint32_t localeListId() const { return mLocaleListId; }
@@ -104,14 +107,13 @@
     // Creates new FontFamily based on this family while applying font variations. Returns nullptr
     // if none of variations apply to this family.
     std::shared_ptr<FontFamily> createFamilyWithVariation(
-            const std::vector<FontVariation>& variations) const;
+            const VariationSettings& variations) const;
 
 private:
     FontFamily(uint32_t localeListId, FamilyVariant variant,
                std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback,
                bool isDefaultFallback, VariationFamilyType varFamilyType);
-    FontFamily(const std::shared_ptr<FontFamily>& parent,
-               const std::vector<FontVariation>& axesOverride);
+    FontFamily(const std::shared_ptr<FontFamily>& parent, const VariationSettings& axesOverride);
     explicit FontFamily(BufferReader* reader, const std::shared_ptr<std::vector<Font>>& fonts);
 
     void writeTo(BufferWriter* writer, uint32_t* fontIndex) const;
@@ -127,7 +129,7 @@
     // This field is empty if mParent is set. Use mParent's coverage instead.
     std::unique_ptr<SparseBitSet[]> mCmapFmt14Coverage;
     std::shared_ptr<FontFamily> mParent;
-    std::vector<FontVariation> mVarOverride;
+    VariationSettings mVarOverride;
     uint32_t mLocaleListId;  // 4 bytes
     uint32_t mFontsCount;    // 4 bytes
     // OpenType supports up to 2^16-1 (uint16) axes.
@@ -140,6 +142,7 @@
     bool mIsDefaultFallback;           // 1 byte
     VariationFamilyType mVarFamilyType;  // 1byte
 
+    bool mIsVariationFamily;
     MINIKIN_PREVENT_COPY_AND_ASSIGN(FontFamily);
 };
 
diff --git a/include/minikin/FontStyle.h b/include/minikin/FontStyle.h
index 7a9e597..5ba6c04 100644
--- a/include/minikin/FontStyle.h
+++ b/include/minikin/FontStyle.h
@@ -62,6 +62,7 @@
 
     constexpr uint16_t weight() const { return mWeight; }
     constexpr Slant slant() const { return mSlant; }
+    bool isItalic() const { return mSlant == Slant::ITALIC; }
 
     constexpr uint32_t identifier() const {
         return (static_cast<uint32_t>(weight()) << 16) | static_cast<uint32_t>(slant());
diff --git a/include/minikin/FontVariation.h b/include/minikin/FontVariation.h
index d3d405f..516fa59 100644
--- a/include/minikin/FontVariation.h
+++ b/include/minikin/FontVariation.h
@@ -18,6 +18,7 @@
 #define MINIKIN_FONT_VARIATION_H
 
 #include <cstdint>
+#include <iostream>
 
 #include "minikin/SortedPackedVector.h"
 
@@ -57,7 +58,24 @@
 }
 
 // Immutable variation settings
-using VariationSettings = SortedPackedVector<FontVariation>;
+using VariationSettings = SortedPackedVector<FontVariation, 2, uint16_t>;
+
+inline std::ostream& operator<<(std::ostream& os, const FontVariation& variation) {
+    return os << "'" << static_cast<char>(variation.axisTag >> 24)
+              << static_cast<char>(variation.axisTag >> 16)
+              << static_cast<char>(variation.axisTag >> 8) << static_cast<char>(variation.axisTag)
+              << "' " << variation.value;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const VariationSettings& varSettings) {
+    for (size_t i = 0; i < varSettings.size(); ++i) {
+        if (i != 0) {
+            os << ", ";
+        }
+        os << varSettings[i];
+    }
+    return os;
+}
 
 }  // namespace minikin
 
diff --git a/include/minikin/Hasher.h b/include/minikin/Hasher.h
index 3121c33..f0bbcc1 100644
--- a/include/minikin/Hasher.h
+++ b/include/minikin/Hasher.h
@@ -74,6 +74,24 @@
         return *this;
     }
 
+    inline Hasher& update(const VariationSettings& vars) {
+        update(vars.size());
+        for (const FontVariation& var : vars) {
+            update(var.axisTag);
+            update(var.value);
+        }
+        return *this;
+    }
+
+    template <typename V>
+    inline Hasher& updatePackedVector(const V& vec) {
+        using T = typename V::value_type;
+        for (const T& p : vec) {
+            update(p);
+        }
+        return *this;
+    }
+
     inline Hasher& updateShorts(const uint16_t* data, uint32_t length) {
         update(length);
         uint32_t i;
@@ -127,6 +145,16 @@
         return hash;
     }
 
+#ifdef __APPLE__
+    inline Hasher& update(uintptr_t data) {
+        update(static_cast<uint32_t>(data));
+        if (sizeof(uintptr_t) > sizeof(uint32_t)) {
+            update(static_cast<uint32_t>(data >> 32));
+        }
+        return *this;
+    }
+#endif
+
 private:
     uint32_t mHash;
 };
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h
index 11ae7ca..c5ccabe 100644
--- a/include/minikin/Layout.h
+++ b/include/minikin/Layout.h
@@ -132,8 +132,8 @@
     size_t nGlyphs() const { return mGlyphs.size(); }
     const Font* getFont(int i) const { return mGlyphs[i].font.font.get(); }
     const std::shared_ptr<Font>& getFontRef(int i) const { return mGlyphs[i].font.font; }
-    FontFakery getFakery(int i) const { return mGlyphs[i].font.fakery; }
-    const std::shared_ptr<MinikinFont>& typeface(int i) const { return mGlyphs[i].font.typeface(); }
+    const FontFakery& getFakery(int i) const { return mGlyphs[i].font.fakery; }
+    std::shared_ptr<MinikinFont> typeface(int i) const { return mGlyphs[i].font.typeface(); }
     unsigned int getGlyphId(int i) const { return mGlyphs[i].glyph_id; }
     float getX(int i) const { return mGlyphs[i].x; }
     float getY(int i) const { return mGlyphs[i].y; }
diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h
index fe44370..40812ac 100644
--- a/include/minikin/LayoutCache.h
+++ b/include/minikin/LayoutCache.h
@@ -26,6 +26,7 @@
 #include "minikin/Hasher.h"
 #include "minikin/LayoutCore.h"
 #include "minikin/MinikinPaint.h"
+#include "minikin/PackedVector.h"
 
 #ifdef _WIN32
 #include <io.h>
@@ -37,10 +38,10 @@
 public:
     LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
                    bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
-            : mChars(text.data()),
-              mNchars(text.size()),
+            : mChars(text.data(), text.size()),
               mStart(range.getStart()),
               mCount(range.getLength()),
+              mFontFlags(paint.fontFlags),
               mId(paint.font->getId()),
               mStyle(paint.fontStyle),
               mSize(paint.size),
@@ -48,13 +49,14 @@
               mSkewX(paint.skewX),
               mLetterSpacing(paint.letterSpacing),
               mWordSpacing(paint.wordSpacing),
-              mFontFlags(paint.fontFlags),
               mLocaleListId(paint.localeListId),
+              mVerticalText(paint.verticalText),
               mFamilyVariant(paint.familyVariant),
               mStartHyphen(startHyphen),
               mEndHyphen(endHyphen),
               mIsRtl(dir),
               mFontFeatureSettings(paint.fontFeatureSettings),
+              mVariationSettings(paint.fontVariationSettings),
               mHash(computeHash()) {}
 
     bool operator==(const LayoutCacheKey& o) const {
@@ -62,32 +64,23 @@
                mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
                mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
                mFontFlags == o.mFontFlags && mLocaleListId == o.mLocaleListId &&
-               mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
-               mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
-               mFontFeatureSettings == o.mFontFeatureSettings &&
-               !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
+               mVerticalText == o.mVerticalText && mFamilyVariant == o.mFamilyVariant &&
+               mStartHyphen == o.mStartHyphen && mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl &&
+               mFontFeatureSettings == o.mFontFeatureSettings && mChars == o.mChars &&
+               mVariationSettings == o.mVariationSettings;
     }
 
     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;
+    uint32_t getMemoryUsage() const {
+        return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mChars.size();
     }
-    void freeText() {
-        delete[] mChars;
-        mChars = NULL;
-        mFontFeatureSettings.clear();
-    }
-
-    uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; }
 
 private:
-    const uint16_t* mChars;
-    uint32_t mNchars;
-    uint32_t mStart;
-    uint32_t mCount;
+    PackedVector<uint16_t, 12> mChars;
+    uint8_t mStart;
+    uint8_t mCount;
+    uint8_t mFontFlags;
     uint32_t mId;  // for the font collection
     FontStyle mStyle;
     float mSize;
@@ -95,13 +88,14 @@
     float mSkewX;
     float mLetterSpacing;
     float mWordSpacing;
-    int32_t mFontFlags;
     uint32_t mLocaleListId;
+    bool mVerticalText;
     FamilyVariant mFamilyVariant;
     StartHyphenEdit mStartHyphen;
     EndHyphenEdit mEndHyphen;
     bool mIsRtl;
-    std::vector<FontFeature> mFontFeatureSettings;
+    PackedVector<FontFeature> mFontFeatureSettings;
+    VariationSettings mVariationSettings;
     // Note: any fields added to MinikinPaint must also be reflected here.
     // TODO: language matching (possibly integrate into style)
     android::hash_t mHash;
@@ -116,14 +110,16 @@
                 .update(mScaleX)
                 .update(mSkewX)
                 .update(mLetterSpacing)
+                .update(mVerticalText)
                 .update(mWordSpacing)
                 .update(mFontFlags)
                 .update(mLocaleListId)
                 .update(static_cast<uint8_t>(mFamilyVariant))
                 .update(packHyphenEdit(mStartHyphen, mEndHyphen))
                 .update(mIsRtl)
-                .updateShorts(mChars, mNchars)
-                .update(mFontFeatureSettings)
+                .updateShorts(mChars.data(), mChars.size())
+                .updatePackedVector(mFontFeatureSettings)
+                .update(mVariationSettings)
                 .hash();
     }
 };
@@ -189,9 +185,6 @@
                 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<LayoutSlot> slot;
         if (boundsCalculation) {
@@ -228,10 +221,7 @@
 
 private:
     // callback for OnEntryRemoved
-    void operator()(LayoutCacheKey& key, LayoutSlot*& value) {
-        key.freeText();
-        delete value;
-    }
+    void operator()(LayoutCacheKey&, LayoutSlot*& value) { delete value; }
 
     android::LruCache<LayoutCacheKey, LayoutSlot*> mCache GUARDED_BY(mMutex);
 
diff --git a/include/minikin/LayoutCore.h b/include/minikin/LayoutCore.h
index 134cc48..e576365 100644
--- a/include/minikin/LayoutCore.h
+++ b/include/minikin/LayoutCore.h
@@ -27,6 +27,7 @@
 #include "minikin/MinikinExtent.h"
 #include "minikin/MinikinFont.h"
 #include "minikin/MinikinRect.h"
+#include "minikin/PackedVector.h"
 #include "minikin/Point.h"
 #include "minikin/Range.h"
 #include "minikin/U16StringPiece.h"
@@ -35,6 +36,12 @@
 
 struct MinikinPaint;
 
+using FontIndexVector = PackedVector<uint8_t, 12>;
+using GlyphIdVector = PackedVector<uint16_t, 12>;
+using PointVector = PackedVector<Point>;
+using ClusterVector = PackedVector<uint8_t, 12>;
+using AdvanceVector = PackedVector<float>;
+
 // Immutable, recycle-able layout result.
 class LayoutPiece {
 public:
@@ -43,10 +50,8 @@
     ~LayoutPiece();
 
     // Low level accessors.
-    const std::vector<uint8_t>& fontIndices() const { return mFontIndices; }
-    const std::vector<uint32_t>& glyphIds() const { return mGlyphIds; }
-    const std::vector<Point>& points() const { return mPoints; }
-    const std::vector<float>& advances() const { return mAdvances; }
+    const PointVector& points() const { return mPoints; }
+    const AdvanceVector& advances() const { return mAdvances; }
     float advance() const { return mAdvance; }
     const MinikinExtent& extent() const { return mExtent; }
     const std::vector<FakedFont>& fonts() const { return mFonts; }
@@ -58,6 +63,7 @@
     uint32_t glyphIdAt(int glyphPos) const { return mGlyphIds[glyphPos]; }
     const Point& pointAt(int glyphPos) const { return mPoints[glyphPos]; }
     uint16_t clusterAt(int glyphPos) const { return mClusters[glyphPos]; }
+    bool isVerticalText() const { return mVerticalText; }
 
     uint32_t getMemoryUsage() const {
         return sizeof(uint8_t) * mFontIndices.size() + sizeof(uint32_t) * mGlyphIds.size() +
@@ -70,16 +76,17 @@
 private:
     FRIEND_TEST(LayoutTest, doLayoutWithPrecomputedPiecesTest);
 
-    std::vector<uint8_t> mFontIndices;      // per glyph
-    std::vector<uint32_t> mGlyphIds;        // per glyph
-    std::vector<Point> mPoints;             // per glyph
-    std::vector<uint8_t> mClusters;         // per glyph
+    FontIndexVector mFontIndices;  // per glyph
+    GlyphIdVector mGlyphIds;       // per glyph
+    PointVector mPoints;           // per glyph
+    ClusterVector mClusters;       // per glyph
 
-    std::vector<float> mAdvances;  // per code units
+    AdvanceVector mAdvances;  // per code units
 
     float mAdvance;
     MinikinExtent mExtent;
     uint32_t mClusterCount;
+    bool mVerticalText;
 
     std::vector<FakedFont> mFonts;
 };
diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h
index 6d5f006..0d9dc07 100644
--- a/include/minikin/MinikinFont.h
+++ b/include/minikin/MinikinFont.h
@@ -69,10 +69,9 @@
 
     virtual int GetSourceId() const { return 0; }
 
-    virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0;
+    virtual const VariationSettings& GetAxes() const = 0;
 
-    virtual std::shared_ptr<MinikinFont> createFontWithVariation(
-            const std::vector<FontVariation>&) const {
+    virtual std::shared_ptr<MinikinFont> createFontWithVariation(const VariationSettings&) const {
         return nullptr;
     }
 };
diff --git a/include/minikin/MinikinPaint.h b/include/minikin/MinikinPaint.h
index 9705b3c..ccc605a 100644
--- a/include/minikin/MinikinPaint.h
+++ b/include/minikin/MinikinPaint.h
@@ -57,6 +57,7 @@
               fontFlags(0),
               localeListId(0),
               familyVariant(FamilyVariant::DEFAULT),
+              verticalText(false),
               fontFeatureSettings(),
               font(font) {}
 
@@ -69,6 +70,7 @@
     uint32_t localeListId;
     FontStyle fontStyle;
     FamilyVariant familyVariant;
+    bool verticalText;
     std::vector<FontFeature> fontFeatureSettings;
     std::shared_ptr<FontCollection> font;
     VariationSettings fontVariationSettings;
@@ -89,7 +91,8 @@
                fontFlags == paint.fontFlags && localeListId == paint.localeListId &&
                fontStyle == paint.fontStyle && familyVariant == paint.familyVariant &&
                fontFeatureSettings == paint.fontFeatureSettings && font.get() == paint.font.get() &&
-               fontVariationSettings == paint.fontVariationSettings;
+               fontVariationSettings == paint.fontVariationSettings &&
+               verticalText == paint.verticalText;
     }
 
     uint32_t hash() const {
@@ -104,6 +107,7 @@
                 .update(fontStyle.identifier())
                 .update(static_cast<uint8_t>(familyVariant))
                 .update(fontFeatureSettings)
+                .update(verticalText)
                 .update(font->getId())
                 .update(fontVariationSettings)
                 .hash();
diff --git a/include/minikin/PackedVector.h b/include/minikin/PackedVector.h
index e383f16..fcd551b 100644
--- a/include/minikin/PackedVector.h
+++ b/include/minikin/PackedVector.h
@@ -26,7 +26,7 @@
 
 // PackedVector optimize short term allocations for small size objects.
 // The public interfaces are following the std::vector.
-template <typename T, size_t ARRAY_SIZE = 2>
+template <typename T, size_t ARRAY_SIZE = 2, typename SIZE_TYPE = uint32_t>
 class PackedVector {
 private:
     // At least two elements of pointer array is reserved.
@@ -42,9 +42,9 @@
 
     // Constructors
     PackedVector() : mSize(0), mCapacity(ARRAY_CAPACITY) {}
-    PackedVector(const T* ptr, uint16_t size) : PackedVector() { copy(ptr, size); }
+    PackedVector(const T* ptr, SIZE_TYPE size) : PackedVector() { copy(ptr, size); }
     PackedVector(const std::vector<T>& src) : PackedVector() {
-        LOG_ALWAYS_FATAL_IF(src.size() >= std::numeric_limits<uint16_t>::max());
+        LOG_ALWAYS_FATAL_IF(src.size() >= std::numeric_limits<SIZE_TYPE>::max());
         copy(src.data(), src.size());
     }
     PackedVector(std::initializer_list<T> init) : PackedVector() {
@@ -76,12 +76,12 @@
     const T* data() const { return getPtr(); }
     T* data() { return getPtr(); }
 
-    const T& operator[](uint16_t i) const { return getPtr()[i]; }
-    T& operator[](uint16_t i) { return getPtr()[i]; }
+    const T& operator[](SIZE_TYPE i) const { return getPtr()[i]; }
+    T& operator[](SIZE_TYPE i) { return getPtr()[i]; }
 
-    void reserve(uint16_t capacity) { ensureCapacity(capacity); }
+    void reserve(SIZE_TYPE capacity) { ensureCapacity(capacity); }
 
-    void resize(uint16_t size, T value = T()) {
+    void resize(SIZE_TYPE size, T value = T()) {
         if (mSize == size) {
             return;
         } else if (mSize > size) {  // reduce size
@@ -106,7 +106,7 @@
         } else {  // mSize < size  // increase size
             ensureCapacity(size);
             T* ptr = getPtr();
-            for (uint16_t i = mSize; i < size; ++i) {
+            for (SIZE_TYPE i = mSize; i < size; ++i) {
                 ptr[i] = value;
             }
             mSize = size;
@@ -116,7 +116,7 @@
     void push_back(const T& x) {
         if (mSize >= mCapacity) [[unlikely]] {
             // exponential backoff
-            constexpr uint16_t kMaxIncrease = static_cast<uint16_t>(4096 / sizeof(T));
+            constexpr SIZE_TYPE kMaxIncrease = static_cast<SIZE_TYPE>(4096 / sizeof(T));
             ensureCapacity(mCapacity + std::min(mCapacity, kMaxIncrease));
         }
         *(getPtr() + mSize) = x;
@@ -153,15 +153,15 @@
 
     bool empty() const { return mSize == 0; }
 
-    uint16_t size() const { return mSize; }
-    uint16_t capacity() const { return mCapacity; }
+    SIZE_TYPE size() const { return mSize; }
+    SIZE_TYPE capacity() const { return mCapacity; }
 
 private:
     uintptr_t mArray[PTR_ARRAY_SIZE];
-    uint16_t mSize;
-    uint16_t mCapacity;
+    SIZE_TYPE mSize;
+    SIZE_TYPE mCapacity;
 
-    void copy(const T* src, uint16_t count) {
+    void copy(const T* src, SIZE_TYPE count) {
         clear();
         ensureCapacity(count);
         mSize = count;
@@ -181,7 +181,7 @@
 
     inline bool isArrayUsed() const { return mCapacity <= ARRAY_CAPACITY; }
 
-    void ensureCapacity(uint16_t capacity) {
+    void ensureCapacity(SIZE_TYPE capacity) {
         if (capacity <= mCapacity) {
             return;
         }
diff --git a/include/minikin/Point.h b/include/minikin/Point.h
index c3fe2f3..01c68ac 100644
--- a/include/minikin/Point.h
+++ b/include/minikin/Point.h
@@ -22,7 +22,6 @@
 namespace minikin {
 
 struct Point {
-    Point(float x, float y) : x(x), y(y) {}
     float x, y;
 };
 
diff --git a/include/minikin/SortedPackedVector.h b/include/minikin/SortedPackedVector.h
index f3367af..75e6a0a 100644
--- a/include/minikin/SortedPackedVector.h
+++ b/include/minikin/SortedPackedVector.h
@@ -25,11 +25,11 @@
 namespace minikin {
 
 // An immutable packed vector that elements are sorted.
-template <typename T, size_t ARRAY_SIZE = 2>
+template <typename T, size_t ARRAY_SIZE = 2, typename SIZE_TYPE = uint32_t>
 class SortedPackedVector {
 public:
     SortedPackedVector() {}
-    SortedPackedVector(const T* ptr, uint16_t count, bool sorted = false) : mPacked(ptr, count) {
+    SortedPackedVector(const T* ptr, SIZE_TYPE count, bool sorted = false) : mPacked(ptr, count) {
         if (!sorted) {
             sort();
         }
@@ -50,15 +50,15 @@
     SortedPackedVector(SortedPackedVector&& o) = default;
     SortedPackedVector& operator=(SortedPackedVector&& o) = default;
 
-    uint16_t size() const { return mPacked.size(); }
+    SIZE_TYPE size() const { return mPacked.size(); }
     bool empty() const { return size() == 0; }
 
-    const T& operator[](uint16_t i) const { return mPacked[i]; }
+    const T& operator[](SIZE_TYPE i) const { return mPacked[i]; }
     const T* data() const { return mPacked.data(); }
 
-    inline bool operator==(const SortedPackedVector<T>& o) const { return mPacked == o.mPacked; }
+    inline bool operator==(const SortedPackedVector& o) const { return mPacked == o.mPacked; }
 
-    inline bool operator!=(const SortedPackedVector<T>& o) const { return !(*this == o); }
+    inline bool operator!=(const SortedPackedVector& o) const { return !(*this == o); }
 
     inline const T* begin() const { return mPacked.begin(); }
     inline const T* end() const { return mPacked.end(); }
@@ -66,7 +66,7 @@
 private:
     void sort() { std::sort(mPacked.begin(), mPacked.end()); }
 
-    PackedVector<T, ARRAY_SIZE> mPacked;
+    PackedVector<T, ARRAY_SIZE, SIZE_TYPE> mPacked;
 };
 
 }  // namespace minikin
diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp
index d7db300..242bddd 100644
--- a/libs/minikin/Android.bp
+++ b/libs/minikin/Android.bp
@@ -76,6 +76,7 @@
         "Emoji.cpp",
         "Font.cpp",
         "FontCollection.cpp",
+        "FontFakery.cpp",
         "FontFamily.cpp",
         "FontFeatureUtils.cpp",
         "FontFileParser.cpp",
diff --git a/libs/minikin/Debug.cpp b/libs/minikin/Debug.cpp
index 8168a77..0a5ba62 100644
--- a/libs/minikin/Debug.cpp
+++ b/libs/minikin/Debug.cpp
@@ -118,6 +118,18 @@
     return ss.str();
 }
 
+std::string toString(const FontStyle& style) {
+    std::stringstream ss;
+    ss << "{ weight=" << style.weight() << ", italic=" << style.isItalic() << "}";
+    return ss.str();
+}
+
+std::string toString(const VariationSettings& varSettings) {
+    std::stringstream ss;
+    ss << varSettings;
+    return ss.str();
+}
+
 }  // namespace debug
 
 }  // namespace minikin
diff --git a/libs/minikin/FeatureFlags.h b/libs/minikin/FeatureFlags.h
index 88afdc2..36be29e 100644
--- a/libs/minikin/FeatureFlags.h
+++ b/libs/minikin/FeatureFlags.h
@@ -37,7 +37,7 @@
 #endif  //  __ANDROID__
 
 DEFINE_FEATURE_FLAG_ACCESSOROR(rust_hyphenator);
-DEFINE_FEATURE_FLAG_ACCESSOROR(typeface_redesign);
+DEFINE_FEATURE_FLAG_ACCESSOROR(typeface_redesign_readonly);
 
 }  // namespace features
 
diff --git a/libs/minikin/Font.cpp b/libs/minikin/Font.cpp
index 0d3d610..df2f4d2 100644
--- a/libs/minikin/Font.cpp
+++ b/libs/minikin/Font.cpp
@@ -22,10 +22,12 @@
 
 #include <vector>
 
+#include "FeatureFlags.h"
 #include "FontUtils.h"
 #include "LocaleListCache.h"
 #include "MinikinInternal.h"
 #include "minikin/Constants.h"
+#include "minikin/Hasher.h"
 #include "minikin/HbUtils.h"
 #include "minikin/MinikinFont.h"
 #include "minikin/MinikinFontFactory.h"
@@ -54,6 +56,10 @@
 
 }  // namespace
 
+inline android::hash_t hash_type(const VariationSettings& vars) {
+    return Hasher().update(vars).hash();
+}
+
 std::shared_ptr<Font> Font::Builder::build() {
     if (mIsWeightSet && mIsSlantSet) {
         // No need to read OS/2 header of the font file.
@@ -93,7 +99,7 @@
     MinikinFontFactory::getInstance().skip(reader);
 }
 
-Font::Font(const std::shared_ptr<Font>& parent, const std::vector<FontVariation>& axes)
+Font::Font(const std::shared_ptr<Font>& parent, const VariationSettings& axes)
         : mExternalRefsHolder(nullptr), mTypefaceMetadataReader(nullptr) {
     mStyle = parent->style();
     mLocaleListId = parent->getLocaleListId();
@@ -324,7 +330,7 @@
     return font;
 }
 
-const std::shared_ptr<MinikinFont>& Font::getAdjustedTypeface(int wght, int ital) const {
+std::shared_ptr<MinikinFont> Font::getAdjustedTypeface(int wght, int ital) const {
     return getExternalRefs()->getAdjustedTypeface(wght, ital);
 }
 
@@ -368,4 +374,82 @@
     return result_iterator->second;
 }
 
+HbFontUniquePtr Font::getAdjustedFont(const VariationSettings& axes) const {
+    return getExternalRefs()->getAdjustedFont(axes, getFVarTable());
+}
+
+HbFontUniquePtr Font::ExternalRefs::getAdjustedFont(const VariationSettings& axes,
+                                                    const FVarTable& table) const {
+    if (axes.empty()) {
+        return HbFontUniquePtr(hb_font_reference(mBaseFont.get()));
+    }
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    HbFontUniquePtr* cached = mVarFontCache2.get(axes);
+    if (cached != nullptr) {
+        return HbFontUniquePtr(hb_font_reference(cached->get()));
+    }
+
+    HbFontUniquePtr font(hb_font_create_sub_font(mBaseFont.get()));
+    std::vector<hb_variation_t> variations;
+    variations.reserve(axes.size());
+    for (const FontVariation& variation : axes) {
+        auto it = table.find(variation.axisTag);
+        if (it == table.end() || it->second.defValue == variation.value) {
+            continue;
+        }
+        variations.push_back({variation.axisTag, variation.value});
+    }
+    hb_font_set_variations(font.get(), variations.data(), variations.size());
+    mVarFontCache2.put(axes, new HbFontUniquePtr(hb_font_reference(font.get())));
+    return font;
+}
+
+std::shared_ptr<MinikinFont> Font::getAdjustedTypeface(const VariationSettings& axes) const {
+    return getExternalRefs()->getAdjustedTypeface(axes, getFVarTable());
+}
+
+std::shared_ptr<MinikinFont> Font::ExternalRefs::getAdjustedTypeface(const VariationSettings& axes,
+                                                                     const FVarTable& table) const {
+    if (axes.empty()) {
+        return mTypeface;
+    }
+
+    std::lock_guard<std::mutex> lock(mMutex);
+    const std::shared_ptr<MinikinFont>& cached = mVarTypefaceCache2.get(axes);
+    if (cached != nullptr) {
+        return cached;
+    }
+
+    std::vector<FontVariation> variations;
+    variations.reserve(axes.size());
+    for (const FontVariation& variation : axes) {
+        auto it = table.find(variation.axisTag);
+        if (it == table.end() || it->second.defValue == variation.value) {
+            continue;
+        }
+        variations.push_back({variation.axisTag, variation.value});
+    }
+    std::shared_ptr<MinikinFont> newTypeface =
+            mTypeface->createFontWithVariation(VariationSettings(variations, false));
+    mVarTypefaceCache2.put(axes, newTypeface);
+    return mVarTypefaceCache2.get(axes);
+}
+
+HbFontUniquePtr FakedFont::hbFont() const {
+    if (features::typeface_redesign_readonly()) {
+        return font->getAdjustedFont(fakery.variationSettings());
+    } else {
+        return font->getAdjustedFont(fakery.wghtAdjustment(), fakery.italAdjustment());
+    }
+}
+
+std::shared_ptr<MinikinFont> FakedFont::typeface() const {
+    if (features::typeface_redesign_readonly()) {
+        return font->getAdjustedTypeface(fakery.variationSettings());
+    } else {
+        return font->getAdjustedTypeface(fakery.wghtAdjustment(), fakery.italAdjustment());
+    }
+}
+
 }  // namespace minikin
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index ded7643..41d8948 100644
--- a/libs/minikin/FontCollection.cpp
+++ b/libs/minikin/FontCollection.cpp
@@ -151,7 +151,7 @@
     std::unordered_set<AxisTag> supportedAxesSet;
     for (size_t i = 0; i < nTypefaces; i++) {
         const std::shared_ptr<FontFamily>& family = typefaces[i];
-        if (family->getClosestMatch(defaultStyle).font == nullptr) {
+        if (family->getNumFonts() == 0) {
             continue;
         }
         const SparseBitSet& coverage = family->getCoverage();
@@ -657,7 +657,8 @@
 
         // Use this family
         MinikinExtent extent(0, 0);
-        FakedFont font = getFamilyAt(i)->getClosestMatch(paint.fontStyle);
+        FakedFont font =
+                getFamilyAt(i)->getClosestMatch(paint.fontStyle, paint.fontVariationSettings);
         font.typeface()->GetFontExtent(&extent, paint, font.fakery);
         result.extendBy(extent);
     }
@@ -684,7 +685,7 @@
         }
 
         MinikinExtent extent(0, 0);
-        FakedFont font = family.getClosestMatch(paint.fontStyle);
+        FakedFont font = family.getClosestMatch(paint.fontStyle, paint.fontVariationSettings);
         font.typeface()->GetFontExtent(&extent, paint, font.fakery);
         result.extendBy(extent);
 
@@ -696,7 +697,7 @@
     filterFamilyByLocale(requestedLocaleList, [&](const FontFamily& family) {
         // Use this family
         MinikinExtent extent(0, 0);
-        FakedFont font = family.getClosestMatch(paint.fontStyle);
+        FakedFont font = family.getClosestMatch(paint.fontStyle, paint.fontVariationSettings);
         font.typeface()->GetFontExtent(&extent, paint, font.fakery);
         result.extendBy(extent);
 
@@ -706,7 +707,8 @@
 
     // If nothing matches, use default font.
     if (!familyFound) {
-        FakedFont font = getFamilyAt(0)->getClosestMatch(paint.fontStyle);
+        FakedFont font =
+                getFamilyAt(0)->getClosestMatch(paint.fontStyle, paint.fontVariationSettings);
         font.typeface()->GetFontExtent(&result, paint, font.fakery);
     }
 
@@ -866,7 +868,8 @@
     return result;
 }
 
-FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontStyle style) {
+FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontStyle style,
+                                      const VariationSettings& variationSettings) {
     uint8_t bestIndex = 0;
     uint32_t bestScore = 0xFFFFFFFF;
 
@@ -885,7 +888,7 @@
     } else {
         bestIndex = run.familyMatch[0];
     }
-    return getFamilyAt(bestIndex)->getClosestMatch(style);
+    return getFamilyAt(bestIndex)->getClosestMatch(style, variationSettings);
 }
 
 FakedFont FontCollection::baseFontFaked(FontStyle style) {
@@ -893,7 +896,7 @@
 }
 
 std::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation(
-        const std::vector<FontVariation>& variations) {
+        const VariationSettings& variations) {
     if (variations.empty() || mSupportedAxesCount == 0) {
         return nullptr;
     }
diff --git a/libs/minikin/FontFakery.cpp b/libs/minikin/FontFakery.cpp
new file mode 100644
index 0000000..4461c6c
--- /dev/null
+++ b/libs/minikin/FontFakery.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2024 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/FontFakery.h"
+
+#include "minikin/Constants.h"
+#include "minikin/FVarTable.h"
+#include "minikin/FontStyle.h"
+#include "minikin/FontVariation.h"
+
+namespace minikin {
+
+FontFakery merge(const FVarTable& fvar, const VariationSettings& baseVS,
+                 const VariationSettings& targetVS, FontStyle baseStyle, FontStyle targetStyle) {
+    const bool hasItal = fvar.count(TAG_ital);
+    const bool hasSlnt = fvar.count(TAG_slnt);
+    const bool hasWght = fvar.count(TAG_wght);
+
+    // Reserve size of base and target plus 2 which is the upper bounds resolved axes.
+    FontVariation* adjustedVars;
+    constexpr uint32_t FIXED_BUFFER_SIZE = 8;
+    FontVariation fixedBuffer[FIXED_BUFFER_SIZE];
+    std::unique_ptr<FontVariation[]> heap;
+    if (baseVS.size() + targetVS.size() + 2 > FIXED_BUFFER_SIZE) {
+        heap = std::make_unique<FontVariation[]>(baseVS.size() + targetVS.size() + 2);
+        adjustedVars = heap.get();
+    } else {
+        adjustedVars = fixedBuffer;
+    }
+
+    // Convert target font style into font variation settings.
+    FontVariation styleVars[2];
+    uint32_t styleVarsSize = 0;
+    if (hasSlnt) {
+        if (targetStyle.slant() == FontStyle::Slant::ITALIC) {
+            styleVars[styleVarsSize++] = {TAG_slnt, -10};
+        } else {
+            styleVars[styleVarsSize++] = {TAG_slnt, 0};
+        }
+    } else if (hasItal) {
+        if (targetStyle.slant() == FontStyle::Slant::ITALIC) {
+            styleVars[styleVarsSize++] = {TAG_ital, 1};
+        } else {
+            styleVars[styleVarsSize++] = {TAG_ital, 0};
+        }
+    }
+    if (hasWght) {
+        styleVars[styleVarsSize++] = {TAG_wght, static_cast<float>(targetStyle.weight())};
+    }
+
+    // Main merge loop: do the three sorted array merge.
+    constexpr uint32_t END = 0xFFFFFFFF;
+    bool fakeBold;
+    uint32_t baseIdx = 0;
+    uint32_t targetIdx = 0;
+    uint32_t styleIdx = 0;
+
+    uint32_t adjustedHead = 0;  // head of the output vector.
+    while (baseIdx < baseVS.size() || targetIdx < targetVS.size() || styleIdx < styleVarsSize) {
+        const AxisTag baseTag = baseIdx < baseVS.size() ? baseVS[baseIdx].axisTag : END;
+        const AxisTag targetTag = targetIdx < targetVS.size() ? targetVS[targetIdx].axisTag : END;
+        const AxisTag styleTag = styleIdx < styleVarsSize ? styleVars[styleIdx].axisTag : END;
+
+        AxisTag tag;
+        float value;
+        bool styleValueUsed = false;
+        if (baseTag < targetTag) {
+            if (styleTag < baseTag) {
+                // style < base < target: only process style.
+                tag = styleTag;
+                value = styleVars[styleIdx].value;
+                styleValueUsed = true;
+                styleIdx++;
+            } else if (styleTag == baseTag) {
+                // style == base < target: process base and style. base is used.
+                tag = styleTag;
+                value = baseVS[baseIdx].value;
+                baseIdx++;
+                styleIdx++;
+            } else {
+                //  base < style < target: only process base.
+                tag = baseTag;
+                value = baseVS[baseIdx].value;
+                baseIdx++;
+            }
+        } else if (targetTag < baseTag) {
+            if (styleTag < targetTag) {
+                // style < target < base: process style only.
+                tag = styleTag;
+                value = styleVars[styleIdx].value;
+                styleValueUsed = true;
+                styleIdx++;
+            } else if (styleTag == targetTag) {
+                // style = target < base: process style and target. target is used.
+                tag = targetTag;
+                value = targetVS[targetIdx].value;
+                styleIdx++;
+                targetIdx++;
+            } else {
+                // target < style < base: process target only.
+                tag = targetTag;
+                value = targetVS[targetIdx].value;
+                targetIdx++;
+            }
+        } else {
+            if (styleTag < baseTag) {
+                // style < base == target: only process style.
+                tag = styleTag;
+                value = styleVars[styleIdx].value;
+                styleValueUsed = true;
+                styleIdx++;
+            } else if (styleTag == baseTag) {
+                //  base == target == style: process all. target is used.
+                tag = targetTag;
+                value = targetVS[targetIdx].value;
+                baseIdx++;
+                targetIdx++;
+                styleIdx++;
+            } else {
+                //  base == target < style: process base and target. target is used.
+                tag = targetTag;
+                value = targetVS[targetIdx].value;
+                baseIdx++;
+                targetIdx++;
+            }
+        }
+
+        const auto& it = fvar.find(tag);
+        if (it == fvar.end()) {
+            continue;  // unsupported axis. Skip.
+        }
+        const FVarEntry& fvarEntry = it->second;
+
+        if (styleValueUsed && value == fvarEntry.defValue) {
+            // Skip the default value if it came from style.
+            continue;
+        }
+        const float clamped = std::clamp(value, fvarEntry.minValue, fvarEntry.maxValue);
+        adjustedVars[adjustedHead++] = {tag, clamped};
+        if (tag == TAG_wght) {
+            // Fake bold is enabled when the max value is more than 200 of difference.
+            fakeBold = targetStyle.weight() >= 600 && (targetStyle.weight() - clamped) >= 200;
+        }
+    }
+
+    // Fake weight is enabled when the TAG_wght is not supported and the weight value has more than
+    // 200 of difference.
+    if (!hasWght) {
+        fakeBold =
+                targetStyle.weight() >= 600 && (targetStyle.weight() - baseStyle.weight()) >= 200;
+    }
+    // Fake italic is enabled when the style is italic and font doesn't support ital or slnt axis.
+    bool fakeItalic = false;
+    if (targetStyle.isItalic()) {
+        if (hasItal || hasSlnt) {
+            fakeItalic = false;
+        } else {
+            fakeItalic = !baseStyle.isItalic();
+        }
+    }
+    return FontFakery(fakeBold, fakeItalic, VariationSettings(adjustedVars, adjustedHead));
+}
+
+}  // namespace minikin
diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp
index d821ea0..42cd07e 100644
--- a/libs/minikin/FontFamily.cpp
+++ b/libs/minikin/FontFamily.cpp
@@ -22,6 +22,7 @@
 #include <unordered_set>
 #include <vector>
 
+#include "FeatureFlags.h"
 #include "FontUtils.h"
 #include "Locale.h"
 #include "LocaleListCache.h"
@@ -64,7 +65,7 @@
 }
 
 std::shared_ptr<FontFamily> FontFamily::create(const std::shared_ptr<FontFamily>& parent,
-                                               const std::vector<FontVariation>& axes) {
+                                               const VariationSettings& axes) {
     if (axes.empty() || parent->getSupportedAxesCount() == 0) {
         return nullptr;
     }
@@ -87,7 +88,7 @@
 }
 
 FontFamily::FontFamily(const std::shared_ptr<FontFamily>& parent,
-                       const std::vector<FontVariation>& axesOverride)
+                       const VariationSettings& axesOverride)
         : mFonts(),
           mSupportedAxes(std::make_unique<AxisTag[]>(parent->getSupportedAxesCount())),
           mCoverage(),
@@ -102,7 +103,8 @@
           mIsColorEmoji(parent->mIsColorEmoji),
           mIsCustomFallback(parent->mIsCustomFallback),
           mIsDefaultFallback(parent->mIsDefaultFallback),
-          mVarFamilyType(VariationFamilyType::None) {
+          mVarFamilyType(VariationFamilyType::None),
+          mIsVariationFamily(true) {
     // Filter only the axis supported font.
     std::vector<std::shared_ptr<Font>> overriddenFonts;
     for (uint16_t i = 0; i < mFontsCount; ++i) {
@@ -147,7 +149,8 @@
                         EmojiStyle::EMOJI),
           mIsCustomFallback(isCustomFallback),
           mIsDefaultFallback(isDefaultFallback),
-          mVarFamilyType(varFamilyType) {
+          mVarFamilyType(varFamilyType),
+          mIsVariationFamily(false) {
     MINIKIN_ASSERT(!fonts.empty(), "FontFamily must contain at least one font.");
     MINIKIN_ASSERT(fonts.size() <= std::numeric_limits<uint32_t>::max(),
                    "Number of fonts must be less than 2^32.");
@@ -183,6 +186,7 @@
     mIsCustomFallback = static_cast<bool>(reader->read<uint8_t>());
     mIsDefaultFallback = static_cast<bool>(reader->read<uint8_t>());
     mVarFamilyType = reader->read<VariationFamilyType>();
+    mIsVariationFamily = false;
     mCoverage = SparseBitSet(reader);
     // Read mCmapFmt14Coverage. As it can have null entries, it is stored in the buffer as a sparse
     // array (size, non-null entry count, array of (index, entry)).
@@ -300,7 +304,36 @@
     return FontFakery(isFakeBold, isFakeItalic);
 }
 
-FakedFont FontFamily::getClosestMatch(FontStyle style) const {
+FakedFont FontFamily::getClosestMatch(FontStyle style, const VariationSettings& axes) const {
+    if (features::typeface_redesign_readonly()) {
+        int bestIndex = 0;
+        Font* bestFont = mFonts[bestIndex].get();
+        int bestMatch = computeMatch(bestFont->style(), style);
+        for (size_t i = 1; i < mFontsCount; i++) {
+            Font* font = mFonts[i].get();
+            int match = computeMatch(font->style(), style);
+            if (i == 0 || match < bestMatch) {
+                bestFont = font;
+                bestIndex = i;
+                bestMatch = match;
+            }
+        }
+
+        if (mIsVariationFamily) {
+            // For backward compatibility reasons, we don't merge the variation settings because it
+            // is developer provided configuration.
+            return FakedFont{mFonts[bestIndex], computeFakery(style, bestFont->style())};
+        }
+
+        if (axes.empty() && style == bestFont->style()) {
+            // Easy case, no merge is necessary.
+            return FakedFont{mFonts[bestIndex], FontFakery(false, false)};
+        }
+        FontFakery fakery = merge(bestFont->getFVarTable(), bestFont->baseTypeface()->GetAxes(),
+                                  axes, bestFont->style(), style);
+        return FakedFont(mFonts[bestIndex], fakery);
+    }
+
     if (mVarFamilyType != VariationFamilyType::None) {
         return getVariationFamilyAdjustment(style);
     }
@@ -334,7 +367,7 @@
 }
 
 void FontFamily::computeCoverage() {
-    const std::shared_ptr<Font>& font = getClosestMatch(FontStyle()).font;
+    const std::shared_ptr<Font>& font = getClosestMatch(FontStyle(), VariationSettings()).font;
     HbBlob cmapTable(font->baseFont(), MakeTag('c', 'm', 'a', 'p'));
     if (cmapTable.get() == nullptr) {
         ALOGE("Could not get cmap table size!\n");
@@ -398,7 +431,7 @@
 }
 
 std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation(
-        const std::vector<FontVariation>& variations) const {
+        const VariationSettings& variations) const {
     if (variations.empty() || mSupportedAxesCount == 0) {
         return nullptr;
     }
diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp
index e612130..ca90668 100644
--- a/libs/minikin/Layout.cpp
+++ b/libs/minikin/Layout.cpp
@@ -68,7 +68,11 @@
             if (!isLetterSpacingCapableCodePoint(cp)) {
                 break;
             }
-            glyphs->at(i).x -= letterSpacingHalf;
+            if (paint.verticalText) {
+                glyphs->at(i).y -= letterSpacingHalf;
+            } else {
+                glyphs->at(i).x -= letterSpacingHalf;
+            }
         }
     }
 
@@ -87,7 +91,11 @@
 
         if (i < glyphCount) {
             for (uint32_t j = glyphCount - i; j < glyphCount; ++j) {
-                glyphs->at(j).x -= letterSpacingHalf;
+                if (paint.verticalText) {
+                    glyphs->at(j).y -= letterSpacingHalf;
+                } else {
+                    glyphs->at(j).x -= letterSpacingHalf;
+                }
             }
         }
     }
@@ -180,12 +188,21 @@
         return;
     }
     if (runFlag & RunFlag::LEFT_EDGE) {
-        bounds->mLeft -= letterSpacingHalf;
-        bounds->mRight -= letterSpacingHalf;
+        if (paint.verticalText) {
+            bounds->mTop -= letterSpacingHalf;
+            bounds->mBottom -= letterSpacingHalf;
+        } else {
+            bounds->mLeft -= letterSpacingHalf;
+            bounds->mRight -= letterSpacingHalf;
+        }
     }
 
     if (runFlag & RunFlag::RIGHT_EDGE) {
-        bounds->mRight -= letterSpacingHalf;
+        if (paint.verticalText) {
+            bounds->mBottom -= letterSpacingHalf;
+        } else {
+            bounds->mRight -= letterSpacingHalf;
+        }
     }
 }
 
@@ -232,7 +249,11 @@
                                               startHyphen, endHyphen, nullptr, advancesForRun,
                                               bounds ? &tmpBounds : nullptr, clusterCount);
         if (bounds) {
-            bounds->join(tmpBounds, advance, 0);
+            if (paint.verticalText) {
+                bounds->join(tmpBounds, 0, advance);
+            } else {
+                bounds->join(tmpBounds, advance, 0);
+            }
         }
         advance += run_advance;
     }
@@ -266,7 +287,11 @@
                 pieceStartHyphen, pieceEndHyphen, layout, advancesForRun,
                 bounds ? &tmpBounds : nullptr, clusterCount);
         if (bounds) {
-            bounds->join(tmpBounds, advance, 0);
+            if (paint.verticalText) {
+                bounds->join(tmpBounds, 0, advance);
+            } else {
+                bounds->join(tmpBounds, advance, 0);
+            }
         }
         advance += word_advance;
     }
@@ -289,11 +314,14 @@
             mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
         }
         if (mAdvances) {
-            const std::vector<float>& advances = layoutPiece.advances();
-            std::copy(advances.begin(), advances.end(), mAdvances);
+            std::copy(layoutPiece.advances().begin(), layoutPiece.advances().end(), mAdvances);
         }
         if (mBounds) {
-            mBounds->join(bounds, mTotalAdvance, 0);
+            if (layoutPiece.isVerticalText()) {
+                mBounds->join(bounds, 0, mTotalAdvance);
+            } else {
+                mBounds->join(bounds, mTotalAdvance, 0);
+            }
         }
         mTotalAdvance += layoutPiece.advance();
         mClusterCount += layoutPiece.clusterCount();
@@ -340,7 +368,9 @@
 }
 
 void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
-    if (features::typeface_redesign()) {
+    float xAdvance = src.isVerticalText() ? 0 : mAdvance;
+    float yAdvance = src.isVerticalText() ? mAdvance : 0;
+    if (features::typeface_redesign_readonly()) {
         if (src.glyphCount() == 0) {
             return;
         }
@@ -363,15 +393,15 @@
             }
 
             mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), src.clusterAt(i) + start,
-                                 mAdvance + src.pointAt(i).x, src.pointAt(i).y);
+                                 xAdvance + src.pointAt(i).x, yAdvance + src.pointAt(i).y);
         }
     } else {
         for (size_t i = 0; i < src.glyphCount(); i++) {
             mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), src.clusterAt(i) + start,
-                                 mAdvance + src.pointAt(i).x, src.pointAt(i).y);
+                                 xAdvance + src.pointAt(i).x, yAdvance + src.pointAt(i).y);
         }
     }
-    const std::vector<float>& advances = src.advances();
+    const AdvanceVector& advances = src.advances();
     for (size_t i = 0; i < advances.size(); i++) {
         mAdvances[i + start] = advances[i];
         if (i == 0) {
diff --git a/libs/minikin/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp
index b32566b..06dd709 100644
--- a/libs/minikin/LayoutCore.cpp
+++ b/libs/minikin/LayoutCore.cpp
@@ -301,10 +301,11 @@
     double size = paint.size;
     double scaleX = paint.scaleX;
 
-    std::unordered_map<const MinikinFont*, uint32_t> fontMap;
+    std::unordered_map<std::shared_ptr<MinikinFont>, uint32_t> fontMap;
 
     float x = 0;
     float y = 0;
+    float* dir = paint.verticalText ? &y : &x;
 
     constexpr uint32_t MAX_LENGTH_FOR_BITSET = 256;  // std::bit_ceil(CHAR_LIMIT_FOR_CACHE);
     std::bitset<MAX_LENGTH_FOR_BITSET> clusterSet;
@@ -315,22 +316,23 @@
          isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
          isRtl ? --run_ix : ++run_ix) {
         FontCollection::Run& run = items[run_ix];
-        FakedFont fakedFont = paint.font->getBestFont(substr, run, paint.fontStyle);
-        const std::shared_ptr<MinikinFont>& typeface = fakedFont.typeface();
-        auto it = fontMap.find(typeface.get());
+        FakedFont fakedFont =
+                paint.font->getBestFont(substr, run, paint.fontStyle, paint.fontVariationSettings);
+        std::shared_ptr<MinikinFont> typeface = fakedFont.typeface();
+        auto it = fontMap.find(typeface);
         uint8_t font_ix;
         if (it == fontMap.end()) {
             // First time to see this font.
             font_ix = mFonts.size();
             mFonts.push_back(fakedFont);
-            fontMap.insert(std::make_pair(typeface.get(), font_ix));
+            fontMap.insert(std::make_pair(typeface, font_ix));
 
             // We override some functions which are not thread safe.
             HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.hbFont().get()));
-            hb_font_set_funcs(
-                    font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
-                    new SkiaArguments({fakedFont.typeface().get(), &paint, fakedFont.fakery}),
-                    [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
+            hb_font_set_funcs(font.get(),
+                              isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
+                              new SkiaArguments({typeface.get(), &paint, fakedFont.fakery}),
+                              [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
             hbFonts.push_back(std::move(font));
         } else {
             font_ix = it->second;
@@ -380,7 +382,11 @@
 
             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);
+            if (paint.verticalText) {
+                hb_buffer_set_direction(buffer.get(), HB_DIRECTION_TTB);
+            } else {
+                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);
@@ -416,7 +422,7 @@
                 const uint32_t cp = textBuf.codePointAt(advIndex + start);
                 if (!u_iscntrl(cp)) {
                     mAdvances[advIndex] += letterSpaceHalf;
-                    x += letterSpaceHalf;
+                    *dir += letterSpaceHalf;
                 }
             }
             for (unsigned int i = 0; i < numGlyphs; i++) {
@@ -439,9 +445,9 @@
                     // To avoid rounding error, add full letter spacing when the both prev and
                     // current code point are non-control characters.
                     if (!isCtrl && !isPrevCtrl) {
-                        x += letterSpace;
+                        *dir += letterSpace;
                     } else if (!isCtrl || !isPrevCtrl) {
-                        x += letterSpaceHalf;
+                        *dir += letterSpaceHalf;
                     }
                 }
 
@@ -451,8 +457,9 @@
                 xoff += yoff * paint.skewX;
                 mFontIndices.push_back(font_ix);
                 mGlyphIds.push_back(glyph_ix);
-                mPoints.emplace_back(x + xoff, y + yoff);
-                float xAdvance = HBFixedToFloat(positions[i].x_advance);
+                mPoints.push_back({x + xoff, y + yoff});
+                float advance = paint.verticalText ? -HBFixedToFloat(positions[i].y_advance)
+                                                   : HBFixedToFloat(positions[i].x_advance);
                 mClusters.push_back(clusterBaseIndex);
                 if (useLargeSet) {
                     clusterSetForLarge.insert(clusterBaseIndex);
@@ -461,19 +468,19 @@
                 }
 
                 if (clusterBaseIndex < count) {
-                    mAdvances[clusterBaseIndex] += xAdvance;
+                    mAdvances[clusterBaseIndex] += advance;
                 } else {
                     ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
                           start, count);
                 }
-                x += xAdvance;
+                *dir += advance;
             }
             if (numGlyphs && letterSpace != 0) {
                 const uint32_t lastAdvIndex = info[numGlyphs - 1].cluster - clusterOffset;
                 const uint32_t lastCp = textBuf.codePointAt(lastAdvIndex + start);
                 if (!u_iscntrl(lastCp)) {
                     mAdvances[lastAdvIndex] += letterSpaceHalf;
-                    x += letterSpaceHalf;
+                    *dir += letterSpaceHalf;
                 }
             }
         }
@@ -482,12 +489,13 @@
     mGlyphIds.shrink_to_fit();
     mPoints.shrink_to_fit();
     mClusters.shrink_to_fit();
-    mAdvance = x;
+    mAdvance = *dir;
     if (useLargeSet) {
         mClusterCount = clusterSetForLarge.size();
     } else {
         mClusterCount = clusterSet.count();
     }
+    mVerticalText = paint.verticalText;
 }
 
 // static
diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp
index 20d5aa3..cd32bd0 100644
--- a/libs/minikin/MeasuredText.cpp
+++ b/libs/minikin/MeasuredText.cpp
@@ -39,8 +39,8 @@
 
     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint,
                     const MinikinRect& bounds) {
-        const std::vector<float>& advances = layoutPiece.advances();
-        std::copy(advances.begin(), advances.end(), mOutAdvances->begin() + mRange.getStart());
+        std::copy(layoutPiece.advances().begin(), layoutPiece.advances().end(),
+                  mOutAdvances->begin() + mRange.getStart());
 
         if (bounds.mLeft < 0 || bounds.mRight > layoutPiece.advance()) {
             for (uint32_t i : mRange) {
@@ -262,7 +262,11 @@
 
     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
                     const MinikinRect& bounds) {
-        mBounds.join(bounds, mAdvance, 0);
+        if (layoutPiece.isVerticalText()) {
+            mBounds.join(bounds, 0, mAdvance);
+        } else {
+            mBounds.join(bounds, mAdvance, 0);
+        }
         mAdvance += layoutPiece.advance();
     }
 
@@ -381,7 +385,16 @@
         }
         auto[advance, bounds] =
                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
-        rect.join(bounds, totalAdvance, 0);
+        const MinikinPaint* paint = run->getPaint();
+        if (paint != nullptr) {
+            if (paint->verticalText) {
+                rect.join(bounds, 0, totalAdvance);
+            } else {
+                rect.join(bounds, totalAdvance, 0);
+            }
+        } else {
+            rect.join(bounds, totalAdvance, 0);
+        }
         totalAdvance += advance;
     }
     return rect;
diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp
index 413dab9..724cde0 100644
--- a/libs/minikin/Measurement.cpp
+++ b/libs/minikin/Measurement.cpp
@@ -197,7 +197,11 @@
 
     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
                     const MinikinRect& bounds) {
-        mBounds.join(bounds, mAdvance, 0);
+        if (layoutPiece.isVerticalText()) {
+            mBounds.join(bounds, 0, mAdvance);
+        } else {
+            mBounds.join(bounds, mAdvance, 0);
+        }
         mAdvance += layoutPiece.advance();
     }
 
diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp
index 28e6094..8790a90 100644
--- a/tests/unittest/FontCollectionTest.cpp
+++ b/tests/unittest/FontCollectionTest.cpp
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+#include <com_android_text_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 
 #include "FontTestUtils.h"
+#include "FontVariationTestUtils.h"
 #include "FreeTypeMinikinFontForTest.h"
 #include "MinikinInternal.h"
 #include "minikin/Constants.h"
@@ -332,4 +335,44 @@
                                                            Builder().add(1).add(3).add(5).build()));
 }
 
+TEST_WITH_FLAGS(FontCollectionTest, getBestFont,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    typeface_redesign_readonly))) {
+    FreeTypeMinikinFontForTestFactory::init();
+
+    const uint32_t localeListId = registerLocaleList("en-US");
+
+    auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(
+            getTestFontPath("WeightEqualsEmVariableFont.ttf"));
+    auto font = Font::Builder(minikinFont).build();
+    auto family = FontFamily::create({font});
+    auto fc = FontCollection::create({family});
+
+    auto getBestFont = [&](FontStyle style, const VariationSettings& varSettings) -> FakedFont {
+        std::vector<uint16_t> text = {'a'};
+        auto runs = fc->itemize(text, style, localeListId, FamilyVariant::DEFAULT, 1);
+        EXPECT_EQ(1u, runs.size());
+        return fc->getBestFont(text, runs[0], style, varSettings);
+    };
+
+    EXPECT_EQ(parseVariationSettings(""),
+              getBestFont(FontStyle(), VariationSettings()).fakery.variationSettings());
+    EXPECT_EQ(parseVariationSettings("'wght' 700"),
+              getBestFont(FontStyle(FontStyle::Weight::BOLD), VariationSettings())
+                      .fakery.variationSettings());
+    EXPECT_EQ(parseVariationSettings("'wght' 700"),
+              getBestFont(FontStyle(), parseVariationSettings("'wght' 700"))
+                      .fakery.variationSettings());
+    EXPECT_EQ(parseVariationSettings("'ital' 1"),
+              getBestFont(FontStyle(FontStyle::Slant::ITALIC), VariationSettings())
+                      .fakery.variationSettings());
+    EXPECT_EQ(parseVariationSettings("'ital' 1, 'wght' 500"),
+              getBestFont(FontStyle(FontStyle::Weight::MEDIUM, FontStyle::Slant::ITALIC),
+                          VariationSettings())
+                      .fakery.variationSettings());
+    EXPECT_EQ(parseVariationSettings("'ital' 1, 'wght' 500"),
+              getBestFont(FontStyle(FontStyle::Slant::ITALIC), parseVariationSettings("'wght' 500"))
+                      .fakery.variationSettings());
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/FontFakeryTest.cpp b/tests/unittest/FontFakeryTest.cpp
index b0fe521..5422a7d 100644
--- a/tests/unittest/FontFakeryTest.cpp
+++ b/tests/unittest/FontFakeryTest.cpp
@@ -16,11 +16,35 @@
 
 #include <gtest/gtest.h>
 
+#include "FontVariationTestUtils.h"
 #include "minikin/Constants.h"
 #include "minikin/FontFakery.h"
 
 namespace minikin {
 
+namespace {
+
+constexpr FontStyle THIN = FontStyle(FontStyle::Weight::THIN, FontStyle::Slant::UPRIGHT);
+constexpr FontStyle REGULAR = FontStyle(FontStyle::Weight::NORMAL, FontStyle::Slant::UPRIGHT);
+constexpr FontStyle MEDIUM = FontStyle(FontStyle::Weight::MEDIUM, FontStyle::Slant::UPRIGHT);
+constexpr FontStyle BOLD = FontStyle(FontStyle::Weight::BOLD, FontStyle::Slant::UPRIGHT);
+constexpr FontStyle BLACK = FontStyle(FontStyle::Weight::BLACK, FontStyle::Slant::UPRIGHT);
+constexpr FontStyle ITALIC = FontStyle(FontStyle::Weight::NORMAL, FontStyle::Slant::ITALIC);
+constexpr FontStyle BOLD_ITALIC = FontStyle(FontStyle::Weight::BOLD, FontStyle::Slant::ITALIC);
+
+FontFakery merge(const FVarTable& fvar, const std::string& base, const std::string& target,
+                 FontStyle baseStyle, FontStyle targetStyle) {
+    return merge(fvar, parseVariationSettings(base), parseVariationSettings(target), baseStyle,
+                 targetStyle);
+}
+
+}  // namespace
+
+inline bool operator==(const char* expect, const VariationSettings& vs) {
+    VariationSettings expectVarSettings = parseVariationSettings(expect);
+    return expectVarSettings == vs;
+}
+
 TEST(FontFakeryTest, testConstruct) {
     EXPECT_EQ(FontFakery(), FontFakery(false, false));
     EXPECT_NE(FontFakery(), FontFakery(true, false));
@@ -57,4 +81,144 @@
     EXPECT_EQ(400, ff.variationSettings()[1].value);
 }
 
+TEST(FontFakeryTest, testMerge) {
+    FVarTable fvar = {{MakeTag('A', 'B', 'C', 'D'), {0, 100, 50}}};
+
+    // Override should be used.
+    EXPECT_EQ("'ABCD' 100", merge(fvar, "", "'ABCD' 100", REGULAR, REGULAR).variationSettings());
+    // Base should be remains
+    EXPECT_EQ("'ABCD' 0", merge(fvar, "'ABCD' 0", "", REGULAR, REGULAR).variationSettings());
+    // The default value from the target VS should be preserved.
+    EXPECT_EQ("'ABCD' 50", merge(fvar, "", "'ABCD' 50", REGULAR, REGULAR).variationSettings());
+    // Override should override the base settings.
+    EXPECT_EQ("'ABCD' 100",
+              merge(fvar, "'ABCD' 0", "'ABCD' 100", REGULAR, REGULAR).variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_twoAxes) {
+    FVarTable fvar = {{MakeTag('A', 'B', 'C', 'D'), {0, 100, 50}},
+                      {MakeTag('E', 'F', 'G', 'H'), {0, 100, 50}}};
+
+    // Different axes should be preserved.
+    EXPECT_EQ("'ABCD' 100, 'EFGH' 100",
+              merge(fvar, "'ABCD' 100", "'EFGH' 100", REGULAR, REGULAR).variationSettings());
+    // Overrides override only matched axis.
+    EXPECT_EQ(
+            "'ABCD' 0, 'EFGH' 100",
+            merge(fvar, "'ABCD' 0, 'EFGH' 0", "'EFGH' 100", REGULAR, REGULAR).variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_styleWeight) {
+    FVarTable fvar = {{TAG_wght, {100, 900, 400}}, {TAG_ital, {0, 1, 0}}};
+
+    // Default FontStyle sets wght 400 and it is dropped.
+    EXPECT_EQ("", merge(fvar, "", "", REGULAR, REGULAR).variationSettings());
+    // Use weight of FontStyle if no override is specified.
+    EXPECT_EQ("'wght' 100", merge(fvar, "", "", REGULAR, THIN).variationSettings());
+    // If override is spseicied, it is used instead of FontStyle.
+    EXPECT_EQ("'wght' 500", merge(fvar, "", "'wght' 500", REGULAR, THIN).variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_styleItal) {
+    FVarTable fvar = {{TAG_wght, {100, 900, 400}}, {TAG_ital, {0, 1, 0}}};
+
+    // Use weight of FontStyle if no override is specified.
+    EXPECT_EQ("'ital' 1", merge(fvar, "", "", REGULAR, ITALIC).variationSettings());
+    EXPECT_EQ("'ital' 1", merge(fvar, "'ital' 1", "", REGULAR, REGULAR).variationSettings());
+    EXPECT_EQ("'ital' 0", merge(fvar, "'ital' 0", "", REGULAR, ITALIC).variationSettings());
+    // If override is spseicied, it is used instead of FontStyle.
+    EXPECT_EQ("'ital' 0", merge(fvar, "", "'ital' 0", REGULAR, ITALIC).variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_styleSlnt) {
+    FVarTable fvar = {{TAG_wght, {100, 900, 400}}, {TAG_slnt, {-10, 0, 0}}};
+
+    // Use weight of FontStyle if no override is specified.
+    EXPECT_EQ("'slnt' -10", merge(fvar, "", "", REGULAR, ITALIC).variationSettings());
+    // If override is spseicied, it is used instead of FontStyle.
+    EXPECT_EQ("'slnt' 0", merge(fvar, "", "'slnt' 0", REGULAR, ITALIC).variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_complex) {
+    FVarTable fvar = {
+            {TAG_wght, {100, 900, 400}},
+            {TAG_slnt, {-10, 0, 0}},
+            {MakeTag('A', 'B', 'C', 'D'), {0, 100, 50}},
+    };
+
+    EXPECT_EQ("'wght' 750, 'slnt' -10, 'ABCD' 75",
+              merge(fvar, "'wght' 650", "'wght' 750, 'ABCD' 75", REGULAR, ITALIC)
+                      .variationSettings());
+}
+
+TEST(FontFakeryTest, testMerge_fakeBold_unsupported_font) {
+    FVarTable fvar = {};
+
+    // The same weight won't enable fake bold.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, REGULAR).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", BOLD, BOLD).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", THIN, THIN).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", BLACK, BLACK).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, ITALIC).isFakeBold());
+
+    // If the weight diff is more than 200, fake bold is enabled.
+    EXPECT_TRUE(merge(fvar, "", "", REGULAR, BOLD).isFakeBold());
+    EXPECT_TRUE(merge(fvar, "", "", REGULAR, BLACK).isFakeBold());
+    EXPECT_TRUE(merge(fvar, "", "", BOLD, BLACK).isFakeBold());
+
+    // If the requested weight is less than 600, the fake bold is not enabled.
+    EXPECT_FALSE(merge(fvar, "", "", THIN, REGULAR).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", THIN, MEDIUM).isFakeBold());
+}
+
+TEST(FontFakeryTest, testMerge_fakeBold_fullrange_font) {
+    FVarTable fvar = {{TAG_wght, {100, 900, 400}}};
+
+    // If the given font supports full range of weight, the fake bold is never enabled.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, THIN).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, REGULAR).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, MEDIUM).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BOLD).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BLACK).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, ITALIC).isFakeBold());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BOLD_ITALIC).isFakeBold());
+}
+
+TEST(FontFakeryTest, testMerge_fakeBold_limited_range_font) {
+    FVarTable fvar = {{TAG_wght, {100, 700, 400}}};
+
+    // If the weight diff from the upper limit of the weight is more than 200, fake bold is enabled.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BOLD).isFakeBold());
+    EXPECT_TRUE(merge(fvar, "", "", REGULAR, BLACK).isFakeBold());
+}
+
+TEST(FontFakeryTest, testMerge_fakeItalic_unsupported_font) {
+    FVarTable fvar = {};
+
+    // The same italic won't enable fake italic.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, REGULAR).isFakeItalic());
+    EXPECT_FALSE(merge(fvar, "", "", ITALIC, ITALIC).isFakeItalic());
+    EXPECT_FALSE(merge(fvar, "", "", BOLD_ITALIC, BOLD_ITALIC).isFakeItalic());
+
+    // If the target style is italic but base style is not, fake bold is enabled.
+    EXPECT_TRUE(merge(fvar, "", "", REGULAR, ITALIC).isFakeItalic());
+    EXPECT_TRUE(merge(fvar, "", "", REGULAR, BOLD_ITALIC).isFakeItalic());
+}
+
+TEST(FontFakeryTest, testMerge_fakeItalic_ital_font) {
+    FVarTable fvar = {{TAG_ital, {0, 1, 0}}};
+
+    // If the font supports ital tag, the fake italic is never enabled.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, ITALIC).isFakeItalic());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BOLD_ITALIC).isFakeItalic());
+}
+
+TEST(FontFakeryTest, testMerge_fakeItalic_slnt_font) {
+    FVarTable fvar = {{TAG_slnt, {-10, 0, 0}}};
+
+    // If the font supports slnt tag, the fake italic is never enabled.
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, ITALIC).isFakeItalic());
+    EXPECT_FALSE(merge(fvar, "", "", REGULAR, BOLD_ITALIC).isFakeItalic());
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/FontTest.cpp b/tests/unittest/FontTest.cpp
index ea3ca8d..a080e67 100644
--- a/tests/unittest/FontTest.cpp
+++ b/tests/unittest/FontTest.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+#include <com_android_text_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <minikin/Constants.h>
 
 #include "BufferUtils.h"
 #include "FontTestUtils.h"
+#include "FontVariationTestUtils.h"
 #include "FreeTypeMinikinFontForTest.h"
 #include "minikin/Font.h"
 
@@ -276,10 +279,10 @@
         EXPECT_NE(minikinFontBase.get(), font->baseTypeface().get());
         auto axes = minikinFontBase->GetAxes();
         ASSERT_EQ(2u, axes.size());
-        EXPECT_EQ(TAG_wght, axes[0].axisTag);
-        EXPECT_EQ(TAG_ital, axes[1].axisTag);
-        EXPECT_EQ(400, axes[0].value);
-        EXPECT_EQ(1, axes[1].value);
+        EXPECT_EQ(TAG_ital, axes[0].axisTag);
+        EXPECT_EQ(TAG_wght, axes[1].axisTag);
+        EXPECT_EQ(1, axes[0].value);
+        EXPECT_EQ(400, axes[1].value);
     }
     {
         // Override existing ital axis.
@@ -288,10 +291,10 @@
         EXPECT_NE(minikinFontBase.get(), font->baseTypeface().get());
         auto axes = minikinFontBase->GetAxes();
         ASSERT_EQ(2u, axes.size());
-        EXPECT_EQ(TAG_wght, axes[0].axisTag);
-        EXPECT_EQ(TAG_ital, axes[1].axisTag);
-        EXPECT_EQ(700, axes[0].value);
-        EXPECT_EQ(1, axes[1].value);
+        EXPECT_EQ(TAG_ital, axes[0].axisTag);
+        EXPECT_EQ(TAG_wght, axes[1].axisTag);
+        EXPECT_EQ(1, axes[0].value);
+        EXPECT_EQ(700, axes[1].value);
     }
 }
 
@@ -337,4 +340,44 @@
     EXPECT_EQ(1, italTable.maxValue);
 }
 
+FakedFont fakedFont(const std::shared_ptr<Font>& font, const std::string& varSettings) {
+    return {font, FontFakery(false, false, parseVariationSettings(varSettings))};
+}
+
+TEST_WITH_FLAGS(FontTest, FakedFont_cached_hbFont,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    typeface_redesign_readonly))) {
+    FreeTypeMinikinFontForTestFactory::init();
+
+    auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(
+            getTestFontPath("WeightEqualsEmVariableFont.ttf"));
+    std::shared_ptr<Font> font = Font::Builder(minikinFont).build();
+
+    FakedFont faked300 = fakedFont(font, "'wght' 300");
+    FakedFont faked400 = fakedFont(font, "'wght' 400");
+    FakedFont faked300_2 = fakedFont(font, "'wght' 300");
+
+    EXPECT_EQ(faked300.hbFont().get(), faked300.hbFont().get());
+    EXPECT_EQ(faked300.hbFont().get(), faked300_2.hbFont().get());
+    EXPECT_NE(faked300.hbFont().get(), faked400.hbFont().get());
+}
+
+TEST_WITH_FLAGS(FontTest, FakedFont_cached_typeface,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    typeface_redesign_readonly))) {
+    FreeTypeMinikinFontForTestFactory::init();
+
+    auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(
+            getTestFontPath("WeightEqualsEmVariableFont.ttf"));
+    std::shared_ptr<Font> font = Font::Builder(minikinFont).build();
+
+    FakedFont faked300 = fakedFont(font, "'wght' 300");
+    FakedFont faked400 = fakedFont(font, "'wght' 400");
+    FakedFont faked300_2 = fakedFont(font, "'wght' 300");
+
+    EXPECT_EQ(faked300.typeface(), faked300.typeface());
+    EXPECT_EQ(faked300.typeface(), faked300_2.typeface());
+    EXPECT_NE(faked300.typeface(), faked400.typeface());
+}
+
 }  // namespace minikin
diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp
index 4213c8e..fd0b5f2 100644
--- a/tests/unittest/LayoutTest.cpp
+++ b/tests/unittest/LayoutTest.cpp
@@ -490,7 +490,7 @@
 
 TEST_F_WITH_FLAGS(LayoutTest, testFontRun,
                   REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
-                                                      typeface_redesign))) {
+                                                      typeface_redesign_readonly))) {
     auto latinFamily = buildFontFamily("Ascii.ttf");
     auto jaFamily = buildFontFamily("Hiragana.ttf");
     const std::vector<std::shared_ptr<FontFamily>> families = {latinFamily, jaFamily};
diff --git a/tests/unittest/PackedVectorTest.cpp b/tests/unittest/PackedVectorTest.cpp
index e0f4124..bf669f9 100644
--- a/tests/unittest/PackedVectorTest.cpp
+++ b/tests/unittest/PackedVectorTest.cpp
@@ -27,14 +27,14 @@
 TEST(PackedVector, construct) {
     {
         PackedVector<int> packed;
-        EXPECT_EQ(0, packed.size());
+        EXPECT_EQ(0u, packed.size());
         EXPECT_TRUE(packed.empty());
     }
     {
         int data[] = {1, 2, 3, 4, 5};
 
         PackedVector<int> packed(data, 5);
-        EXPECT_EQ(5, packed.size());
+        EXPECT_EQ(5u, packed.size());
         EXPECT_EQ(1, packed[0]);
         EXPECT_EQ(2, packed[1]);
         EXPECT_EQ(3, packed[2]);
@@ -45,7 +45,7 @@
         int data[] = {1, 2, 3, 4, 5};
 
         PackedVector<int> packed(data + 2, 2);
-        EXPECT_EQ(2, packed.size());
+        EXPECT_EQ(2u, packed.size());
         EXPECT_EQ(3, packed[0]);
         EXPECT_EQ(4, packed[1]);
     }
@@ -53,7 +53,7 @@
         std::vector<int> data = {1, 2, 3, 4, 5};
 
         PackedVector<int> packed(data);
-        EXPECT_EQ(5, packed.size());
+        EXPECT_EQ(5u, packed.size());
         EXPECT_EQ(1, packed[0]);
         EXPECT_EQ(2, packed[1]);
         EXPECT_EQ(3, packed[2]);
@@ -66,14 +66,14 @@
     PackedVector<int> packed;
 
     packed.push_back(0);
-    EXPECT_EQ(1, packed.size());
+    EXPECT_EQ(1u, packed.size());
     EXPECT_FALSE(packed.empty());
     EXPECT_EQ(0, packed[0]);
     EXPECT_EQ(0, packed.data()[0]);
     EXPECT_EQ(0, *packed.back());
 
     packed.push_back(10);
-    EXPECT_EQ(2, packed.size());
+    EXPECT_EQ(2u, packed.size());
     EXPECT_FALSE(packed.empty());
     EXPECT_EQ(10, packed[1]);
     EXPECT_EQ(10, packed.data()[1]);
@@ -122,13 +122,13 @@
     {
         PackedVector<int> packed;
         packed.reserve(100);
-        EXPECT_EQ(0, packed.size());
-        EXPECT_EQ(100, packed.capacity());
+        EXPECT_EQ(0u, packed.size());
+        EXPECT_EQ(100u, packed.capacity());
         packed.shrink_to_fit();
-        EXPECT_EQ(0, packed.size());
+        EXPECT_EQ(0u, packed.size());
         // The PackedVector has minimum capacity for the space of pointers. So cannot expect it
         // becomes 0.
-        EXPECT_NE(100, packed.capacity());
+        EXPECT_NE(100u, packed.capacity());
     }
     {
         PackedVector<int> packed;
@@ -136,11 +136,11 @@
         for (int i = 0; i < 50; ++i) {
             packed.push_back(i);
         }
-        EXPECT_EQ(50, packed.size());
-        EXPECT_EQ(100, packed.capacity());
+        EXPECT_EQ(50u, packed.size());
+        EXPECT_EQ(100u, packed.capacity());
         packed.shrink_to_fit();
-        EXPECT_EQ(50, packed.size());
-        EXPECT_EQ(50, packed.capacity());
+        EXPECT_EQ(50u, packed.size());
+        EXPECT_EQ(50u, packed.capacity());
     }
 }
 
@@ -157,91 +157,91 @@
         // Reduction
         PackedVector<int> packed = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
         packed.resize(10);
-        EXPECT_EQ(10, packed.size());
-        EXPECT_EQ(10, packed.capacity());
+        EXPECT_EQ(10u, packed.size());
+        EXPECT_EQ(10u, packed.capacity());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), packed);
 
         packed.resize(9);
-        EXPECT_EQ(9, packed.size());
+        EXPECT_EQ(9u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8, 9}), packed);
 
         packed.resize(8);
-        EXPECT_EQ(8, packed.size());
+        EXPECT_EQ(8u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8}), packed);
 
         packed.resize(7);
-        EXPECT_EQ(7, packed.size());
+        EXPECT_EQ(7u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7}), packed);
 
         packed.resize(6);
-        EXPECT_EQ(6, packed.size());
+        EXPECT_EQ(6u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6}), packed);
 
         packed.resize(5);
-        EXPECT_EQ(5, packed.size());
+        EXPECT_EQ(5u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5}), packed);
 
         packed.resize(4);
-        EXPECT_EQ(4, packed.size());
+        EXPECT_EQ(4u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4}), packed);
 
         packed.resize(3);
-        EXPECT_EQ(3, packed.size());
+        EXPECT_EQ(3u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3}), packed);
 
         packed.resize(2);
-        EXPECT_EQ(2, packed.size());
+        EXPECT_EQ(2u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2}), packed);
 
         packed.resize(1);
-        EXPECT_EQ(1, packed.size());
+        EXPECT_EQ(1u, packed.size());
         EXPECT_EQ(PackedVector<int>({1}), packed);
 
         packed.resize(0);
-        EXPECT_EQ(0, packed.size());
+        EXPECT_EQ(0u, packed.size());
         EXPECT_EQ(PackedVector<int>({}), packed);
     }
     {
         // Expansion
         PackedVector<int> packed = {};
         packed.resize(1, 1);
-        EXPECT_EQ(1, packed.size());
+        EXPECT_EQ(1u, packed.size());
         EXPECT_EQ(PackedVector<int>({1}), packed);
 
         packed.resize(2, 2);
-        EXPECT_EQ(2, packed.size());
+        EXPECT_EQ(2u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2}), packed);
 
         packed.resize(3, 3);
-        EXPECT_EQ(3, packed.size());
+        EXPECT_EQ(3u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3}), packed);
 
         packed.resize(4, 4);
-        EXPECT_EQ(4, packed.size());
+        EXPECT_EQ(4u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4}), packed);
 
         packed.resize(5, 5);
-        EXPECT_EQ(5, packed.size());
+        EXPECT_EQ(5u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5}), packed);
 
         packed.resize(6, 6);
-        EXPECT_EQ(6, packed.size());
+        EXPECT_EQ(6u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6}), packed);
 
         packed.resize(7, 7);
-        EXPECT_EQ(7, packed.size());
+        EXPECT_EQ(7u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7}), packed);
 
         packed.resize(8, 8);
-        EXPECT_EQ(8, packed.size());
+        EXPECT_EQ(8u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8}), packed);
 
         packed.resize(9, 9);
-        EXPECT_EQ(9, packed.size());
+        EXPECT_EQ(9u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8, 9}), packed);
 
         packed.resize(10, 10);
-        EXPECT_EQ(10, packed.size());
+        EXPECT_EQ(10u, packed.size());
         EXPECT_EQ(PackedVector<int>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), packed);
     }
 }
diff --git a/tests/unittest/SortedPackedVectorTest.cpp b/tests/unittest/SortedPackedVectorTest.cpp
index 361c98a..4e25750 100644
--- a/tests/unittest/SortedPackedVectorTest.cpp
+++ b/tests/unittest/SortedPackedVectorTest.cpp
@@ -23,7 +23,7 @@
 TEST(SortedPackedVector, construct) {
     {
         auto sorted = SortedPackedVector({1, 2, 3, 4, 5});
-        EXPECT_EQ(5, sorted.size());
+        EXPECT_EQ(5u, sorted.size());
         EXPECT_EQ(1, sorted[0]);
         EXPECT_EQ(2, sorted[1]);
         EXPECT_EQ(3, sorted[2]);
@@ -32,7 +32,7 @@
     }
     {
         auto sorted = SortedPackedVector({1, 2, 3, 4, 5}, true);
-        EXPECT_EQ(5, sorted.size());
+        EXPECT_EQ(5u, sorted.size());
         EXPECT_EQ(1, sorted[0]);
         EXPECT_EQ(2, sorted[1]);
         EXPECT_EQ(3, sorted[2]);
@@ -41,7 +41,7 @@
     }
     {
         auto sorted = SortedPackedVector({2, 1, 4, 3, 5});
-        EXPECT_EQ(5, sorted.size());
+        EXPECT_EQ(5u, sorted.size());
         EXPECT_EQ(1, sorted[0]);
         EXPECT_EQ(2, sorted[1]);
         EXPECT_EQ(3, sorted[2]);
@@ -51,7 +51,7 @@
     {
         std::vector<int> vec = {2, 1, 4, 3, 5};
         auto sorted = SortedPackedVector(vec);
-        EXPECT_EQ(5, sorted.size());
+        EXPECT_EQ(5u, sorted.size());
         EXPECT_EQ(1, sorted[0]);
         EXPECT_EQ(2, sorted[1]);
         EXPECT_EQ(3, sorted[2]);
@@ -61,7 +61,7 @@
     {
         auto sorted = SortedPackedVector({1, 2, 3, 4, 5});
         auto copied = SortedPackedVector(sorted);
-        EXPECT_EQ(5, copied.size());
+        EXPECT_EQ(5u, copied.size());
         EXPECT_EQ(1, copied[0]);
         EXPECT_EQ(2, copied[1]);
         EXPECT_EQ(3, copied[2]);
@@ -71,7 +71,7 @@
     {
         auto sorted = SortedPackedVector({1, 2, 3, 4, 5});
         auto moved = SortedPackedVector(std::move(sorted));
-        EXPECT_EQ(5, moved.size());
+        EXPECT_EQ(5u, moved.size());
         EXPECT_EQ(1, moved[0]);
         EXPECT_EQ(2, moved[1]);
         EXPECT_EQ(3, moved[2]);
diff --git a/tests/util/Android.bp b/tests/util/Android.bp
index d8c153d..6f12e8b 100644
--- a/tests/util/Android.bp
+++ b/tests/util/Android.bp
@@ -7,6 +7,7 @@
     srcs: [
         "FileUtils.cpp",
         "FontTestUtils.cpp",
+        "FontVariationTestUtils.cpp",
         "FreeTypeMinikinFontForTest.cpp",
         "PathUtils.cpp",
         "UnicodeUtils.cpp",
diff --git a/tests/util/FontVariationTestUtils.cpp b/tests/util/FontVariationTestUtils.cpp
new file mode 100644
index 0000000..7ff8d7f
--- /dev/null
+++ b/tests/util/FontVariationTestUtils.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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 <hb.h>
+
+#include <vector>
+
+#include "StringPiece.h"
+#include "minikin/FontVariation.h"
+
+namespace minikin {
+
+VariationSettings parseVariationSettings(const std::string& varSettings) {
+    std::vector<FontVariation> variations;
+
+    SplitIterator it(varSettings, ',');
+    while (it.hasNext()) {
+        StringPiece var = it.next();
+
+        static hb_variation_t variation;
+        if (hb_variation_from_string(var.data(), var.size(), &variation)) {
+            variations.push_back({static_cast<AxisTag>(variation.tag), variation.value});
+        }
+    }
+    return VariationSettings(variations);
+}
+
+}  // namespace minikin
diff --git a/tests/util/FontVariationTestUtils.h b/tests/util/FontVariationTestUtils.h
new file mode 100644
index 0000000..d4ef23a
--- /dev/null
+++ b/tests/util/FontVariationTestUtils.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MINIKIN_FONT_VARIATION_TEST_UTILS_H
+#define MINIKIN_FONT_VARIATION_TEST_UTILS_H
+
+#include "minikin/FontVariation.h"
+
+namespace minikin {
+
+VariationSettings parseVariationSettings(const std::string& varSettings);
+
+}  // namespace minikin
+#endif  // MINIKIN_FONT_VARIATION_TEST_UTILS_H
diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp
index ee04662..ab0e0dd 100644
--- a/tests/util/FreeTypeMinikinFontForTest.cpp
+++ b/tests/util/FreeTypeMinikinFontForTest.cpp
@@ -58,7 +58,7 @@
 }  // namespace
 
 FreeTypeMinikinFontForTest::FreeTypeMinikinFontForTest(const std::string& font_path, int index,
-                                                       const std::vector<FontVariation>& axes)
+                                                       const VariationSettings& axes)
         : mFontPath(font_path), mFontIndex(index), mAxes(axes) {
     int fd = open(font_path.c_str(), O_RDONLY);
     LOG_ALWAYS_FATAL_IF(fd == -1, "Open failed: %s", font_path.c_str());
@@ -135,7 +135,7 @@
 }
 
 std::shared_ptr<MinikinFont> FreeTypeMinikinFontForTest::createFontWithVariation(
-        const std::vector<FontVariation>& axes) const {
+        const VariationSettings& axes) const {
     return std::make_shared<FreeTypeMinikinFontForTest>(mFontPath, mFontIndex, axes);
 }
 
diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h
index f684ec6..4b19f2d 100644
--- a/tests/util/FreeTypeMinikinFontForTest.h
+++ b/tests/util/FreeTypeMinikinFontForTest.h
@@ -34,11 +34,11 @@
 class FreeTypeMinikinFontForTest : public MinikinFont {
 public:
     FreeTypeMinikinFontForTest(const std::string& font_path, int index,
-                               const std::vector<FontVariation>& axes);
+                               const VariationSettings& axes);
     FreeTypeMinikinFontForTest(const std::string& font_path, int index)
-            : FreeTypeMinikinFontForTest(font_path, index, std::vector<FontVariation>()) {}
+            : FreeTypeMinikinFontForTest(font_path, index, VariationSettings()) {}
     FreeTypeMinikinFontForTest(const std::string& font_path)
-            : FreeTypeMinikinFontForTest(font_path, 0, std::vector<FontVariation>()) {}
+            : FreeTypeMinikinFontForTest(font_path, 0, VariationSettings()) {}
     virtual ~FreeTypeMinikinFontForTest();
 
     // MinikinFont overrides.
@@ -53,15 +53,15 @@
     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; }
-    std::shared_ptr<MinikinFont> createFontWithVariation(const std::vector<FontVariation>&) const;
+    const VariationSettings& GetAxes() const { return mAxes; }
+    std::shared_ptr<MinikinFont> createFontWithVariation(const VariationSettings&) const;
 
 private:
     const std::string mFontPath;
     const int mFontIndex;
     void* mFontData;
     size_t mFontSize;
-    std::vector<minikin::FontVariation> mAxes;
+    VariationSettings mAxes;
 
     FT_Library mFtLibrary;
     FT_Face mFtFace;