Repository: microsoft/terminal (language: cpp)

Issue:
Any use of `FindFontWithLocalizedName` is broken and requires refactoring
From #16821

> Font features and axes have multiple blocking bugs right now anyway (I mean even without this PR), so I figured that we can do a follow-up refactoring later where we fix both issues: The existing bugs and the incompatibilities introduced by this one. I'm half done making the changes to fix this and they're highly related


Discussion / hints:
@lhecker I was told this was blocking for 1.21 - are we fine shipping 1.21 with this?
No. I'm currently working on this issue.

Technically this should be a trivial change but funneling this into XAML turns out is harder than writing the renderer lol. "Oh this doesn't have a ItemsSource you need to instantiate UI elements yourself. Oh this is only a getter, needs a manual injection. Oh you can't set Items in the Click handler or it gets unresponsive. Oh you can't have generic types in a DataTemplate, needs a custom wrapper. Oh you can't reference the VM from inside a DataTemplate. Oh…" Hilarious lol.

Fix patch:
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index 34945728c03..d354bf0bcc0 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -349,7 +349,7 @@
           "description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.",
           "type": "object",
           "patternProperties": {
-            "^(([A-Za-z0-9]){4})$": {
+            "^[\\x20-\\x7E]{4}$": {
               "type": "integer"
             }
           },
@@ -359,7 +359,7 @@
           "description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.",
           "type": "object",
           "patternProperties": {
-            "^([A-Za-z]{4})$": {
+            "^[\\x20-\\x7E]{4}$": {
               "type": "number"
             }
           },
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 02e1af5802e..9698c659d42 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -959,26 +959,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         if (_renderEngine)
         {
-            std::unordered_map<std::wstring_view, uint32_t> featureMap;
-            if (const auto fontFeatures = _settings->FontFeatures())
-            {
-                featureMap.reserve(fontFeatures.Size());
-
-                for (const auto& [tag, param] : fontFeatures)
+            static constexpr auto cloneMap = [](const IFontFeatureMap& map) {
+                std::unordered_map<std::wstring_view, float> clone;
+                if (map)
                 {
-                    featureMap.emplace(tag, param);
+                    clone.reserve(map.Size());
+                    for (const auto& [tag, param] : map)
+                    {
+                        clone.emplace(tag, param);
+                    }
                 }
-            }
-            std::unordered_map<std::wstring_view, float> axesMap;
-            if (const auto fontAxes = _settings->FontAxes())
-            {
-                axesMap.reserve(fontAxes.Size());
+                return clone;
+            };
 
-                for (const auto& [axis, value] : fontAxes)
-                {
-                    axesMap.emplace(axis, value);
-                }
-            }
+            const auto fontFeatures = _settings->FontFeatures();
+            const auto fontAxes = _settings->FontAxes();
+            const auto featureMap = cloneMap(fontFeatures);
+            const auto axesMap = cloneMap(fontAxes);
 
             // TODO: MSFT:20895307 If the font doesn't exist, this doesn't
             //      actually fail. We need a way to gracefully fallback.
diff --git a/src/cascadia/TerminalControl/ControlSettings.h b/src/cascadia/TerminalControl/ControlSettings.h
index 0423b36f7b7..75d12b97070 100644
--- a/src/cascadia/TerminalControl/ControlSettings.h
+++ b/src/cascadia/TerminalControl/ControlSettings.h
@@ -10,7 +10,7 @@ Licensed under the MIT license.
 #include <conattrs.hpp>
 #include "ControlAppearance.h"
 
-using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
+using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 
 namespace winrt::Microsoft::Terminal::Control::implementation
diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl
index 29cd96b6f8e..51eaf34f90d 100644
--- a/src/cascadia/TerminalControl/IControlSettings.idl
+++ b/src/cascadia/TerminalControl/IControlSettings.idl
@@ -40,7 +40,7 @@ namespace Microsoft.Terminal.Control
         Single FontSize { get; };
         Windows.UI.Text.FontWeight FontWeight { get; };
         String Padding { get; };
-        Windows.Foundation.Collections.IMap<String, UInt32> FontFeatures { get; };
+        Windows.Foundation.Collections.IMap<String, Single> FontFeatures { get; };
         Windows.Foundation.Collections.IMap<String, Single> FontAxes { get; };
         Boolean EnableBuiltinGlyphs { get; };
         Boolean EnableColorGlyphs { get; };
diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp
index 036b045ca5c..c2823b955bc 100644
--- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp
+++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp
@@ -3,13 +3,14 @@
 
 #include "pch.h"
 #include "Appearances.h"
-#include "Appearances.g.cpp"
-#include "AxisKeyValuePair.g.cpp"
-#include "FeatureKeyValuePair.g.cpp"
 
-#include "EnumEntry.h"
 #include <LibraryResources.h>
-#include "..\WinRTUtils\inc\Utils.h"
+#include "../WinRTUtils/inc/Utils.h"
+
+#include "EnumEntry.h"
+#include "ProfileViewModel.h"
+
+#include "Appearances.g.cpp"
 
 using namespace winrt::Windows::UI::Text;
 using namespace winrt::Windows::UI::Xaml;
@@ -20,299 +21,186 @@ using namespace winrt::Windows::Foundation;
 using namespace winrt::Windows::Foundation::Collections;
 using namespace winrt::Microsoft::Terminal::Settings::Model;
 
-static constexpr std::array<std::wstring_view, 11> DefaultFeatures{
-    L"rlig",
-    L"locl",
-    L"ccmp",
-    L"calt",
-    L"liga",
-    L"clig",
-    L"rnrn",
-    L"kern",
-    L"mark",
-    L"mkmk",
-    L"dist"
+// These features are enabled by default by DWrite, so if a user adds them,
+// we initialize the setting to a value of 1 instead of 0.
+static constexpr std::array s_defaultFeatures{
+    DWRITE_MAKE_FONT_FEATURE_TAG('c', 'a', 'l', 't'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('c', 'c', 'm', 'p'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('c', 'l', 'i', 'g'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('d', 'i', 's', 't'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('k', 'e', 'r', 'n'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('l', 'i', 'g', 'a'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('l', 'o', 'c', 'l'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('m', 'a', 'r', 'k'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('m', 'k', 'm', 'k'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('r', 'l', 'i', 'g'),
+    DWRITE_MAKE_FONT_FEATURE_TAG('r', 'n', 'r', 'n'),
 };
 
 namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
 {
-    Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> Font::FontAxesTagsAndNames()
+    struct TagToStringImpl
     {
-        if (!_fontAxesTagsAndNames)
+        explicit TagToStringImpl(uint32_t tag) noexcept
         {
-            wil::com_ptr<IDWriteFont> font;
-            THROW_IF_FAILED(_family->GetFont(0, font.put()));
-            wil::com_ptr<IDWriteFontFace> fontFace;
-            THROW_IF_FAILED(font->CreateFontFace(fontFace.put()));
-            wil::com_ptr<IDWriteFontFace5> fontFace5;
-            if (fontFace5 = fontFace.try_query<IDWriteFontFace5>())
-            {
-                wil::com_ptr<IDWriteFontResource> fontResource;
-                THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.put()));
+            _buffer[0] = static_cast<wchar_t>((tag >> 0) & 0xFF);
+            _buffer[1] = static_cast<wchar_t>((tag >> 8) & 0xFF);
+            _buffer[2] = static_cast<wchar_t>((tag >> 16) & 0xFF);
+            _buffer[3] = static_cast<wchar_t>((tag >> 24) & 0xFF);
+            _buffer[4] = 0;
+        }
 
-                const auto axesCount = fontFace5->GetFontAxisValueCount();
-                if (axesCount > 0)
-                {
-                    std::vector<DWRITE_FONT_AXIS_VALUE> axesVector(axesCount);
-                    fontFace5->GetFontAxisValues(axesVector.data(), axesCount);
+        operator std::wstring_view() const noexcept
+        {
+            return { &_buffer[0], 4 };
+        }
 
-                    uint32_t localeIndex;
-                    BOOL localeExists;
-                    wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
-                    const auto localeToTry = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) ? localeName : L"en-US";
+    private:
+        wchar_t _buffer[5];
+    };
 
-                    std::unordered_map<winrt::hstring, winrt::hstring> fontAxesTagsAndNames;
-                    for (uint32_t i = 0; i < axesCount; ++i)
-                    {
-                        wil::com_ptr<IDWriteLocalizedStrings> names;
-                        THROW_IF_FAILED(fontResource->GetAxisNames(i, names.put()));
-
-                        if (!SUCCEEDED(names->FindLocaleName(localeToTry, &localeIndex, &localeExists)) || !localeExists)
-                        {
-                            // default to the first locale in the list
-                            localeIndex = 0;
-                        }
-
-                        UINT32 length = 0;
-                        if (SUCCEEDED(names->GetStringLength(localeIndex, &length)))
-                        {
-                            winrt::impl::hstring_builder builder{ length };
-                            if (SUCCEEDED(names->GetString(localeIndex, builder.data(), length + 1)))
-                            {
-                                fontAxesTagsAndNames.insert(std::pair<winrt::hstring, winrt::hstring>(_tagToString(axesVector[i].axisTag), builder.to_hstring()));
-                                continue;
-                            }
-                        }
-                        // if there was no name found, it means the font does not actually support this axis
-                        // don't insert anything into the vector in this case
-                    }
-                    _fontAxesTagsAndNames = winrt::single_threaded_map(std::move(fontAxesTagsAndNames));
-                }
-            }
-        }
-        return _fontAxesTagsAndNames;
+    // Turns a DWRITE_MAKE_OPENTYPE_TAG into a string_view...
+    // (...buffer holder because someone needs to hold onto the data the view refers to.)
+    static TagToStringImpl tagToString(uint32_t tag) noexcept
+    {
+        return TagToStringImpl{ tag };
     }
 
-    IMap<hstring, hstring> Font::FontFeaturesTagsAndNames()
+    // Turns a string to a DWRITE_MAKE_OPENTYPE_TAG. Returns 0 on failure.
+    static uint32_t tagFromString(std::wstring_view str) noexcept
     {
-        if (!_fontFeaturesTagsAndNames)
+        if (str.size() != 4)
         {
-            wil::com_ptr<IDWriteFont> font;
-            THROW_IF_FAILED(_family->GetFont(0, font.put()));
-            wil::com_ptr<IDWriteFontFace> fontFace;
-            THROW_IF_FAILED(font->CreateFontFace(fontFace.put()));
-
-            wil::com_ptr<IDWriteFactory2> factory;
-            THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof())));
-            wil::com_ptr<IDWriteTextAnalyzer> textAnalyzer;
-            factory->CreateTextAnalyzer(textAnalyzer.addressof());
-            wil::com_ptr<IDWriteTextAnalyzer2> textAnalyzer2 = textAnalyzer.query<IDWriteTextAnalyzer2>();
-
-            DWRITE_SCRIPT_ANALYSIS scriptAnalysis{};
-            UINT32 tagCount;
-            // we have to call GetTypographicFeatures twice, first to get the actual count then to get the features
-            std::ignore = textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", 0, &tagCount, nullptr);
-            std::vector<DWRITE_FONT_FEATURE_TAG> tags{ tagCount };
-            textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", tagCount, &tagCount, tags.data());
-
-            std::unordered_map<winrt::hstring, winrt::hstring> fontFeaturesTagsAndNames;
-            for (auto tag : tags)
-            {
-                const auto tagString = _tagToString(tag);
-                hstring formattedResourceString{ fmt::format(L"Profile_FontFeature_{}", tagString) };
-                hstring localizedName{ tagString };
-                // we have resource strings for common font features, see if one for this feature exists
-                if (HasLibraryResourceWithName(formattedResourceString))
-                {
-                    localizedName = GetLibraryResourceString(formattedResourceString);
-                }
-                fontFeaturesTagsAndNames.insert(std::pair<winrt::hstring, winrt::hstring>(tagString, localizedName));
-            }
-            _fontFeaturesTagsAndNames = winrt::single_threaded_map<winrt::hstring, winrt::hstring>(std::move(fontFeaturesTagsAndNames));
+            return 0;
         }
-        return _fontFeaturesTagsAndNames;
-    }
 
-    winrt::hstring Font::_tagToString(DWRITE_FONT_AXIS_TAG tag)
-    {
-        std::wstring result;
+        // Check if all 4 characters are printable ASCII.
         for (int i = 0; i < 4; ++i)
         {
-            result.push_back((tag >> (i * 8)) & 0xFF);
+            const auto ch = str[i];
+            if (ch < 0x20 || ch > 0x7E)
+            {
+                return 0;
+            }
         }
-        return winrt::hstring{ result };
+
+        return DWRITE_MAKE_OPENTYPE_TAG(str[0], str[1], str[2], str[3]);
     }
 
-    hstring Font::_tagToString(DWRITE_FONT_FEATURE_TAG tag)
+    static winrt::hstring getLocalizedStringByIndex(IDWriteLocalizedStrings* strings, UINT32 index)
     {
-        std::wstring result;
-        for (int i = 0; i < 4; ++i)
-        {
-            result.push_back((tag >> (i * 8)) & 0xFF);
-        }
-        return hstring{ result };
+        UINT32 length = 0;
+        THROW_IF_FAILED(strings->GetStringLength(index, &length));
+
+        winrt::impl::hstring_builder builder{ length };
+        THROW_IF_FAILED(strings->GetString(index, builder.data(), length + 1));
+
+        return builder.to_hstring();
     }
 
-    AxisKeyValuePair::AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap) :
-        _AxisKey{ axisKey },
-        _AxisValue{ axisValue },
-        _baseMap{ baseMap },
-        _tagToNameMap{ tagToNameMap }
+    static UINT32 getLocalizedStringIndex(IDWriteLocalizedStrings* strings, const wchar_t* locale, UINT32 fallback)
     {
-        if (_tagToNameMap.HasKey(_AxisKey))
+        UINT32 index;
+        BOOL exists;
+        if (FAILED(strings->FindLocaleName(locale, &index, &exists)) || !exists)
         {
-            int32_t i{ 0 };
-            // IMap guarantees that the iteration order is the same every time
-            // so this conversion of key to index is safe
-            for (const auto tagAndName : _tagToNameMap)
-            {
-                if (tagAndName.Key() == _AxisKey)
-                {
-                    _AxisIndex = i;
-                    break;
-                }
-                ++i;
-            }
+            index = fallback;
         }
+        return index;
     }
 
-    winrt::hstring AxisKeyValuePair::AxisKey()
+    Font::Font(winrt::hstring name, winrt::hstring localizedName) :
+        _Name{ std::move(name) },
+        _LocalizedName{ std::move(localizedName) }
     {
-        return _AxisKey;
     }
 
-    float AxisKeyValuePair::AxisValue()
+    bool FontKeyValuePair::SortAscending(const Editor::FontKeyValuePair& lhs, const Editor::FontKeyValuePair& rhs)
     {
-        return _AxisValue;
+        const auto& a = winrt::get_self<FontKeyValuePair>(lhs)->KeyDisplayStringRef();
+        const auto& b = winrt::get_self<FontKeyValuePair>(rhs)->KeyDisplayStringRef();
+        return til::compare_linguistic_insensitive(a, b) < 0;
     }
 
-    int32_t AxisKeyValuePair::AxisIndex()
+    FontKeyValuePair::FontKeyValuePair(winrt::weak_ref<AppearanceViewModel> vm, winrt::hstring keyDisplayString, uint32_t key, float value, bool isFontFeature) :
+        _vm{ std::move(vm) },
+        _keyDisplayString{ std::move(keyDisplayString) },
+        _key{ key },
+        _value{ value },
+        _isFontFeature{ isFontFeature }
     {
-        return _AxisIndex;
     }
 
-    void AxisKeyValuePair::AxisValue(float axisValue)
+    uint32_t FontKeyValuePair::Key() const noexcept
     {
-        if (axisValue != _AxisValue)
-        {
-            _baseMap.Remove(_AxisKey);
-            _AxisValue = axisValue;
-            _baseMap.Insert(_AxisKey, _AxisValue);
-            PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"AxisValue" });
-        }
+        return _key;
     }
 
-    void AxisKeyValuePair::AxisKey(winrt::hstring axisKey)
+    winrt::hstring FontKeyValuePair::KeyDisplayString()
     {
-        if (axisKey != _AxisKey)
-        {
-            _baseMap.Remove(_AxisKey);
-            _AxisKey = axisKey;
-            _baseMap.Insert(_AxisKey, _AxisValue);
-            PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"AxisKey" });
-        }
+        return KeyDisplayStringRef();
     }
 
-    void AxisKeyValuePair::AxisIndex(int32_t axisIndex)
+    // You can't return a const-ref from a WinRT function, because the cppwinrt generated wrapper chokes on it.
+    // So, now we got two KeyDisplayString() functions, because I refuse to AddRef/Release this for no reason.
+    // I mean, really it makes no perf. difference, but I'm not kneeling down for an incompetent code generator.
+    const winrt::hstring& FontKeyValuePair::KeyDisplayStringRef()
     {
-        if (axisIndex != _AxisIndex)
+        if (!_keyDisplayString.empty())
         {
-            _AxisIndex = axisIndex;
-
-            int32_t i{ 0 };
-            // same as in the constructor, iterating through IMap
-            // gives us the same order every time
-            for (const auto tagAndName : _tagToNameMap)
-            {
-                if (i == _AxisIndex)
-                {
-                    AxisKey(tagAndName.Key());
-                    break;
-                }
-                ++i;
-            }
+            return _keyDisplayString;
         }
-    }
 
-    FeatureKeyValuePair::FeatureKeyValuePair(hstring featureKey, uint32_t featureValue, const IMap<hstring, uint32_t>& baseMap, const IMap<hstring, hstring>& tagToNameMap) :
-        _FeatureKey{ featureKey },
-        _FeatureValue{ featureValue },
-        _baseMap{ baseMap },
-        _tagToNameMap{ tagToNameMap }
-    {
-        if (_tagToNameMap.HasKey(_FeatureKey))
+        const auto tagString = tagToString(_key);
+        hstring displayString;
+
+        if (_isFontFeature)
         {
-            int32_t i{ 0 };
-            // this loop assumes that every time we iterate through the map
-            // we get the same ordering
-            for (const auto tagAndName : _tagToNameMap)
+            const auto key = fmt::format(FMT_COMPILE(L"Profile_FontFeature_{}"), std::wstring_view{ tagString });
+            if (HasLibraryResourceWithName(key))
             {
-                if (tagAndName.Key() == _FeatureKey)
-                {
-                    _FeatureIndex = i;
-                    break;
-                }
-                ++i;
+                displayString = GetLibraryResourceString(key);
+                displayString = hstring{ fmt::format(FMT_COMPILE(L"{} ({})"), displayString, std::wstring_view{ tagString }) };
             }
         }
-    }
 
-    hstring FeatureKeyValuePair::FeatureKey()
-    {
-        return _FeatureKey;
-    }
+        if (displayString.empty())
+        {
+            displayString = hstring{ tagString };
+        }
 
-    uint32_t FeatureKeyValuePair::FeatureValue()
-    {
-        return _FeatureValue;
+        _keyDisplayString = displayString;
+        return _keyDisplayString;
     }
 
-    int32_t FeatureKeyValuePair::FeatureIndex()
+    float FontKeyValuePair::Value() const noexcept
     {
-        return _FeatureIndex;
+        return _value;
     }
 
-    void FeatureKeyValuePair::FeatureValue(uint32_t featureValue)
+    void FontKeyValuePair::Value(float v)
     {
-        if (featureValue != _FeatureValue)
+        if (_value == v)
         {
-            _baseMap.Remove(_FeatureKey);
-            _FeatureValue = featureValue;
-            _baseMap.Insert(_FeatureKey, _FeatureValue);
-            PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FeatureValue" });
+            return;
         }
-    }
 
-    void FeatureKeyValuePair::FeatureKey(hstring featureKey)
-    {
-        if (featureKey != _FeatureKey)
+        _value = v;
+
+        if (const auto vm = _vm.get())
         {
-            _baseMap.Remove(_FeatureKey);
-            _FeatureKey = featureKey;
-            _baseMap.Insert(_FeatureKey, _FeatureValue);
-            PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FeatureKey" });
+            vm->UpdateFontSetting(this);
         }
     }
 
-    void FeatureKeyValuePair::FeatureIndex(int32_t featureIndex)
+    void FontKeyValuePair::SetValueDirect(float v)
     {
-        if (featureIndex != _FeatureIndex)
-        {
-            _FeatureIndex = featureIndex;
+        _value = v;
+    }
 
-            int32_t i{ 0 };
-            // same as in the constructor, this assumes that iterating through the map
-            // gives us the same order every time
-            for (const auto tagAndName : _tagToNameMap)
-            {
-                if (i == _FeatureIndex)
-                {
-                    FeatureKey(tagAndName.Key());
-                    break;
-                }
-                ++i;
-            }
-        }
+    bool FontKeyValuePair::IsFontFeature() const noexcept
+    {
+        return _isFontFeature;
     }
 
     AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) :
@@ -333,25 +221,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
                 // box, prevent it from ever being changed again.
                 _NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible");
             }
-            else if (viewModelProperty == L"FontAxes")
-            {
-                // this is a weird one
-                // we manually make the observable vector based on the map in the settings model
-                // (this is due to xaml being unable to bind a list view to a map)
-                // so when the FontAxes change (say from the reset button), reinitialize the observable vector
-                InitializeFontAxesVector();
-            }
-            else if (viewModelProperty == L"FontFeatures")
-            {
-                // same as the FontAxes one
-                InitializeFontFeaturesVector();
-            }
         });
 
-        _refreshFontFaceDependents();
-        InitializeFontAxesVector();
-        InitializeFontFeaturesVector();
-
         // Cache the original BG image path. If the user clicks "Use desktop
         // wallpaper", then un-checks it, this is the string we'll restore to
         // them.
@@ -375,7 +246,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         }
 
         fontInfo.FontFace(value);
-        _refreshFontFaceDependents();
+        _invalidateFontFaceDependents();
 
         _NotifyChanges(L"HasFontFace", L"FontFace");
     }
@@ -388,15 +259,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
     void AppearanceViewModel::ClearFontFace()
     {
         const auto fontInfo = _appearance.SourceProfile().FontInfo();
-        const auto hadValue = fontInfo.HasFontFace();
 
         fontInfo.ClearFontFace();
-        _refreshFontFaceDependents();
+        _invalidateFontFaceDependents();
 
-        if (hadValue)
-        {
-            _NotifyChanges(L"HasFontFace", L"FontFace");
-        }
+        _NotifyChanges(L"HasFontFace", L"FontFace");
     }
 
     Model::FontConfig AppearanceViewModel::FontFaceOverrideSource() const
@@ -412,50 +279,62 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         wil::com_ptr<IDWriteFontCollection> fontCollection;
         THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), FALSE));
 
-        const auto fontFace = FontFace();
-        std::wstring primaryFontName;
+        const auto fontFaceSpec = FontFace();
         std::wstring missingFonts;
         std::wstring proportionalFonts;
+        std::array<std::vector<Editor::FontKeyValuePair>, 2> fontSettingsRemaining;
         BOOL hasPowerlineCharacters = FALSE;
 
-        til::iterate_font_families(fontFace, [&](wil::zwstring_view name) {
-            if (primaryFontName.empty())
-            {
-                primaryFontName = name;
-            }
+        wchar_t localeNameBuffer[LOCALE_NAME_MAX_LENGTH];
+        const auto localeName = GetUserDefaultLocaleName(localeNameBuffer, LOCALE_NAME_MAX_LENGTH) ? localeNameBuffer : L"en-US";
 
+        til::iterate_font_families(fontFaceSpec, [&](wil::zwstring_view name) {
             std::wstring* accumulator = nullptr;
 
-            UINT32 index = 0;
-            BOOL exists = FALSE;
-            THROW_IF_FAILED(fontCollection->FindFamilyName(name.c_str(), &index, &exists));
-
-            // Look ma, no goto!
-            do
+            try
             {
-                if (!exists)
+                UINT32 index = 0;
+                BOOL exists = FALSE;
+                THROW_IF_FAILED(fontCollection->FindFamilyName(name.c_str(), &index, &exists));
+
+                // Look ma, no goto!
+                do
                 {
-                    accumulator = &missingFonts;
-                    break;
-                }
+                    if (!exists)
+                    {
+                        accumulator = &missingFonts;
+                        break;
+                    }
 
-                wil::com_ptr<IDWriteFontFamily> fontFamily;
-                THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof()));
+                    wil::com_ptr<IDWriteFontFamily> fontFamily;
+                    THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof()));
 
-                wil::com_ptr<IDWriteFont> font;
-                THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
+                    wil::com_ptr<IDWriteFont> font;
+                    THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
 
-                if (!font.query<IDWriteFont1>()->IsMonospacedFont())
-                {
-                    accumulator = &proportionalFonts;
-                }
+                    if (!font.query<IDWriteFont1>()->IsMonospacedFont())
+                    {
+                        accumulator = &proportionalFonts;
+                    }
+
+                    // We're actually checking for the "Extended" PowerLine glyph set.
+                    // They're more fun.
+                    BOOL hasE0B6 = FALSE;
+                    std::ignore = font->HasCharacter(0xE0B6, &hasE0B6);
+                    hasPowerlineCharacters |= hasE0B6;
 
-                // We're actually checking for the "Extended" PowerLine glyph set.
-                // They're more fun.
-                BOOL hasE0B6 = FALSE;
-                std::ignore = font->HasCharacter(0xE0B6, &hasE0B6);
-                hasPowerlineCharacters |= hasE0B6;
-            } while (false);
+                    wil::com_ptr<IDWriteFontFace> fontFace;
+                    THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));
+
+                    _generateFontAxes(fontFace.get(), localeName, fontSettingsRemaining[FontAxesIndex]);
+                    _generateFontFeatures(fontFace.get(), fontSettingsRemaining[FontFeaturesIndex]);
+                } while (false);
+            }
+            catch (...)
+            {
+                accumulator = &missingFonts;
+                LOG_CAUGHT_EXCEPTION();
+            }
 
             if (accumulator)
             {
@@ -467,10 +346,212 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
             }
         });
 
-        _primaryFontName = std::move(primaryFontName);
-        MissingFontFaces(winrt::hstring{ missingFonts });
-        ProportionalFontFaces(winrt::hstring{ proportionalFonts });
-        HasPowerlineCharacters(hasPowerlineCharacters);
+        // Up to this point, our two vectors are sorted by tag value. We want to sort them by display string now,
+        // because this will result in sorted fontSettingsUsed/Unused lists below.
+        for (auto& v : fontSettingsRemaining)
+        {
+            std::sort(v.begin(), v.end(), FontKeyValuePair::SortAscending);
+        }
+
+        std::array<std::vector<Editor::FontKeyValuePair>, 2> fontSettingsUsed;
+        const std::array fontSettingsUser{
+            _appearance.SourceProfile().FontInfo().FontAxes(),
+            _appearance.SourceProfile().FontInfo().FontFeatures(),
+        };
+
+        // Find all axes and features that are in the user settings, and move them to the used list.
+        // They'll be displayed as a list in the UI.
+        for (int i = FontAxesIndex; i <= FontFeaturesIndex; i++)
+        {
+            const auto& map = fontSettingsUser[i];
+            if (!map)
+            {
+                continue;
+            }
+
+            for (const auto& [tagString, value] : fontSettingsUser[i])
+            {
+                const auto tag = tagFromString(tagString);
+                if (!tag)
+                {
+                    continue;
+                }
+
+                auto& remaining = fontSettingsRemaining[i];
+                const auto it = std::ranges::find_if(remaining, [&](const Editor::FontKeyValuePair& kv) {
+                    return winrt::get_self<FontKeyValuePair>(kv)->Key() == tag;
+                });
+
+                Editor::FontKeyValuePair kv{ nullptr };
+                if (it != remaining.end())
+                {
+                    kv = std::move(*it);
+                    remaining.erase(it);
+
+                    const auto kvImpl = winrt::get_self<FontKeyValuePair>(kv);
+                    kvImpl->SetValueDirect(value);
+                }
+                else
+                {
+                    kv = winrt::make<FontKeyValuePair>(get_weak(), hstring{}, tag, value, i == FontFeaturesIndex);
+                }
+
+                fontSettingsUsed[i].emplace_back(std::move(kv));
+            }
+        }
+
+        std::array<std::vector<MenuFlyoutItemBase>, 2> fontSettingsUnused;
+
+        // All remaining (= unused) axes and features are turned into menu items.
+        // They'll be displayed as a flyout when clicking the "add item" button.
+        for (int i = FontAxesIndex; i <= FontFeaturesIndex; i++)
+        {
+            for (const auto& kv : fontSettingsRemaining[i])
+            {
+                fontSettingsUnused[i].emplace_back(_createFontSettingMenuItem(kv));
+            }
+        }
+
+        auto& d = _fontFaceDependents.emplace();
+        d.missingFontFaces = winrt::hstring{ missingFonts };
+        d.proportionalFontFaces = winrt::hstring{ proportionalFonts };
+        d.hasPowerlineCharacters = hasPowerlineCharacters;
+
+        d.fontSettingsUsed[FontAxesIndex] = winrt::single_threaded_observable_vector(std::move(fontSettingsUsed[FontAxesIndex]));
+        d.fontSettingsUsed[FontFeaturesIndex] = winrt::single_threaded_observable_vector(std::move(fontSettingsUsed[FontFeaturesIndex]));
+        d.fontSettingsUnused = std::move(fontSettingsUnused);
+
+        _notifyChangesForFontSettings();
+    }
+
+    std::pair<std::vector<Editor::FontKeyValuePair>::const_iterator, bool> AppearanceViewModel::_fontSettingSortedByKeyInsertPosition(const std::vector<Editor::FontKeyValuePair>& vec, uint32_t key)
+    {
+        const auto it = std::lower_bound(vec.begin(), vec.end(), key, [](const Editor::FontKeyValuePair& lhs, uint32_t rhs) {
+            return winrt::get_self<FontKeyValuePair>(lhs)->Key() < rhs;
+        });
+        const auto exists = it != vec.end() && winrt::get_self<FontKeyValuePair>(*it)->Key() == key;
+        return { it, exists };
+    }
+
+    void AppearanceViewModel::_generateFontAxes(IDWriteFontFace* fontFace, const wchar_t* localeName, std::vector<Editor::FontKeyValuePair>& list)
+    {
+        const auto fontFace5 = wil::try_com_query<IDWriteFontFace5>(fontFace);
+        if (!fontFace5)
+        {
+            return;
+        }
+
+        const auto axesCount = fontFace5->GetFontAxisValueCount();
+        if (axesCount == 0)
+        {
+            return;
+        }
+
+        std::vector<DWRITE_FONT_AXIS_VALUE> axesVector(axesCount);
+        THROW_IF_FAILED(fontFace5->GetFontAxisValues(axesVector.data(), axesCount));
+
+        wil::com_ptr<IDWriteFontResource> fontResource;
+        THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.addressof()));
+
+        for (UINT32 i = 0; i < axesCount; ++i)
+        {
+            wil::com_ptr<IDWriteLocalizedStrings> names;
+            THROW_IF_FAILED(fontResource->GetAxisNames(i, names.addressof()));
+
+            // As per MSDN:
+            // > The font author may not have supplied names for some font axes.
+            // > The localized strings will be empty in that case.
+            if (names->GetCount() == 0)
+            {
+                continue;
+            }
+
+            const auto tag = axesVector[i].axisTag;
+            const auto [it, tagExists] = _fontSettingSortedByKeyInsertPosition(list, tag);
+            if (tagExists)
+            {
+                continue;
+            }
+
+            UINT32 index;
+            BOOL exists;
+            if (FAILED(names->FindLocaleName(localeName, &index, &exists)) || !exists)
+            {
+                index = 0;
+            }
+
+            const auto idx = getLocalizedStringIndex(names.get(), localeName, 0);
+            const auto localizedName = getLocalizedStringByIndex(names.get(), idx);
+            const auto tagString = tagToString(tag);
+            hstring displayString{ fmt::format(FMT_COMPILE(L"{} ({})"), localizedName, std::wstring_view{ tagString }) };
+
+            const auto value = axesVector[i].value;
+
+            list.emplace(it, winrt::make<FontKeyValuePair>(get_weak(), std::move(displayString), tag, value, false));
+        }
+    }
+
+    void AppearanceViewModel::_generateFontFeatures(IDWriteFontFace* fontFace, std::vector<Editor::FontKeyValuePair>& list)
+    {
+        wil::com_ptr<IDWriteFactory> factory;
+        THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof())));
+
+        wil::com_ptr<IDWriteTextAnalyzer> textAnalyzer;
+        THROW_IF_FAILED(factory->CreateTextAnalyzer(textAnalyzer.addressof()));
+        const auto textAnalyzer2 = textAnalyzer.query<IDWriteTextAnalyzer2>();
+
+        static constexpr DWRITE_SCRIPT_ANALYSIS scriptAnalysis{};
+        UINT32 tagCount;
+        if (textAnalyzer2->GetTypographicFeatures(fontFace, scriptAnalysis, L"en-US", 0, &tagCount, nullptr) != E_NOT_SUFFICIENT_BUFFER)
+        {
+            return;
+        }
+        std::vector<DWRITE_FONT_FEATURE_TAG> tags{ tagCount };
+        if (FAILED(textAnalyzer2->GetTypographicFeatures(fontFace, scriptAnalysis, L"en-US", tagCount, &tagCount, tags.data())))
+        {
+            return;
+        }
+
+        for (const auto& tag : tags)
+        {
+            const auto [it, tagExists] = _fontSettingSortedByKeyInsertPosition(list, tag);
+            if (tagExists)
+            {
+                continue;
+            }
+
+            const auto dfBeg = s_defaultFeatures.begin();
+            const auto dfEnd = s_defaultFeatures.end();
+            const auto isDefaultFeature = std::find(dfBeg, dfEnd, tag) != dfEnd;
+            const auto value = isDefaultFeature ? 1.0f : 0.0f;
+
+            list.emplace(it, winrt::make<FontKeyValuePair>(get_weak(), hstring{}, tag, value, true));
+        }
+    }
+
+    MenuFlyoutItemBase AppearanceViewModel::_createFontSettingMenuItem(const Editor::FontKeyValuePair& kv)
+    {
+        const auto kvImpl = winrt::get_self<FontKeyValuePair>(kv);
+
+        MenuFlyoutItem item;
+        item.Text(kvImpl->KeyDisplayStringRef());
+        item.Click([weakSelf = get_weak(), kv](const IInspectable& sender, const RoutedEventArgs&) {
+            if (const auto self = weakSelf.get())
+            {
+                self->AddFontKeyValuePair(sender, kv);
+            }
+        });
+        return item;
+    }
+
+    void AppearanceViewModel::_notifyChangesForFontSettings()
+    {
+        _NotifyChanges(
+            L"FontFaceDependents",
+            L"FontAxes",
+            L"FontFeatures",
+            L"HasFontAxes",
+            L"HasFontFeatures");
     }
 
     double AppearanceViewModel::LineHeight() const
@@ -535,333 +616,250 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         FontWeight(winrt::Microsoft::Terminal::UI::Converters::DoubleToFontWeight(fontWeight));
     }
 
-    void AppearanceViewModel::SetBackgroundImageOpacityFromPercentageValue(double percentageValue)
+    IObservableVector<Editor::FontKeyValuePair> AppearanceViewModel::FontAxes()
     {
-        BackgroundImageOpacity(static_cast<float>(percentageValue) / 100.0f);
+        return FontFaceDependents().fontSettingsUsed[FontAxesIndex];
     }
 
-    void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path)
+    bool AppearanceViewModel::HasFontAxes() const
     {
-        BackgroundImagePath(path);
+        return _appearance.SourceProfile().FontInfo().HasFontAxes();
     }
 
-    bool AppearanceViewModel::UseDesktopBGImage()
+    void AppearanceViewModel::ClearFontAxes()
     {
-        return BackgroundImagePath() == L"desktopWallpaper";
+        _deleteAllFontSettings(FontAxesIndex);
     }
 
-    void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop)
+    Model::FontConfig AppearanceViewModel::FontAxesOverrideSource() const
     {
-        if (useDesktop)
-        {
-            // Stash the current value of BackgroundImagePath. If the user
-            // checks and un-checks the "Use desktop wallpaper" button, we want
-            // the path that we display in the text box to remain unchanged.
-            //
-            // Only stash this value if it's not the special "desktopWallpaper"
-            // value.
-            if (BackgroundImagePath() != L"desktopWallpaper")
-            {
-                _lastBgImagePath = BackgroundImagePath();
-            }
-            BackgroundImagePath(L"desktopWallpaper");
-        }
-        else
-        {
-            // Restore the path we had previously cached. This might be the
-            // empty string.
-            BackgroundImagePath(_lastBgImagePath);
-        }
+        return _appearance.SourceProfile().FontInfo().FontAxesOverrideSource();
     }
 
-    bool AppearanceViewModel::BackgroundImageSettingsVisible()
+    IObservableVector<Editor::FontKeyValuePair> AppearanceViewModel::FontFeatures()
     {
-        return BackgroundImagePath() != L"";
+        return FontFaceDependents().fontSettingsUsed[FontFeaturesIndex];
     }
 
-    void AppearanceViewModel::ClearColorScheme()
+    bool AppearanceViewModel::HasFontFeatures() const
     {
-        ClearDarkColorSchemeName();
-        _NotifyChanges(L"CurrentColorScheme");
+        return _appearance.SourceProfile().FontInfo().HasFontFeatures();
     }
 
-    Editor::ColorSchemeViewModel AppearanceViewModel::CurrentColorScheme()
+    void AppearanceViewModel::ClearFontFeatures()
     {
-        const auto schemeName{ DarkColorSchemeName() };
-        const auto allSchemes{ SchemesList() };
-        for (const auto& scheme : allSchemes)
-        {
-            if (scheme.Name() == schemeName)
-            {
-                return scheme;
-            }
-        }
-        // This Appearance points to a color scheme that was renamed or deleted.
-        // Fallback to the first one in the list.
-        return allSchemes.GetAt(0);
+        _deleteAllFontSettings(FontFeaturesIndex);
     }
 
-    void AppearanceViewModel::CurrentColorScheme(const ColorSchemeViewModel& val)
+    Model::FontConfig AppearanceViewModel::FontFeaturesOverrideSource() const
     {
-        DarkColorSchemeName(val.Name());
-        LightColorSchemeName(val.Name());
+        return _appearance.SourceProfile().FontInfo().FontFeaturesOverrideSource();
     }
 
-    void AppearanceViewModel::AddNewAxisKeyValuePair()
+    void AppearanceViewModel::AddFontKeyValuePair(const IInspectable& sender, const Editor::FontKeyValuePair& kv)
     {
-        if (!_appearance.SourceProfile().FontInfo().FontAxes())
+        if (!_fontFaceDependents)
         {
-            _appearance.SourceProfile().FontInfo().FontAxes(winrt::single_threaded_map<winrt::hstring, float>());
+            return;
         }
-        auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes();
 
-        // find one axis that does not already exist, and add that
-        // if there are no more possible axes to add, the button is disabled so there shouldn't be a way to get here
-        const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames();
-        for (const auto tagAndName : possibleAxesTagsAndNames)
+        const auto kvImpl = winrt::get_self<FontKeyValuePair>(kv);
+        const auto fontSettingsIndex = kvImpl->IsFontFeature() ? 1 : 0;
+        auto& d = *_fontFaceDependents;
+        auto& used = d.fontSettingsUsed[fontSettingsIndex];
+        auto& unused = d.fontSettingsUnused[fontSettingsIndex];
+
+        const auto it = std::ranges::find(unused, sender);
+        if (it == unused.end())
         {
-            if (!fontAxesMap.HasKey(tagAndName.Key()))
-            {
-                fontAxesMap.Insert(tagAndName.Key(), gsl::narrow<float>(0));
-                FontAxesVector().Append(_CreateAxisKeyValuePairHelper(tagAndName.Key(), gsl::narrow<float>(0), fontAxesMap, possibleAxesTagsAndNames));
-                break;
-            }
+            return;
         }
-        _NotifyChanges(L"CanFontAxesBeAdded");
-    }
 
-    void AppearanceViewModel::DeleteAxisKeyValuePair(winrt::hstring key)
-    {
-        for (uint32_t i = 0; i < _FontAxesVector.Size(); i++)
+        // Sync the added value into the user settings model.
+        UpdateFontSetting(kvImpl);
+
+        // Insert the item into the used list, keeping it sorted by the display text.
         {
-            if (_FontAxesVector.GetAt(i).AxisKey() == key)
-            {
-                FontAxesVector().RemoveAt(i);
-                _appearance.SourceProfile().FontInfo().FontAxes().Remove(key);
-                if (_FontAxesVector.Size() == 0)
-                {
-                    _appearance.SourceProfile().FontInfo().ClearFontAxes();
-                }
-                break;
-            }
+            const auto it = std::lower_bound(used.begin(), used.end(), kv, FontKeyValuePair::SortAscending);
+            used.InsertAt(gsl::narrow<uint32_t>(it - used.begin()), kv);
         }
-        _NotifyChanges(L"CanFontAxesBeAdded");
+
+        unused.erase(it);
+
+        _notifyChangesForFontSettings();
     }
 
-    void AppearanceViewModel::InitializeFontAxesVector()
+    void AppearanceViewModel::DeleteFontKeyValuePair(const Editor::FontKeyValuePair& kv)
     {
-        if (!_FontAxesVector)
+        if (!_fontFaceDependents)
         {
-            _FontAxesVector = winrt::single_threaded_observable_vector<Editor::AxisKeyValuePair>();
+            return;
         }
 
-        _FontAxesVector.Clear();
-        if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
+        const auto kvImpl = winrt::get_self<FontKeyValuePair>(kv);
+        const auto tag = kvImpl->Key();
+        const auto tagString = tagToString(tag);
+        const auto fontSettingsIndex = kvImpl->IsFontFeature() ? 1 : 0;
+        auto& d = *_fontFaceDependents;
+        auto& used = d.fontSettingsUsed[fontSettingsIndex];
+        auto& unused = d.fontSettingsUnused[fontSettingsIndex];
+
+        const auto fontInfo = _appearance.SourceProfile().FontInfo();
+        auto fontSettingsUser = kvImpl->IsFontFeature() ? fontInfo.FontFeatures() : fontInfo.FontAxes();
+        if (!fontSettingsUser)
         {
-            const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames();
-            for (const auto axis : fontAxesMap)
-            {
-                // only show the axes that the font supports
-                // any axes that the font doesn't support continue to be stored in the json, we just don't show them in the UI
-                if (fontAxesTagToNameMap.HasKey(axis.Key()))
-                {
-                    _FontAxesVector.Append(_CreateAxisKeyValuePairHelper(axis.Key(), axis.Value(), fontAxesMap, fontAxesTagToNameMap));
-                }
-            }
+            return;
         }
-        _NotifyChanges(L"AreFontAxesAvailable", L"CanFontAxesBeAdded");
-    }
 
-    // Method Description:
-    // - Determines whether the currently selected font has any variable font axes
-    bool AppearanceViewModel::AreFontAxesAvailable()
-    {
-        return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames().Size() > 0;
-    }
+        const auto it = std::ranges::find(used, kv);
+        if (it == used.end())
+        {
+            return;
+        }
 
-    // Method Description:
-    // - Determines whether the currently selected font has any variable font axes that have not already been set
-    bool AppearanceViewModel::CanFontAxesBeAdded()
-    {
-        if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0)
+        fontSettingsUser.Remove(std::wstring_view{ tagString });
+
+        // Insert the item into the unused list, keeping it sorted by the display text.
         {
-            if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
-            {
-                for (const auto tagAndName : fontAxesTagToNameMap)
-                {
-                    if (!fontAxesMap.HasKey(tagAndName.Key()))
-                    {
-                        // we found an axis that has not been set
-                        return true;
-                    }
-                }
-                // all possible axes have been set already
-                return false;
-            }
-            // the font supports font axes but the profile has none set
-            return true;
+            const auto item = _createFontSettingMenuItem(*it);
+            const auto it = std::lower_bound(unused.begin(), unused.end(), item, [](const MenuFlyoutItemBase& lhs, const MenuFlyoutItemBase& rhs) {
+                const auto& a = lhs.as<MenuFlyoutItem>().Text();
+                const auto& b = rhs.as<MenuFlyoutItem>().Text();
+                return til::compare_linguistic_insensitive(a, b) < 0;
+            });
+            unused.insert(it, item);
         }
-        // the font does not support any font axes
-        return false;
-    }
 
-    // Method Description:
-    // - Creates an AxisKeyValuePair and sets up an event handler for it
-    Editor::AxisKeyValuePair AppearanceViewModel::_CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap)
-    {
-        const auto axisKeyValuePair = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::AxisKeyValuePair>(axisKey, axisValue, baseMap, tagToNameMap);
-        // when either the key or the value changes, send an event for the preview control to catch
-        axisKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& /*sender*/, auto& /*e*/) {
-            if (auto appVM{ weakThis.get() })
-            {
-                appVM->_NotifyChanges(L"AxisKeyValuePair");
-            }
-        });
-        return axisKeyValuePair;
+        used.RemoveAt(gsl::narrow<uint32_t>(it - used.begin()));
+
+        _notifyChangesForFontSettings();
     }
 
-    void AppearanceViewModel::AddNewFeatureKeyValuePair()
+    void AppearanceViewModel::UpdateFontSetting(const FontKeyValuePair* kvImpl)
     {
+        const auto tag = kvImpl->Key();
+        const auto value = kvImpl->Value();
+        const auto tagString = tagToString(tag);
         const auto fontInfo = _appearance.SourceProfile().FontInfo();
-        auto fontFeaturesMap = fontInfo.FontFeatures();
-        if (!fontFeaturesMap)
-        {
-            fontFeaturesMap = winrt::single_threaded_map<hstring, uint32_t>();
-            fontInfo.FontFeatures(fontFeaturesMap);
-        }
+        auto fontSettingsUser = kvImpl->IsFontFeature() ? fontInfo.FontFeatures() : fontInfo.FontAxes();
 
-        // find one feature that does not already exist, and add that
-        // if there are no more possible features to add, the button is disabled so there shouldn't be a way to get here
-        const auto possibleFeaturesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames();
-        for (const auto tagAndName : possibleFeaturesTagsAndNames)
+        if (!fontSettingsUser)
         {
-            const auto featureKey = tagAndName.Key();
-            if (!fontFeaturesMap.HasKey(featureKey))
+            fontSettingsUser = winrt::single_threaded_map<hstring, float>();
+            if (kvImpl->IsFontFeature())
+            {
+                fontInfo.FontFeatures(fontSettingsUser);
+            }
+            else
             {
-                const auto featureDefaultValue = _IsDefaultFeature(featureKey) ? 1 : 0;
-                fontFeaturesMap.Insert(featureKey, featureDefaultValue);
-                FontFeaturesVector().Append(_CreateFeatureKeyValuePairHelper(featureKey, featureDefaultValue, fontFeaturesMap, possibleFeaturesTagsAndNames));
-                break;
+                fontInfo.FontAxes(fontSettingsUser);
             }
         }
-        _NotifyChanges(L"CanFontFeaturesBeAdded");
+
+        std::ignore = fontSettingsUser.Insert(std::wstring_view{ tagString }, value);
     }
 
-    void AppearanceViewModel::DeleteFeatureKeyValuePair(hstring key)
+    void AppearanceViewModel::_deleteAllFontSettings(FontSettingIndex fontSettingsIndex)
     {
-        for (uint32_t i = 0; i < _FontFeaturesVector.Size(); i++)
+        const auto fontInfo = _appearance.SourceProfile().FontInfo();
+        if (fontSettingsIndex == FontFeaturesIndex)
         {
-            if (_FontFeaturesVector.GetAt(i).FeatureKey() == key)
-            {
-                FontFeaturesVector().RemoveAt(i);
-                _appearance.SourceProfile().FontInfo().FontFeatures().Remove(key);
-                if (_FontFeaturesVector.Size() == 0)
-                {
-                    _appearance.SourceProfile().FontInfo().ClearFontFeatures();
-                }
-                break;
-            }
+            fontInfo.ClearFontFeatures();
+        }
+        else
+        {
+            fontInfo.ClearFontAxes();
         }
-        _NotifyChanges(L"CanFontAxesBeAdded");
-    }
 
-    void AppearanceViewModel::InitializeFontFeaturesVector()
-    {
-        if (!_FontFeaturesVector)
+        if (!_fontFaceDependents)
         {
-            _FontFeaturesVector = single_threaded_observable_vector<Editor::FeatureKeyValuePair>();
+            return;
         }
 
-        _FontFeaturesVector.Clear();
-        if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures())
+        auto& d = *_fontFaceDependents;
+        auto& used = d.fontSettingsUsed[fontSettingsIndex];
+        auto& unused = d.fontSettingsUnused[fontSettingsIndex];
+
+        for (const auto& kv : used)
         {
-            const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames();
-            for (const auto feature : fontFeaturesMap)
-            {
-                const auto featureKey = feature.Key();
-                // only show the features that the font supports
-                // any features that the font doesn't support continue to be stored in the json, we just don't show them in the UI
-                if (fontFeaturesTagToNameMap.HasKey(featureKey))
-                {
-                    _FontFeaturesVector.Append(_CreateFeatureKeyValuePairHelper(featureKey, feature.Value(), fontFeaturesMap, fontFeaturesTagToNameMap));
-                }
-            }
+            unused.emplace_back(_createFontSettingMenuItem(kv));
         }
-        _NotifyChanges(L"AreFontFeaturesAvailable", L"CanFontFeaturesBeAdded");
+
+        used.Clear();
+
+        _notifyChangesForFontSettings();
     }
 
-    // Method Description:
-    // - Determines whether the currently selected font has any font features
-    bool AppearanceViewModel::AreFontFeaturesAvailable()
+    void AppearanceViewModel::SetBackgroundImageOpacityFromPercentageValue(double percentageValue)
+    {
+        BackgroundImageOpacity(static_cast<float>(percentageValue) / 100.0f);
+    }
+
+    void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path)
     {
-        return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames().Size() > 0;
+        BackgroundImagePath(path);
     }
 
-    // Method Description:
-    // - Determines whether the currently selected font has any font features that have not already been set
-    bool AppearanceViewModel::CanFontFeaturesBeAdded()
+    bool AppearanceViewModel::UseDesktopBGImage()
     {
-        if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0)
+        return BackgroundImagePath() == L"desktopWallpaper";
+    }
+
+    void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop)
+    {
+        if (useDesktop)
         {
-            if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures())
+            // Stash the current value of BackgroundImagePath. If the user
+            // checks and un-checks the "Use desktop wallpaper" button, we want
+            // the path that we display in the text box to remain unchanged.
+            //
+            // Only stash this value if it's not the special "desktopWallpaper"
+            // value.
+            if (BackgroundImagePath() != L"desktopWallpaper")
             {
-                for (const auto tagAndName : fontFeaturesTagToNameMap)
-                {
-                    if (!fontFeaturesMap.HasKey(tagAndName.Key()))
-                    {
-                        // we found a feature that has not been set
-                        return true;
-                    }
-                }
-                // all possible features have been set already
-                return false;
+                _lastBgImagePath = BackgroundImagePath();
             }
-            // the font supports font features but the profile has none set
-            return true;
+            BackgroundImagePath(L"desktopWallpaper");
+        }
+        else
+        {
+            // Restore the path we had previously cached. This might be the
+            // empty string.
+            BackgroundImagePath(_lastBgImagePath);
         }
-        // the font does not support any font features
-        return false;
     }
 
-    // Method Description:
-    // - Creates a FeatureKeyValuePair and sets up an event handler for it
-    Editor::FeatureKeyValuePair AppearanceViewModel::_CreateFeatureKeyValuePairHelper(hstring featureKey, uint32_t featureValue, const IMap<hstring, uint32_t>& baseMap, const IMap<hstring, hstring>& tagToNameMap)
+    bool AppearanceViewModel::BackgroundImageSettingsVisible()
     {
-        const auto featureKeyValuePair = winrt::make<winrt::Microsoft::Terminal::Settings::Editor::implementation::FeatureKeyValuePair>(featureKey, featureValue, baseMap, tagToNameMap);
-        // when either the key or the value changes, send an event for the preview control to catch
-        featureKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& sender, const PropertyChangedEventArgs& args) {
-            if (auto appVM{ weakThis.get() })
-            {
-                appVM->_NotifyChanges(L"FeatureKeyValuePair");
-                const auto settingName{ args.PropertyName() };
-                if (settingName == L"FeatureKey")
-                {
-                    const auto senderPair = sender.as<FeatureKeyValuePair>();
-                    const auto senderKey = senderPair->FeatureKey();
-                    if (appVM->_IsDefaultFeature(senderKey))
-                    {
-                        senderPair->FeatureValue(1);
-                    }
-                    else
-                    {
-                        senderPair->FeatureValue(0);
-                    }
-                }
-            }
-        });
-        return featureKeyValuePair;
+        return BackgroundImagePath() != L"";
     }
 
-    bool AppearanceViewModel::_IsDefaultFeature(winrt::hstring featureKey)
+    void AppearanceViewModel::ClearColorScheme()
     {
-        for (const auto defaultFeature : DefaultFeatures)
+        ClearDarkColorSchemeName();
+        _NotifyChanges(L"CurrentColorScheme");
+    }
+
+    Editor::ColorSchemeViewModel AppearanceViewModel::CurrentColorScheme()
+    {
+        const auto schemeName{ DarkColorSchemeName() };
+        const auto allSchemes{ SchemesList() };
+        for (const auto& scheme : allSchemes)
         {
-            if (defaultFeature == featureKey)
+            if (scheme.Name() == schemeName)
             {
-                return true;
+                return scheme;
             }
         }
-        return false;
+        // This Appearance points to a color scheme that was renamed or deleted.
+        // Fallback to the first one in the list.
+        return allSchemes.GetAt(0);
+    }
+
+    void AppearanceViewModel::CurrentColorScheme(const ColorSchemeViewModel& val)
+    {
+        DarkColorSchemeName(val.Name());
+        LightColorSchemeName(val.Name());
     }
 
     DependencyProperty Appearances::_AppearanceProperty{ nullptr };
@@ -928,12 +926,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         const auto backgroundImgCheckboxTooltip{ ToolTipService::GetToolTip(UseDesktopImageCheckBox()) };
         Automation::AutomationProperties::SetFullDescription(UseDesktopImageCheckBox(), unbox_value<hstring>(backgroundImgCheckboxTooltip));
 
-        _FontAxesNames = winrt::single_threaded_observable_vector<winrt::hstring>();
-        FontAxesNamesCVS().Source(_FontAxesNames);
-
-        _FontFeaturesNames = winrt::single_threaded_observable_vector<hstring>();
-        FontFeaturesNamesCVS().Source(_FontFeaturesNames);
-
         INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content");
     }
 
@@ -983,55 +975,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         {
             appearance.FontFace(fontSpec);
         }
-
-        // TODO: Any use of FindFontWithLocalizedName is broken and requires refactoring in time for version 1.21.
-        const auto newFontFace = ProfileViewModel::FindFontWithLocalizedName(fontSpec);
-        if (!newFontFace)
-        {
-            return;
-        }
-
-        _FontAxesNames.Clear();
-        const auto axesTagsAndNames = newFontFace.FontAxesTagsAndNames();
-        for (const auto tagAndName : axesTagsAndNames)
-        {
-            _FontAxesNames.Append(tagAndName.Value());
-        }
-
-        _FontFeaturesNames.Clear();
-        const auto featuresTagsAndNames = newFontFace.FontFeaturesTagsAndNames();
-        for (const auto tagAndName : featuresTagsAndNames)
-        {
-            _FontFeaturesNames.Append(tagAndName.Value());
-        }
-
-        // when the font face changes, we have to tell the view model to update the font axes/features vectors
-        // since the new font may not have the same possible axes as the previous one
-        Appearance().InitializeFontAxesVector();
-        if (!Appearance().AreFontAxesAvailable())
-        {
-            // if the previous font had available font axes and the expander was expanded,
-            // at this point the expander would be set to disabled so manually collapse it
-            FontAxesContainer().SetExpanded(false);
-            FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text"));
-        }
-        else
-        {
-            FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text"));
-        }
-
-        Appearance().InitializeFontFeaturesVector();
-        if (!Appearance().AreFontFeaturesAvailable())
-        {
-            // if the previous font had available font features and the expander was expanded,
-            // at this point the expander would be set to disabled so manually collapse it
-            FontFeaturesContainer().SetExpanded(false);
-            FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesUnavailable/Text"));
-        }
-        else
-        {
-            FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesAvailable/Text"));
-        }
     }
 
     void Appearances::FontFaceBox_SuggestionChosen(const AutoSuggestBox& sender, const AutoSuggestBoxSuggestionChosenEventArgs& args)
@@ -1127,17 +1070,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
     {
         if (const auto appearance = Appearance())
         {
-            const auto& biAlignmentVal{ static_cast<int32_t>(appearance.BackgroundImageAlignment()) };
+            const auto appearanceImpl = winrt::get_self<AppearanceViewModel>(appearance);
+            const auto& biAlignmentVal{ static_cast<int32_t>(appearanceImpl->BackgroundImageAlignment()) };
             for (const auto& biButton : _BIAlignmentButtons)
             {
                 biButton.IsChecked(biButton.Tag().as<int32_t>() == biAlignmentVal);
             }
 
-            FontAxesCVS().Source(Appearance().FontAxesVector());
-            FontAxesContainer().HelpText(appearance.AreFontAxesAvailable() ? RS_(L"Profile_FontAxesAvailable/Text") : RS_(L"Profile_FontAxesUnavailable/Text"));
+            {
+                const auto& d = appearanceImpl->FontFaceDependents();
+
+                const std::array buttons{
+                    AddFontAxisButton(),
+                    AddFontFeatureButton(),
+                };
 
-            FontFeaturesCVS().Source(Appearance().FontFeaturesVector());
-            FontFeaturesContainer().HelpText(appearance.AreFontFeaturesAvailable() ? RS_(L"Profile_FontFeaturesAvailable/Text") : RS_(L"Profile_FontFeaturesUnavailable/Text"));
+                for (int i = 0; i < 2; ++i)
+                {
+                    const auto& button = buttons[i];
+                    const auto& data = d.fontSettingsUnused[i];
+                    const auto items = button.Flyout().as<MenuFlyout>().Items();
+                    items.ReplaceAll(data);
+                    button.IsEnabled(!data.empty());
+                }
+            }
 
             _ViewModelChangedRevoker = appearance.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
                 const auto settingName{ args.PropertyName() };
@@ -1156,13 +1112,36 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
                 }
                 else if (settingName == L"BackgroundImageAlignment")
                 {
-                    _UpdateBIAlignmentControl(static_cast<int32_t>(appearance.BackgroundImageAlignment()));
+                    _UpdateBIAlignmentControl(static_cast<int32_t>(appearanceImpl->BackgroundImageAlignment()));
                 }
                 else if (settingName == L"FontWeight")
                 {
                     PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" });
                     PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" });
                 }
+                else if (settingName == L"FontFaceDependents")
+                {
+                    const auto& d = appearanceImpl->FontFaceDependents();
+
+                    const std::array buttons{
+                        AddFontAxisButton(),
+                        AddFontFeatureButton(),
+                    };
+
+                    for (int i = 0; i < 2; ++i)
+                    {
+                        const auto& button = buttons[i];
+                        const auto& data = d.fontSettingsUnused[i];
+                        const auto flyout = button.Flyout().as<MenuFlyout>();
+                        const auto items = flyout.Items();
+                        items.ReplaceAll(data);
+                        button.IsEnabled(!data.empty());
+                        // WinUI doesn't hide the flyout when it's currently open and the items are now empty.
+                        // In fact it doesn't close it, period. You click an item? Flyout stays open!
+                        // This gets called whenever an item is selected, so it's the "perfect" time to close it.
+                        flyout.Hide();
+                    }
+                }
                 else if (settingName == L"IntenseTextStyle")
                 {
                     PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" });
@@ -1248,36 +1227,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         }
     }
 
-    void Appearances::DeleteAxisKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
-    {
-        if (const auto& button{ sender.try_as<Controls::Button>() })
-        {
-            if (const auto& tag{ button.Tag().try_as<winrt::hstring>() })
-            {
-                Appearance().DeleteAxisKeyValuePair(tag.value());
-            }
-        }
-    }
-
-    void Appearances::AddNewAxisKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
-    {
-        Appearance().AddNewAxisKeyValuePair();
-    }
-
-    void Appearances::DeleteFeatureKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
-    {
-        if (const auto& button{ sender.try_as<Controls::Button>() })
-        {
-            if (const auto& tag{ button.Tag().try_as<hstring>() })
-            {
-                Appearance().DeleteFeatureKeyValuePair(tag.value());
-            }
-        }
-    }
-
-    void Appearances::AddNewFeatureKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
+    void Appearances::DeleteFontKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
     {
-        Appearance().AddNewFeatureKeyValuePair();
+        const auto element = sender.as<FrameworkElement>();
+        const auto tag = element.Tag();
+        const auto kv = tag.as<Editor::FontKeyValuePair>();
+        winrt::get_self<AppearanceViewModel>(Appearance())->DeleteFontKeyValuePair(kv);
     }
 
     bool Appearances::IsVintageCursor() const
diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h
index d01819b24b5..572b6240984 100644
--- a/src/cascadia/TerminalSettingsEditor/Appearances.h
+++ b/src/cascadia/TerminalSettingsEditor/Appearances.h
@@ -17,8 +17,7 @@ Author(s):
 #pragma once
 
 #include "Font.g.h"
-#include "AxisKeyValuePair.g.h"
-#include "FeatureKeyValuePair.g.h"
+#include "FontKeyValuePair.g.h"
 #include "Appearances.g.h"
 #include "AppearanceViewModel.g.h"
 #include "Utils.h"
@@ -28,81 +27,57 @@ Author(s):
 
 namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
 {
+    struct AppearanceViewModel;
+
     struct Font : FontT<Font>
     {
-    public:
-        Font(winrt::hstring name, winrt::hstring localizedName, wil::com_ptr<IDWriteFontFamily> family) :
-            _Name{ std::move(name) },
-            _LocalizedName{ std::move(localizedName) },
-            _family{ std::move(family) }
-        {
-        }
-
-        hstring ToString() { return _LocalizedName; }
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> FontAxesTagsAndNames();
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> FontFeaturesTagsAndNames();
+        Font(winrt::hstring name, winrt::hstring localizedName);
 
         WINRT_PROPERTY(hstring, Name);
         WINRT_PROPERTY(hstring, LocalizedName);
-
-    private:
-        winrt::hstring _tagToString(DWRITE_FONT_AXIS_TAG tag);
-        winrt::hstring _tagToString(DWRITE_FONT_FEATURE_TAG tag);
-
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> _fontAxesTagsAndNames;
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> _fontFeaturesTagsAndNames;
-        wil::com_ptr<IDWriteFontFamily> _family;
     };
 
-    struct AxisKeyValuePair : AxisKeyValuePairT<AxisKeyValuePair>, ViewModelHelper<AxisKeyValuePair>
+    struct FontKeyValuePair : FontKeyValuePairT<FontKeyValuePair>
     {
-        AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap);
+        static bool SortAscending(const Editor::FontKeyValuePair& lhs, const Editor::FontKeyValuePair& rhs);
 
-        winrt::hstring AxisKey();
-        void AxisKey(winrt::hstring axisKey);
+        FontKeyValuePair(winrt::weak_ref<AppearanceViewModel> vm, winrt::hstring keyDisplayString, uint32_t key, float value, bool isFontFeature);
 
-        float AxisValue();
-        void AxisValue(float axisValue);
+        winrt::hstring KeyDisplayString();
+        const winrt::hstring& KeyDisplayStringRef();
+        uint32_t Key() const noexcept;
+        float Value() const noexcept;
+        void Value(float v);
 
-        int32_t AxisIndex();
-        void AxisIndex(int32_t axisIndex);
-
-        til::property_changed_event PropertyChanged;
+        void SetValueDirect(float v);
+        bool IsFontFeature() const noexcept;
 
     private:
-        winrt::hstring _AxisKey;
-        float _AxisValue;
-        int32_t _AxisIndex;
-        Windows::Foundation::Collections::IMap<winrt::hstring, float> _baseMap{ nullptr };
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> _tagToNameMap{ nullptr };
+        winrt::weak_ref<AppearanceViewModel> _vm;
+        winrt::hstring _keyDisplayString;
+        uint32_t _key;
+        float _value;
+        bool _isFontFeature;
     };
 
-    struct FeatureKeyValuePair : FeatureKeyValuePairT<FeatureKeyValuePair>, ViewModelHelper<FeatureKeyValuePair>
+    struct AppearanceViewModel : AppearanceViewModelT<AppearanceViewModel>, ViewModelHelper<AppearanceViewModel>
     {
-        FeatureKeyValuePair(winrt::hstring featureKey, uint32_t featureValue, const Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap);
-
-        winrt::hstring FeatureKey();
-        void FeatureKey(winrt::hstring featureKey);
-
-        uint32_t FeatureValue();
-        void FeatureValue(uint32_t featureValue);
-
-        int32_t FeatureIndex();
-        void FeatureIndex(int32_t featureIndex);
+        enum FontSettingIndex
+        {
+            FontAxesIndex,
+            FontFeaturesIndex,
+        };
 
-        til::property_changed_event PropertyChanged;
+        struct FontFaceDependentsData
+        {
+            winrt::hstring missingFontFaces;
+            winrt::hstring proportionalFontFaces;
+            bool hasPowerlineCharacters = false;
 
-    private:
-        winrt::hstring _FeatureKey;
-        uint32_t _FeatureValue;
-        int32_t _FeatureIndex;
-        Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t> _baseMap{ nullptr };
-        Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> _tagToNameMap{ nullptr };
-    };
+            std::array<Windows::Foundation::Collections::IObservableVector<Editor::FontKeyValuePair>, 2> fontSettingsUsed;
+            std::array<std::vector<Windows::UI::Xaml::Controls::MenuFlyoutItemBase>, 2> fontSettingsUnused;
+        };
 
-    struct AppearanceViewModel : AppearanceViewModelT<AppearanceViewModel>, ViewModelHelper<AppearanceViewModel>
-    {
-    public:
         AppearanceViewModel(const Model::AppearanceConfig& appearance);
 
         winrt::hstring FontFace() const;
@@ -118,32 +93,46 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         Model::FontConfig LineHeightOverrideSource() const;
 
         void SetFontWeightFromDouble(double fontWeight);
-        void SetBackgroundImageOpacityFromPercentageValue(double percentageValue);
-        void SetBackgroundImagePath(winrt::hstring path);
+
+        const FontFaceDependentsData& FontFaceDependents()
+        {
+            if (!_fontFaceDependents)
+            {
+                _refreshFontFaceDependents();
+            }
+            return *_fontFaceDependents;
+        }
+
+        winrt::hstring MissingFontFaces() { return FontFaceDependents().missingFontFaces; }
+        winrt::hstring ProportionalFontFaces() { return FontFaceDependents().proportionalFontFaces; }
+        bool HasPowerlineCharacters() { return FontFaceDependents().hasPowerlineCharacters; }
+
+        Windows::Foundation::Collections::IObservableVector<Editor::FontKeyValuePair> FontAxes();
+        bool HasFontAxes() const;
+        void ClearFontAxes();
+        Model::FontConfig FontAxesOverrideSource() const;
+
+        Windows::Foundation::Collections::IObservableVector<Editor::FontKeyValuePair> FontFeatures();
+        bool HasFontFeatures() const;
+        void ClearFontFeatures();
+        Model::FontConfig FontFeaturesOverrideSource() const;
+
+        void AddFontKeyValuePair(const IInspectable& sender, const Editor::FontKeyValuePair& kv);
+        void DeleteFontKeyValuePair(const Editor::FontKeyValuePair& kv);
+        void UpdateFontSetting(const FontKeyValuePair* kv);
 
         // background image
         bool UseDesktopBGImage();
         void UseDesktopBGImage(const bool useDesktop);
         bool BackgroundImageSettingsVisible();
+        void SetBackgroundImageOpacityFromPercentageValue(double percentageValue);
+        void SetBackgroundImagePath(winrt::hstring path);
 
         void ClearColorScheme();
         Editor::ColorSchemeViewModel CurrentColorScheme();
         void CurrentColorScheme(const Editor::ColorSchemeViewModel& val);
 
-        void AddNewAxisKeyValuePair();
-        void DeleteAxisKeyValuePair(winrt::hstring key);
-        void InitializeFontAxesVector();
-        bool AreFontAxesAvailable();
-        bool CanFontAxesBeAdded();
-
-        void AddNewFeatureKeyValuePair();
-        void DeleteFeatureKeyValuePair(winrt::hstring key);
-        void InitializeFontFeaturesVector();
-        bool AreFontFeaturesAvailable();
-        bool CanFontFeaturesBeAdded();
-
         WINRT_PROPERTY(bool, IsDefault, false);
-        WINRT_PROPERTY(bool, HasPowerlineCharacters, false);
 
         // These settings are not defined in AppearanceConfig, so we grab them
         // from the source profile itself. The reason we still want them in the
@@ -152,8 +141,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         // are defined in AppearanceConfig and some that are not.
         OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize);
         OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight);
-        OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontAxes);
-        OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFeatures);
         OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs);
         OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableColorGlyphs);
 
@@ -169,28 +156,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle);
         OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors);
         WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::ColorSchemeViewModel>, SchemesList, _propertyChangedHandlers, nullptr);
-        WINRT_OBSERVABLE_PROPERTY(winrt::hstring, MissingFontFaces, _propertyChangedHandlers);
-        WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ProportionalFontFaces, _propertyChangedHandlers);
-        WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::AxisKeyValuePair>, FontAxesVector, _propertyChangedHandlers, nullptr);
-        WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::FeatureKeyValuePair>, FontFeaturesVector, _propertyChangedHandlers, nullptr);
 
     private:
+        void _invalidateFontFaceDependents() { _fontFaceDependents.reset(); }
         void _refreshFontFaceDependents();
-        Editor::AxisKeyValuePair _CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, float>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap);
-        Editor::FeatureKeyValuePair _CreateFeatureKeyValuePairHelper(winrt::hstring axisKey, uint32_t axisValue, const Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>& baseMap, const Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>& tagToNameMap);
-        bool _IsDefaultFeature(winrt::hstring featureTag);
+        static std::pair<std::vector<Editor::FontKeyValuePair>::const_iterator, bool> _fontSettingSortedByKeyInsertPosition(const std::vector<Editor::FontKeyValuePair>& vec, uint32_t key);
+        void _generateFontAxes(IDWriteFontFace* fontFace, const wchar_t* localeName, std::vector<Editor::FontKeyValuePair>& list);
+        void _generateFontFeatures(IDWriteFontFace* fontFace, std::vector<Editor::FontKeyValuePair>& list);
+        Windows::UI::Xaml::Controls::MenuFlyoutItemBase _createFontSettingMenuItem(const Editor::FontKeyValuePair& kv);
+        void _notifyChangesForFontSettings();
+        void _deleteAllFontSettings(FontSettingIndex index);
 
         Model::AppearanceConfig _appearance;
         winrt::hstring _lastBgImagePath;
-        std::wstring _primaryFontName;
+        std::optional<FontFaceDependentsData> _fontFaceDependents;
     };
 
     struct Appearances : AppearancesT<Appearances>
     {
-    public:
-        static winrt::hstring FontAxisName(const winrt::hstring& key);
-        static winrt::hstring FontFeatureName(const winrt::hstring& key);
-
         Appearances();
 
         // CursorShape visibility logic
@@ -204,12 +187,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         void FontFaceBox_LostFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
         void FontFaceBox_SuggestionChosen(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxSuggestionChosenEventArgs&);
         void FontFaceBox_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs&);
+        void DeleteFontKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
         fire_and_forget BackgroundImage_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
         void BIAlignment_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
-        void DeleteAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
-        void AddNewAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
-        void DeleteFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
-        void AddNewFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
 
         // manually bind FontWeight
         Windows::Foundation::IInspectable CurrentFontWeight() const;
@@ -253,6 +233,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
 namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
 {
     BASIC_FACTORY(Appearances);
-    BASIC_FACTORY(AxisKeyValuePair);
-    BASIC_FACTORY(FeatureKeyValuePair);
 }
diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl
index 2855fa21b72..0907e5f8f3d 100644
--- a/src/cascadia/TerminalSettingsEditor/Appearances.idl
+++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl
@@ -17,30 +17,17 @@ import "ColorSchemesPageViewModel.idl";
 
 namespace Microsoft.Terminal.Settings.Editor
 {
-    runtimeclass Font : Windows.Foundation.IStringable
+    runtimeclass Font
     {
         String Name { get; };
         String LocalizedName { get; };
-        Windows.Foundation.Collections.IMap<String, String> FontAxesTagsAndNames { get; };
-        Windows.Foundation.Collections.IMap<String, String> FontFeaturesTagsAndNames { get; };
     }
 
-    // We have to make this because we cannot bind an IObservableMap to a ListView in XAML (in c++)
-    // So instead we make an IObservableVector of these AxisKeyValuePair objects
-    runtimeclass AxisKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged
+    runtimeclass FontKeyValuePair
     {
-        AxisKeyValuePair(String axisKey, Single axisValue, Windows.Foundation.Collections.IMap<String, Single> baseMap, Windows.Foundation.Collections.IMap<String, String> tagToNameMap);
-        String AxisKey;
-        Single AxisValue;
-        Int32 AxisIndex;
-    }
-
-    runtimeclass FeatureKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged
-    {
-        FeatureKeyValuePair(String featureKey, UInt32 featureValue, Windows.Foundation.Collections.IMap<String, UInt32> baseMap, Windows.Foundation.Collections.IMap<String, String> tagToNameMap);
-        String FeatureKey;
-        UInt32 FeatureValue;
-        Int32 FeatureIndex;
+        UInt32 Key { get; };
+        String KeyDisplayString { get; };
+        Single Value;
     }
 
     runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
@@ -56,27 +43,21 @@ namespace Microsoft.Terminal.Settings.Editor
 
         void ClearColorScheme();
         ColorSchemeViewModel CurrentColorScheme;
-        Windows.Foundation.Collections.IObservableVector<ColorSchemeViewModel> SchemesList;
+        IObservableVector<ColorSchemeViewModel> SchemesList;
 
         String MissingFontFaces { get; };
         String ProportionalFontFaces { get; };
         Boolean HasPowerlineCharacters { get; };
 
-        void AddNewAxisKeyValuePair();
-        void DeleteAxisKeyValuePair(String key);
-        void InitializeFontAxesVector();
-        Boolean AreFontAxesAvailable { get; };
-        Boolean CanFontAxesBeAdded { get; };
-        Windows.Foundation.Collections.IObservableVector<AxisKeyValuePair> FontAxesVector;
-        OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap<String COMMA Single>, FontAxes);
-
-        void AddNewFeatureKeyValuePair();
-        void DeleteFeatureKeyValuePair(String key);
-        void InitializeFontFeaturesVector();
-        Boolean AreFontFeaturesAvailable { get; };
-        Boolean CanFontFeaturesBeAdded { get; };
-        Windows.Foundation.Collections.IObservableVector<FeatureKeyValuePair> FontFeaturesVector;
-        OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap<String COMMA UInt32>, FontFeatures);
+        IObservableVector<FontKeyValuePair> FontAxes { get; };
+        Boolean HasFontAxes { get; };
+        void ClearFontAxes();
+        Object FontAxesOverrideSource { get; };
+
+        IObservableVector<FontKeyValuePair> FontFeatures { get; };
+        Boolean HasFontFeatures { get; };
+        void ClearFontFeatures();
+        Object FontFeaturesOverrideSource { get; };
 
         OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, FontFace);
         OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize);
@@ -106,24 +87,24 @@ namespace Microsoft.Terminal.Settings.Editor
         IHostedInWindow WindowRoot;
         static Windows.UI.Xaml.DependencyProperty AppearanceProperty { get; };
 
-        Windows.Foundation.Collections.IObservableVector<Font> FilteredFontList { get; };
+        IObservableVector<Font> FilteredFontList { get; };
         Boolean ShowAllFonts;
 
         IInspectable CurrentCursorShape;
         Boolean IsVintageCursor { get; };
-        Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> CursorShapeList { get; };
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> CursorShapeList { get; };
 
         IInspectable CurrentAdjustIndistinguishableColors;
-        Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> AdjustIndistinguishableColorsList { get; };
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> AdjustIndistinguishableColorsList { get; };
 
         IInspectable CurrentBackgroundImageStretchMode;
-        Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> BackgroundImageStretchModeList { get; };
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> BackgroundImageStretchModeList { get; };
 
         IInspectable CurrentFontWeight;
         Boolean IsCustomFontWeight { get; };
-        Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> FontWeightList { get; };
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> FontWeightList { get; };
 
         IInspectable CurrentIntenseTextStyle;
-        Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> IntenseTextStyleList { get; };
+        IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> IntenseTextStyleList { get; };
     }
 }
diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml
index 7d065dc4c92..2f94cf4473b 100644
--- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml
+++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml
@@ -38,10 +38,29 @@
                         Background="{x:Bind mtu:Converters.ColorToBrush(Color)}"
                         CornerRadius="1" />
             </DataTemplate>
-            <CollectionViewSource x:Key="FontAxesCVS"
-                                  x:Name="FontAxesCVS" />
-            <CollectionViewSource x:Key="FontFeaturesCVS"
-                                  x:Name="FontFeaturesCVS" />
+
+            <DataTemplate x:Key="FontKeyValuePairTemplate"
+                          x:DataType="local:FontKeyValuePair">
+                <Grid ColumnSpacing="8">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*" />
+                        <ColumnDefinition Width="Auto" />
+                        <ColumnDefinition Width="Auto" />
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Column="0"
+                               VerticalAlignment="Center"
+                               Text="{x:Bind KeyDisplayString}" />
+                    <muxc:NumberBox Grid.Column="1"
+                                    Value="{x:Bind Value, Mode=TwoWay}" />
+                    <Button Grid.Column="2"
+                            Click="DeleteFontKeyValuePair_Click"
+                            Style="{StaticResource DeleteButtonStyle}"
+                            Tag="{x:Bind (local:FontKeyValuePair)}">
+                        <FontIcon FontSize="{StaticResource StandardIconSize}"
+                                  Glyph="&#xE74D;" />
+                    </Button>
+                </Grid>
+            </DataTemplate>
         </ResourceDictionary>
     </UserControl.Resources>
 
@@ -190,8 +209,7 @@
                                     Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}">
 
                 <StackPanel Margin="0,8,0,0">
-                    <AutoSuggestBox x:Name="_fontFaceBox"
-                                    x:Uid="Profile_FontFaceBox"
+                    <AutoSuggestBox x:Uid="Profile_FontFaceBox"
                                     GotFocus="FontFaceBox_GotFocus"
                                     ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
                                     ItemsSource="{x:Bind FilteredFontList, Mode=OneWay}"
@@ -247,8 +265,7 @@
             </local:SettingContainer>
 
             <!--  Font Weight  -->
-            <local:SettingContainer x:Name="FontWeightContainer"
-                                    x:Uid="Profile_FontWeight"
+            <local:SettingContainer x:Uid="Profile_FontWeight"
                                     ClearSettingValue="{x:Bind Appearance.ClearFontWeight}"
                                     HasSettingValue="{x:Bind Appearance.HasFontWeight, Mode=OneWay}"
                                     SettingOverrideSource="{x:Bind Appearance.FontWeightOverrideSource, Mode=OneWay}"
@@ -284,56 +301,19 @@
                     </Grid>
                 </StackPanel>
             </local:SettingContainer>
-            <local:SettingContainer x:Name="FontAxesContainer"
-                                    x:Uid="Profile_FontAxes"
+            <local:SettingContainer x:Uid="Profile_FontAxes"
                                     ClearSettingValue="{x:Bind Appearance.ClearFontAxes}"
                                     HasSettingValue="{x:Bind Appearance.HasFontAxes, Mode=OneWay}"
-                                    IsEnabled="{x:Bind Appearance.AreFontAxesAvailable, Mode=OneWay}"
                                     SettingOverrideSource="{x:Bind Appearance.FontAxesOverrideSource, Mode=OneWay}"
                                     Style="{StaticResource ExpanderSettingContainerStyle}">
                 <StackPanel Spacing="16">
                     <ListView IsItemClickEnabled="False"
-                              ItemsSource="{Binding Source={StaticResource FontAxesCVS}}"
-                              SelectionMode="None">
-                        <ListView.ItemContainerStyle>
-                            <Style TargetType="ListViewItem">
-                                <Setter Property="Padding" Value="0" />
-                                <Setter Property="Margin" Value="0" />
-                            </Style>
-                        </ListView.ItemContainerStyle>
-                        <ListView.Resources>
-                            <CollectionViewSource x:Key="FontAxesNamesCVS"
-                                                  x:Name="FontAxesNamesCVS" />
-                        </ListView.Resources>
-                        <ListView.ItemTemplate>
-                            <DataTemplate x:DataType="local:AxisKeyValuePair">
-                                <Grid ColumnSpacing="8">
-                                    <Grid.ColumnDefinitions>
-                                        <ColumnDefinition Width="Auto" />
-                                        <ColumnDefinition Width="Auto" />
-                                        <ColumnDefinition Width="*" />
-                                    </Grid.ColumnDefinitions>
-                                    <ComboBox Grid.Column="0"
-                                              IsSynchronizedWithCurrentItem="False"
-                                              ItemsSource="{Binding Source={StaticResource FontAxesNamesCVS}}"
-                                              SelectedIndex="{x:Bind AxisIndex, Mode=TwoWay}" />
-                                    <muxc:NumberBox Grid.Column="1"
-                                                    Value="{x:Bind AxisValue, Mode=TwoWay}" />
-                                    <Button Grid.Column="2"
-                                            HorizontalAlignment="Right"
-                                            Click="DeleteAxisKeyValuePair_Click"
-                                            Style="{StaticResource DeleteButtonStyle}"
-                                            Tag="{x:Bind AxisKey, Mode=OneWay}">
-                                        <FontIcon FontSize="{StaticResource StandardIconSize}"
-                                                  Glyph="&#xE74D;" />
-                                    </Button>
-                                </Grid>
-                            </DataTemplate>
-                        </ListView.ItemTemplate>
-                    </ListView>
-                    <Button Click="AddNewAxisKeyValuePair_Click"
-                            IsEnabled="{x:Bind Appearance.CanFontAxesBeAdded, Mode=OneWay}">
-                        <Button.Content>
+                              ItemTemplate="{StaticResource FontKeyValuePairTemplate}"
+                              ItemsSource="{x:Bind Appearance.FontAxes}"
+                              SelectionMode="None" />
+                    <muxc:DropDownButton x:Name="AddFontAxisButton"
+                                         x:Uid="Profile_AddFontAxisButton">
+                        <muxc:DropDownButton.Content>
                             <StackPanel Orientation="Horizontal">
                                 <FontIcon VerticalAlignment="Bottom"
                                           FontSize="{StaticResource StandardIconSize}"
@@ -341,60 +321,26 @@
                                 <TextBlock x:Uid="Profile_AddNewFontAxis"
                                            Style="{StaticResource IconButtonTextBlockStyle}" />
                             </StackPanel>
-                        </Button.Content>
-                    </Button>
+                        </muxc:DropDownButton.Content>
+                        <muxc:DropDownButton.Flyout>
+                            <MenuFlyout />
+                        </muxc:DropDownButton.Flyout>
+                    </muxc:DropDownButton>
                 </StackPanel>
             </local:SettingContainer>
-            <local:SettingContainer x:Name="FontFeaturesContainer"
-                                    x:Uid="Profile_FontFeatures"
+            <local:SettingContainer x:Uid="Profile_FontFeatures"
                                     ClearSettingValue="{x:Bind Appearance.ClearFontFeatures}"
                                     HasSettingValue="{x:Bind Appearance.HasFontFeatures, Mode=OneWay}"
-                                    IsEnabled="{x:Bind Appearance.AreFontFeaturesAvailable, Mode=OneWay}"
                                     SettingOverrideSource="{x:Bind Appearance.FontFeaturesOverrideSource, Mode=OneWay}"
                                     Style="{StaticResource ExpanderSettingContainerStyle}">
                 <StackPanel Spacing="16">
                     <ListView IsItemClickEnabled="False"
-                              ItemsSource="{Binding Source={StaticResource FontFeaturesCVS}}"
-                              SelectionMode="None">
-                        <ListView.ItemContainerStyle>
-                            <Style TargetType="ListViewItem">
-                                <Setter Property="Padding" Value="0" />
-                                <Setter Property="Margin" Value="0" />
-                            </Style>
-                        </ListView.ItemContainerStyle>
-                        <ListView.Resources>
-                            <CollectionViewSource x:Key="FontFeaturesNamesCVS"
-                                                  x:Name="FontFeaturesNamesCVS" />
-                        </ListView.Resources>
-                        <ListView.ItemTemplate>
-                            <DataTemplate x:DataType="local:FeatureKeyValuePair">
-                                <Grid ColumnSpacing="8">
-                                    <Grid.ColumnDefinitions>
-                                        <ColumnDefinition Width="Auto" />
-                                        <ColumnDefinition Width="Auto" />
-                                        <ColumnDefinition Width="*" />
-                                    </Grid.ColumnDefinitions>
-                                    <ComboBox Grid.Column="0"
-                                              IsSynchronizedWithCurrentItem="False"
-                                              ItemsSource="{Binding Source={StaticResource FontFeaturesNamesCVS}}"
-                                              SelectedIndex="{x:Bind FeatureIndex, Mode=TwoWay}" />
-                                    <muxc:NumberBox Grid.Column="1"
-                                                    Value="{x:Bind FeatureValue, Mode=TwoWay}" />
-                                    <Button Grid.Column="2"
-                                            HorizontalAlignment="Right"
-                                            Click="DeleteFeatureKeyValuePair_Click"
-                                            Style="{StaticResource DeleteButtonStyle}"
-                                            Tag="{x:Bind FeatureKey, Mode=OneWay}">
-                                        <FontIcon FontSize="{StaticResource StandardIconSize}"
-                                                  Glyph="&#xE74D;" />
-                                    </Button>
-                                </Grid>
-                            </DataTemplate>
-                        </ListView.ItemTemplate>
-                    </ListView>
-                    <Button Click="AddNewFeatureKeyValuePair_Click"
-                            IsEnabled="{x:Bind Appearance.CanFontFeaturesBeAdded, Mode=OneWay}">
-                        <Button.Content>
+                              ItemTemplate="{StaticResource FontKeyValuePairTemplate}"
+                              ItemsSource="{x:Bind Appearance.FontFeatures}"
+                              SelectionMode="None" />
+                    <muxc:DropDownButton x:Name="AddFontFeatureButton"
+                                         x:Uid="Profile_AddFontFeatureButton">
+                        <muxc:DropDownButton.Content>
                             <StackPanel Orientation="Horizontal">
                                 <FontIcon VerticalAlignment="Bottom"
                                           FontSize="{StaticResource StandardIconSize}"
@@ -402,8 +348,11 @@
                                 <TextBlock x:Uid="Profile_AddNewFontFeature"
                                            Style="{StaticResource IconButtonTextBlockStyle}" />
                             </StackPanel>
-                        </Button.Content>
-                    </Button>
+                        </muxc:DropDownButton.Content>
+                        <muxc:DropDownButton.Flyout>
+                            <MenuFlyout />
+                        </muxc:DropDownButton.Flyout>
+                    </muxc:DropDownButton>
                 </StackPanel>
             </local:SettingContainer>
 
@@ -721,8 +670,7 @@
             </local:SettingContainer>
 
             <!--  Background Image Opacity  -->
-            <local:SettingContainer x:Name="BackgroundImageOpacityContainer"
-                                    x:Uid="Profile_BackgroundImageOpacity"
+            <local:SettingContainer x:Uid="Profile_BackgroundImageOpacity"
                                     ClearSettingValue="{x:Bind Appearance.ClearBackgroundImageOpacity}"
                                     HasSettingValue="{x:Bind Appearance.HasBackgroundImageOpacity, Mode=OneWay}"
                                     SettingOverrideSource="{x:Bind Appearance.BackgroundImageOpacityOverrideSource, Mode=OneWay}"
diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
index d373926b380..66d8371c3ea 100644
--- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
+++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
@@ -176,36 +176,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
     }
     CATCH_LOG();
 
-    Editor::Font ProfileViewModel::FindFontWithLocalizedName(const winrt::hstring& name) noexcept
-    {
-        // look for the current font in our shown list of fonts
-        Editor::Font fallbackFont{ nullptr };
-        try
-        {
-            if (!CompleteFontList())
-            {
-                UpdateFontList();
-            }
-            const auto& currentFontList{ CompleteFontList() };
-            fallbackFont = currentFontList.First().Current();
-            for (const auto& font : currentFontList)
-            {
-                if (font.LocalizedName() == name)
-                {
-                    return font;
-                }
-                else if (font.LocalizedName() == L"Cascadia Mono")
-                {
-                    fallbackFont = font;
-                }
-            }
-        }
-        CATCH_LOG();
-
-        // we couldn't find the desired font, set to "Cascadia Mono" if we found that since it ships by default
-        return fallbackFont;
-    }
-
     static winrt::hstring getLocalizedStringByIndex(IDWriteLocalizedStrings* strings, UINT32 index)
     {
         UINT32 length = 0;
@@ -234,7 +204,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         THROW_IF_FAILED(family->GetFamilyNames(familyNames.addressof()));
 
         // If en-us is missing we fall back to whatever is at index 0.
-        const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-us", 0);
+        const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-US", 0);
         // If our locale is missing we fall back to en-us.
         const auto li = getLocalizedStringIndex(familyNames.get(), locale, ci);
 
@@ -242,7 +212,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         // If the canonical/localized indices are the same, there's no need to get the other string.
         auto localized = ci == li ? canonical : getLocalizedStringByIndex(familyNames.get(), li);
 
-        return make<Font>(std::move(canonical), std::move(localized), family);
+        return make<Font>(std::move(canonical), std::move(localized));
     }
 
     winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept
diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
index 04225a46efa..f0ce84f1f05 100644
--- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
+++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
@@ -33,7 +33,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         static void UpdateFontList() noexcept;
         static Windows::Foundation::Collections::IObservableVector<Editor::Font> CompleteFontList() noexcept { return _FontList; };
         static Windows::Foundation::Collections::IObservableVector<Editor::Font> MonospaceFontList() noexcept { return _MonospaceFontList; };
-        static Editor::Font FindFontWithLocalizedName(winrt::hstring const& name) noexcept;
 
         ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings);
         Model::TerminalSettings TermSettings() const;
@@ -156,9 +155,3 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         guid _ProfileGuid{};
     };
 };
-
-namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
-{
-    // Since we have static functions, we need a factory.
-    BASIC_FACTORY(ProfileViewModel);
-}
diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
index 315afdfb4fe..c0bc407c7ae 100644
--- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
+++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
@@ -34,10 +34,6 @@ namespace Microsoft.Terminal.Settings.Editor
 
     runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
     {
-        static Windows.Foundation.Collections.IObservableVector<Font> CompleteFontList { get; };
-        static Windows.Foundation.Collections.IObservableVector<Font> MonospaceFontList { get; };
-        static Font FindFontWithLocalizedName(String name);
-
         Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; };
 
         event Windows.Foundation.TypedEventHandler<ProfileViewModel, DeleteProfileEventArgs> DeleteProfileRequested;
diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
index 405734f241b..f27f774c55c 100644
--- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
@@ -733,10 +733,16 @@
     <value>Browse...</value>
     <comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
   </data>
+  <data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
+    <value>Add new font axis</value>
+  </data>
   <data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
     <value>Add new</value>
     <comment>Button label that adds a new font axis for the current font.</comment>
   </data>
+  <data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
+    <value>Add new font feature</value>
+  </data>
   <data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
     <value>Add new</value>
     <comment>Button label that adds a new font feature for the current font.</comment>
diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp
index fd9a181ae30..746ceee649d 100644
--- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp
+++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp
@@ -32,24 +32,21 @@ winrt::com_ptr<FontConfig> FontConfig::CopyFontInfo(const FontConfig* source, wi
 
     // We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;`
     // since that'll just create a reference; we have to manually copy the values.
-    if (source->_FontAxes)
-    {
+    static constexpr auto cloneFontMap = [](const IFontFeatureMap& map) {
         std::map<winrt::hstring, float> fontAxes;
-        for (const auto keyValuePair : source->_FontAxes.value())
+        for (const auto& [k, v] : map)
         {
-            fontAxes.insert(std::pair<winrt::hstring, float>(keyValuePair.Key(), keyValuePair.Value()));
+            fontAxes.emplace(k, v);
         }
-        fontInfo->_FontAxes = winrt::single_threaded_map(std::move(fontAxes));
+        return winrt::single_threaded_map(std::move(fontAxes));
+    };
+    if (source->_FontAxes)
+    {
+        fontInfo->_FontAxes = cloneFontMap(*source->_FontAxes);
     }
-
     if (source->_FontFeatures)
     {
-        std::map<winrt::hstring, uint32_t> fontFeatures;
-        for (const auto keyValuePair : source->_FontFeatures.value())
-        {
-            fontFeatures.insert(std::pair<winrt::hstring, uint32_t>(keyValuePair.Key(), keyValuePair.Value()));
-        }
-        fontInfo->_FontFeatures = winrt::single_threaded_map(std::move(fontFeatures));
+        fontInfo->_FontFeatures = cloneFontMap(*source->_FontFeatures);
     }
 
     return fontInfo;
diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.h b/src/cascadia/TerminalSettingsModel/FontConfig.h
index 8a789278eb7..c249f5a1b46 100644
--- a/src/cascadia/TerminalSettingsModel/FontConfig.h
+++ b/src/cascadia/TerminalSettingsModel/FontConfig.h
@@ -24,7 +24,7 @@ Author(s):
 #include <DefaultSettings.h>
 
 using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
-using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
+using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 
 namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 {
diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.idl b/src/cascadia/TerminalSettingsModel/FontConfig.idl
index 9145919c438..0303b0c27e5 100644
--- a/src/cascadia/TerminalSettingsModel/FontConfig.idl
+++ b/src/cascadia/TerminalSettingsModel/FontConfig.idl
@@ -18,7 +18,7 @@ namespace Microsoft.Terminal.Settings.Model
         INHERITABLE_FONT_SETTING(String, FontFace);
         INHERITABLE_FONT_SETTING(Single, FontSize);
         INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight);
-        INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA UInt32>, FontFeatures);
+        INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA Single>, FontFeatures);
         INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA Single>, FontAxes);
         INHERITABLE_FONT_SETTING(Boolean, EnableBuiltinGlyphs);
         INHERITABLE_FONT_SETTING(Boolean, EnableColorGlyphs);
diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h
index ec94d23c92e..e08047bf0e9 100644
--- a/src/cascadia/TerminalSettingsModel/JsonUtils.h
+++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h
@@ -731,6 +731,16 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
 
         Json::Value ToJson(const float val)
         {
+            // Convert floats that are almost integers to proper integers, because that looks way neater in JSON.
+            if (val >= static_cast<float>(Json::Value::minInt) && val <= static_cast<float>(Json::Value::maxInt))
+            {
+                const auto i = static_cast<Json::Value::Int>(std::lround(val));
+                const auto f = static_cast<float>(i);
+                if (std::fabs(f - val) < 1e-6f)
+                {
+                    return i;
+                }
+            }
             return val;
         }
 
@@ -755,6 +765,16 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
 
         Json::Value ToJson(const double val)
         {
+            // Convert floats that are almost integers to proper integers, because that looks way neater in JSON.
+            if (val >= static_cast<double>(Json::Value::minInt) && val <= static_cast<double>(Json::Value::maxInt))
+            {
+                const auto i = static_cast<Json::Value::Int>(std::lround(val));
+                const auto f = static_cast<double>(i);
+                if (std::fabs(f - val) < 1e-6)
+                {
+                    return i;
+                }
+            }
             return val;
         }
 
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
index 45b4db2aa4d..33658427f94 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
@@ -21,7 +21,7 @@ Author(s):
 #include <conattrs.hpp>
 
 using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
-using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
+using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring>;
 
 // fwdecl unittest classes
diff --git a/src/cascadia/UnitTests_Control/MockControlSettings.h b/src/cascadia/UnitTests_Control/MockControlSettings.h
index 9a9ebcda745..50b71687e8a 100644
--- a/src/cascadia/UnitTests_Control/MockControlSettings.h
+++ b/src/cascadia/UnitTests_Control/MockControlSettings.h
@@ -8,7 +8,7 @@ Licensed under the MIT license.
 #include <conattrs.hpp>
 #include "../../inc/ControlProperties.h"
 
-using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
+using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
 
 namespace ControlUnitTests
diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp
index 82f9f924de3..20357656638 100644
--- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp
+++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp
@@ -151,7 +151,7 @@ namespace SettingsModelUnitTests
 
                 "font": {
                     "face": "Cascadia Mono",
-                    "size": 12.0,
+                    "size": 12,
                     "weight": "normal"
                 },
                 "padding": "8, 8, 8, 8",
@@ -175,7 +175,7 @@ namespace SettingsModelUnitTests
                 "backgroundImage": "made_you_look.jpeg",
                 "backgroundImageStretchMode": "uniformToFill",
                 "backgroundImageAlignment": "center",
-                "backgroundImageOpacity": 1.0,
+                "backgroundImageOpacity": 1,
 
                 "scrollbarState": "visible",
                 "snapOnInput": true,
@@ -298,8 +298,8 @@ namespace SettingsModelUnitTests
 
         // complex command with key chords
         static constexpr std::string_view actionsString4A{ R"([
-                                                { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" },
-                                                { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" }
+                                                { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" },
+                                                { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" }
                                             ])" };
 
         // command with name and icon and multiple key chords
@@ -323,8 +323,8 @@ namespace SettingsModelUnitTests
                                                 {
                                                     "name": "Change font size...",
                                                     "commands": [
-                                                        { "command": { "action": "adjustFontSize", "delta": 1.0 } },
-                                                        { "command": { "action": "adjustFontSize", "delta": -1.0 } },
+                                                        { "command": { "action": "adjustFontSize", "delta": 1 } },
+                                                        { "command": { "action": "adjustFontSize", "delta": -1 } },
                                                         { "command": "resetFontSize" },
                                                     ]
                                                 }
@@ -523,7 +523,7 @@ namespace SettingsModelUnitTests
                 "name": "Profile with legacy font settings",
 
                 "fontFace": "Cascadia Mono",
-                "fontSize": 12.0,
+                "fontSize": 12,
                 "fontWeight": "normal"
             })" };
 
@@ -533,7 +533,7 @@ namespace SettingsModelUnitTests
 
                 "font": {
                     "face": "Cascadia Mono",
-                    "size": 12.0,
+                    "size": 12,
                     "weight": "normal"
                 }
             })" };
@@ -1007,8 +1007,8 @@ namespace SettingsModelUnitTests
                 {
                     "name": "Change font size...",
                     "commands": [
-                        { "command": { "action": "adjustFontSize", "delta": 1.0 } },
-                        { "command": { "action": "adjustFontSize", "delta": -1.0 } },
+                        { "command": { "action": "adjustFontSize", "delta": 1 } },
+                        { "command": { "action": "adjustFontSize", "delta": -1 } },
                         { "command": "resetFontSize" },
                     ]
                 }
diff --git a/src/cascadia/ut_app/JsonUtilsTests.cpp b/src/cascadia/ut_app/JsonUtilsTests.cpp
index 475e76abb5f..29404fc1a96 100644
--- a/src/cascadia/ut_app/JsonUtilsTests.cpp
+++ b/src/cascadia/ut_app/JsonUtilsTests.cpp
@@ -354,16 +354,16 @@ namespace TerminalAppUnitTests
         TryBasicType(int{ -1024 }, -1024);
         TryBasicType(std::numeric_limits<unsigned int>::max(), std::numeric_limits<unsigned int>::max());
         TryBasicType(false, false);
-        TryBasicType(1.0f, 1.0f);
+        TryBasicType(1.1f, 1.1f);
 
         // string -> wstring
         TryBasicType(std::wstring{ L"hello" }, "hello");
 
         // float -> double
-        TryBasicType(1.0, 1.0f);
+        TryBasicType(static_cast<double>(1.1f), 1.1f);
 
         // double -> float
-        TryBasicType(1.0f, 1.0);
+        TryBasicType(1.1f, static_cast<double>(1.1f));
 
         TryBasicType(til::color{ 0xab, 0xcd, 0xef }, "#ABCDEF");
         TryBasicType(til::color{ 0xcc, 0xcc, 0xcc }, "#CCC", "#CCCCCC");
diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp
index 674dc3baf7e..2fbaf7cd52a 100644
--- a/src/renderer/atlas/AtlasEngine.api.cpp
+++ b/src/renderer/atlas/AtlasEngine.api.cpp
@@ -493,7 +493,7 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT, wil::zwstring_v
     return S_OK;
 }
 
-[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
+[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
 {
     // We're currently faced with a font caching bug that we're unable to reproduce locally. See GH#9375.
     // But it occurs often enough and has no proper workarounds, so we're forced to fix it.
@@ -544,7 +544,7 @@ void AtlasEngine::_resolveTransparencySettings() noexcept
     }
 }
 
-[[nodiscard]] HRESULT AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
+[[nodiscard]] HRESULT AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
 try
 {
     std::vector<DWRITE_FONT_FEATURE> fontFeatures;
@@ -567,19 +567,20 @@ try
             if (p.first.size() == 4)
             {
                 const auto s = p.first.data();
+                const auto v = static_cast<UINT32>(std::max(0l, lrintf(p.second)));
                 switch (const auto tag = DWRITE_MAKE_FONT_FEATURE_TAG(s[0], s[1], s[2], s[3]))
                 {
                 case DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES:
-                    fontFeatures[0].parameter = p.second;
+                    fontFeatures[0].parameter = v;
                     break;
                 case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES:
-                    fontFeatures[1].parameter = p.second;
+                    fontFeatures[1].parameter = v;
                     break;
                 case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES:
-                    fontFeatures[2].parameter = p.second;
+                    fontFeatures[2].parameter = v;
                     break;
                 default:
-                    fontFeatures.emplace_back(tag, p.second);
+                    fontFeatures.emplace_back(tag, v);
                     break;
                 }
             }
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index 9ad4425beae..1f26644ebe6 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -78,7 +78,7 @@ namespace Microsoft::Console::Render::Atlas
         void SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept;
         void SetWarningCallback(std::function<void(HRESULT, wil::zwstring_view)> pfn) noexcept;
         [[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept;
-        [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
+        [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
 
     private:
         // AtlasEngine.cpp
@@ -96,7 +96,7 @@ namespace Microsoft::Console::Render::Atlas
 
         // AtlasEngine.api.cpp
         void _resolveTransparencySettings() noexcept;
-        [[nodiscard]] HRESULT _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
+        [[nodiscard]] HRESULT _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, float>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
         void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr);
         [[nodiscard]] bool _updateWithNearbyFontCollection() noexcept;
 


Test patch: