Repository: microsoft/terminal (language: cpp)

Issue:
Unable to find the selected font "Cascadia Mono" in Windows 10
### Windows Terminal version

1.13.10336.0

### Windows build number

10.0.19044.0

### Other Software

I have both Windows Terminal (1.11.3471.0) and Windows Terminal Preview (1.13.10336.0) installed. The Store says Windows Terminal Preview updated today, but both apps are unable to find the "Cascadia Mono" font, which I'm told should be bundled with the app.

### Steps to reproduce

Open Windows Terminal Preview

### Expected Behavior

A "Warning" popup does not appear.

### Actual Behavior

A "Warning" popup in the Terminal window appears with the following text
```
Unable to find the selected font "Cascadia Mono".

"Consolas" has been selected instead.

Please either install the missing font or choose another one.
```


Discussion / hints:
Again?! :(

This was a really annoying issue some 5+ months ago. Not great that it's popped up again.
Thanks for the suggestion! Please make sure to [search for duplicates](https://github.com/microsoft/terminal/issues?q=is%3Aissue+Unable+to+find+the+selected+font+%22Cascadia+Mono%22+) when filing new issues. This is already being tracked by another issue on our repo - please refer to #11648 for more discussion.

/dup #11648
Hi! We've identified this issue as a duplicate of another one that already exists on this Issue Tracker. This specific instance is being closed in favor of tracking the concern over on the referenced thread. Thanks for your report!
FYI: When I implemented a fix I used APIs which only work on Windows 11.
Today I made some changes to make it work on Windows 10. I'll open a PR soon.
I'm repurposing this issue to submit a change for Windows 10 support.

Fix patch:
diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
index baa44c2bb15..86eefe56868 100644
--- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
+++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
@@ -7,55 +7,8 @@
 #include "EnumEntry.h"
 
 #include <LibraryResources.h>
-#include "..\WinRTUtils\inc\Utils.h"
-
-// This function is a copy of DxFontInfo::_NearbyCollection() with
-// * the call to DxFontInfo::s_GetNearbyFonts() inlined
-// * checkForUpdates for GetSystemFontCollection() set to true
-static wil::com_ptr<IDWriteFontCollection1> NearbyCollection(IDWriteFactory* dwriteFactory)
-{
-    // The convenience interfaces for loading fonts from files
-    // are only available on Windows 10+.
-    wil::com_ptr<IDWriteFactory6> factory6;
-    // wil's query() facilities don't work inside WinRT land at the moment.
-    // They produce a compilation error due to IUnknown and winrt::Windows::Foundation::IUnknown being ambiguous.
-    if (!SUCCEEDED(dwriteFactory->QueryInterface(__uuidof(IDWriteFactory6), factory6.put_void())))
-    {
-        return nullptr;
-    }
-
-    wil::com_ptr<IDWriteFontCollection1> systemFontCollection;
-    THROW_IF_FAILED(factory6->GetSystemFontCollection(false, systemFontCollection.addressof(), true));
-
-    wil::com_ptr<IDWriteFontSet> systemFontSet;
-    THROW_IF_FAILED(systemFontCollection->GetFontSet(systemFontSet.addressof()));
-
-    wil::com_ptr<IDWriteFontSetBuilder2> fontSetBuilder2;
-    THROW_IF_FAILED(factory6->CreateFontSetBuilder(fontSetBuilder2.addressof()));
-
-    THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.get()));
-
-    {
-        const std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
-        const auto folder{ module.parent_path() };
-
-        for (const auto& p : std::filesystem::directory_iterator(folder))
-        {
-            if (til::ends_with(p.path().native(), L".ttf"))
-            {
-                fontSetBuilder2->AddFontFile(p.path().c_str());
-            }
-        }
-    }
-
-    wil::com_ptr<IDWriteFontSet> fontSet;
-    THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(fontSet.addressof()));
-
-    wil::com_ptr<IDWriteFontCollection1> fontCollection;
-    THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.get(), &fontCollection));
-
-    return fontCollection;
-}
+#include "../WinRTUtils/inc/Utils.h"
+#include "../../renderer/base/FontCache.h"
 
 using namespace winrt::Windows::UI::Text;
 using namespace winrt::Windows::UI::Xaml;
@@ -166,15 +119,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
         std::vector<Editor::Font> fontList;
         std::vector<Editor::Font> monospaceFontList;
 
-        // get a DWriteFactory
-        com_ptr<IDWriteFactory> factory;
-        THROW_IF_FAILED(DWriteCreateFactory(
-            DWRITE_FACTORY_TYPE_SHARED,
-            __uuidof(IDWriteFactory),
-            reinterpret_cast<::IUnknown**>(factory.put())));
-
         // get the font collection; subscribe to updates
-        const auto fontCollection = NearbyCollection(factory.get());
+        const auto fontCollection = ::Microsoft::Console::Render::FontCache::GetFresh();
 
         for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i)
         {
diff --git a/src/features.xml b/src/features.xml
index 2a04dd1b0b0..d9bc3a87e90 100644
--- a/src/features.xml
+++ b/src/features.xml
@@ -67,6 +67,15 @@
         </alwaysDisabledBrandingTokens>
     </feature>
 
+    <feature>
+        <name>Feature_NearbyFontLoading</name>
+        <description>Controls whether fonts in the same directory as the binary are used during rendering. Disabled for conhost so that it doesn't iterate the entire system32 directory.</description>
+        <stage>AlwaysEnabled</stage>
+        <alwaysDisabledBrandingTokens>
+            <brandingToken>WindowsInbox</brandingToken>
+        </alwaysDisabledBrandingTokens>
+    </feature>
+
     <feature>
         <name>Feature_AdjustIndistinguishableText</name>
         <description>If enabled, the foreground color will, when necessary, be automatically adjusted to make it more visible.</description>
diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp
index 2d0fa45e78a..2e0db997333 100644
--- a/src/renderer/atlas/AtlasEngine.api.cpp
+++ b/src/renderer/atlas/AtlasEngine.api.cpp
@@ -4,6 +4,8 @@
 #include "pch.h"
 #include "AtlasEngine.h"
 
+#include "../base/FontCache.h"
+
 // #### NOTE ####
 // If you see any code in here that contains "_r." you might be seeing a race condition.
 // The AtlasEngine::Present() method is called on a background thread without any locks,
@@ -227,7 +229,7 @@ try
     }
 #endif
 
-    _resolveFontMetrics(fontInfoDesired, fontInfo);
+    _resolveFontMetrics(nullptr, fontInfoDesired, fontInfo);
     return S_OK;
 }
 CATCH_RETURN()
@@ -401,7 +403,50 @@ void AtlasEngine::ToggleShaderEffects() 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
-try
+{
+    static constexpr std::array fallbackFaceNames{ static_cast<const wchar_t*>(nullptr), L"Consolas", L"Lucida Console", L"Courier New" };
+    auto it = fallbackFaceNames.begin();
+    const auto end = fallbackFaceNames.end();
+
+    for (;;)
+    {
+        try
+        {
+            _updateFont(*it, fontInfoDesired, fontInfo, features, axes);
+            return S_OK;
+        }
+        catch (...)
+        {
+            ++it;
+            if (it == end)
+            {
+                RETURN_CAUGHT_EXCEPTION();
+            }
+            else
+            {
+                LOG_CAUGHT_EXCEPTION();
+            }
+        }
+    }
+}
+
+void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
+{
+    _api.hyperlinkHoveredId = hoveredId;
+}
+
+#pragma endregion
+
+void AtlasEngine::_resolveAntialiasingMode() noexcept
+{
+    // If the user asks for ClearType, but also for a transparent background
+    // (which our ClearType shader doesn't simultaneously support)
+    // then we need to sneakily force the renderer to grayscale AA.
+    const auto forceGrayscaleAA = _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && !_api.backgroundOpaqueMixin;
+    _api.realizedAntialiasingMode = forceGrayscaleAA ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
+}
+
+void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes)
 {
     std::vector<DWRITE_FONT_FEATURE> fontFeatures;
     if (!features.empty())
@@ -478,7 +523,7 @@ try
     }
 
     const auto previousCellSize = _api.fontMetrics.cellSize;
-    _resolveFontMetrics(fontInfoDesired, fontInfo, &_api.fontMetrics);
+    _resolveFontMetrics(faceName, fontInfoDesired, fontInfo, &_api.fontMetrics);
     _api.fontFeatures = std::move(fontFeatures);
     _api.fontAxisValues = std::move(fontAxisValues);
 
@@ -489,37 +534,21 @@ try
         _api.cellCount = _api.sizeInPixel / _api.fontMetrics.cellSize;
         WI_SetFlag(_api.invalidations, ApiInvalidations::Size);
     }
-
-    return S_OK;
-}
-CATCH_RETURN()
-
-void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
-{
-    _api.hyperlinkHoveredId = hoveredId;
 }
 
-#pragma endregion
-
-void AtlasEngine::_resolveAntialiasingMode() noexcept
+void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const
 {
-    // If the user asks for ClearType, but also for a transparent background
-    // (which our ClearType shader doesn't simultaneously support)
-    // then we need to sneakily force the renderer to grayscale AA.
-    const auto forceGrayscaleAA = _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && !_api.backgroundOpaqueMixin;
-    _api.realizedAntialiasingMode = forceGrayscaleAA ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
-}
-
-void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics) const
-{
-    auto requestedFaceName = fontInfoDesired.GetFaceName().c_str();
     const auto requestedFamily = fontInfoDesired.GetFamily();
     auto requestedWeight = fontInfoDesired.GetWeight();
     auto requestedSize = fontInfoDesired.GetEngineSize();
 
     if (!requestedFaceName)
     {
-        requestedFaceName = L"Consolas";
+        requestedFaceName = fontInfoDesired.GetFaceName().c_str();
+        if (!requestedFaceName)
+        {
+            requestedFaceName = L"Consolas";
+        }
     }
     if (!requestedSize.Y)
     {
@@ -530,16 +559,15 @@ void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, Fo
         requestedWeight = DWRITE_FONT_WEIGHT_NORMAL;
     }
 
-    wil::com_ptr<IDWriteFontCollection> systemFontCollection;
-    THROW_IF_FAILED(_sr.dwriteFactory->GetSystemFontCollection(systemFontCollection.addressof(), false));
+    auto fontCollection = FontCache::GetCached();
 
     u32 index = 0;
     BOOL exists = false;
-    THROW_IF_FAILED(systemFontCollection->FindFamilyName(requestedFaceName, &index, &exists));
+    THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists));
     THROW_HR_IF(DWRITE_E_NOFONT, !exists);
 
     wil::com_ptr<IDWriteFontFamily> fontFamily;
-    THROW_IF_FAILED(systemFontCollection->GetFontFamily(index, fontFamily.addressof()));
+    THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof()));
 
     wil::com_ptr<IDWriteFont> font;
     THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(static_cast<DWRITE_FONT_WEIGHT>(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof()));
@@ -601,6 +629,7 @@ void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, Fo
         // NOTE: From this point onward no early returns or throwing code should exist,
         // as we might cause _api to be in an inconsistent state otherwise.
 
+        fontMetrics->fontCollection = std::move(fontCollection);
         fontMetrics->fontName = std::move(fontName);
         fontMetrics->fontSizeInDIP = static_cast<float>(fontSizeInPx / static_cast<double>(_api.dpi) * 96.0);
         fontMetrics->baselineInDIP = static_cast<float>(baseline / static_cast<double>(_api.dpi) * 96.0);
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index dd9267698b3..bf108602862 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -7,6 +7,7 @@
 #include <shader_ps.h>
 #include <shader_vs.h>
 
+#include "../base/FontCache.h"
 #include "../../interactivity/win32/CustomWindowMessages.h"
 
 // #### NOTE ####
@@ -1023,7 +1024,7 @@ void AtlasEngine::_recreateFontDependentResources()
                 const auto fontStyle = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
                 auto& textFormat = _r.textFormats[italic][bold];
 
-                THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontMetrics.fontName.get(), nullptr, fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontMetrics.fontSizeInDIP, L"", textFormat.put()));
+                THROW_IF_FAILED(_sr.dwriteFactory->CreateTextFormat(_api.fontMetrics.fontName.get(), _api.fontMetrics.fontCollection.get(), fontWeight, fontStyle, DWRITE_FONT_STRETCH_NORMAL, _api.fontMetrics.fontSizeInDIP, L"", textFormat.put()));
                 textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
                 textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
 
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index df4f095e42c..5c417b7386b 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -376,6 +376,7 @@ namespace Microsoft::Console::Render
 
         struct FontMetrics
         {
+            wil::com_ptr<IDWriteFontCollection> fontCollection;
             wil::unique_process_heap_string fontName;
             float baselineInDIP = 0.0f;
             float fontSizeInDIP = 0.0f;
@@ -615,7 +616,8 @@ namespace Microsoft::Console::Render
 
         // AtlasEngine.api.cpp
         void _resolveAntialiasingMode() noexcept;
-        void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
+        void _updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes);
+        void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
 
         // AtlasEngine.r.cpp
         void _setShaderResources() const;
diff --git a/src/renderer/base/FontCache.h b/src/renderer/base/FontCache.h
new file mode 100644
index 00000000000..e7a5c4134ca
--- /dev/null
+++ b/src/renderer/base/FontCache.h
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+namespace Microsoft::Console::Render::FontCache
+{
+    namespace details
+    {
+        inline const std::vector<wil::com_ptr<IDWriteFontFile>>& getNearbyFontFiles(IDWriteFactory5* factory5)
+        {
+            static const auto fontFiles = [=]() {
+                std::vector<wil::com_ptr<IDWriteFontFile>> files;
+
+                const std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
+                const auto folder{ module.parent_path() };
+
+                for (const auto& p : std::filesystem::directory_iterator(folder))
+                {
+                    if (til::ends_with(p.path().native(), L".ttf"))
+                    {
+                        wil::com_ptr<IDWriteFontFile> fontFile;
+                        if (SUCCEEDED_LOG(factory5->CreateFontFileReference(p.path().c_str(), nullptr, fontFile.addressof())))
+                        {
+                            files.emplace_back(std::move(fontFile));
+                        }
+                    }
+                }
+
+                files.shrink_to_fit();
+                return files;
+            }();
+            return fontFiles;
+        }
+
+        inline wil::com_ptr<IDWriteFontCollection> getFontCollection(bool forceUpdate)
+        {
+            wil::com_ptr<IDWriteFactory> factory;
+            THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof())));
+
+            wil::com_ptr<IDWriteFontCollection> systemFontCollection;
+            THROW_IF_FAILED(factory->GetSystemFontCollection(systemFontCollection.addressof(), forceUpdate));
+
+            if constexpr (Feature_NearbyFontLoading::IsEnabled())
+            {
+                // IDWriteFactory5 is supported since Windows 10, build 15021.
+                const auto factory5 = factory.try_query<IDWriteFactory5>();
+                if (!factory5)
+                {
+                    return systemFontCollection;
+                }
+
+                const auto& nearbyFontFiles = getNearbyFontFiles(factory5.get());
+                if (nearbyFontFiles.empty())
+                {
+                    return systemFontCollection;
+                }
+
+                wil::com_ptr<IDWriteFontSet> systemFontSet;
+                // IDWriteFontCollection1 is supported since Windows 7.
+                THROW_IF_FAILED(systemFontCollection.query<IDWriteFontCollection1>()->GetFontSet(systemFontSet.addressof()));
+
+                wil::com_ptr<IDWriteFontSetBuilder1> fontSetBuilder;
+                THROW_IF_FAILED(factory5->CreateFontSetBuilder(fontSetBuilder.addressof()));
+                THROW_IF_FAILED(fontSetBuilder->AddFontSet(systemFontSet.get()));
+
+                for (const auto& file : nearbyFontFiles)
+                {
+                    LOG_IF_FAILED(fontSetBuilder->AddFontFile(file.get()));
+                }
+
+                wil::com_ptr<IDWriteFontSet> fontSet;
+                THROW_IF_FAILED(fontSetBuilder->CreateFontSet(fontSet.addressof()));
+
+                wil::com_ptr<IDWriteFontCollection1> fontCollection;
+                THROW_IF_FAILED(factory5->CreateFontCollectionFromFontSet(fontSet.get(), fontCollection.addressof()));
+
+                return std::move(fontCollection);
+            }
+            else
+            {
+                return systemFontCollection;
+            }
+        }
+    }
+
+    inline wil::com_ptr<IDWriteFontCollection> GetCached()
+    {
+        return details::getFontCollection(false);
+    }
+
+    inline wil::com_ptr<IDWriteFontCollection> GetFresh()
+    {
+        return details::getFontCollection(true);
+    }
+}
diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj
index 1bcfe8dddb0..5d7bc816c41 100644
--- a/src/renderer/base/lib/base.vcxproj
+++ b/src/renderer/base/lib/base.vcxproj
@@ -34,6 +34,7 @@
     <ClInclude Include="..\..\inc\IRenderTarget.hpp" />
     <ClInclude Include="..\..\inc\RenderEngineBase.hpp" />
     <ClInclude Include="..\..\inc\RenderSettings.hpp" />
+    <ClInclude Include="..\FontCache.h" />
     <ClInclude Include="..\precomp.h" />
     <ClInclude Include="..\renderer.hpp" />
     <ClInclude Include="..\thread.hpp" />
diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters
index bda75e18fe8..4e8f12c1821 100644
--- a/src/renderer/base/lib/base.vcxproj.filters
+++ b/src/renderer/base/lib/base.vcxproj.filters
@@ -89,6 +89,9 @@
     <ClInclude Include="..\..\inc\RenderSettings.hpp">
       <Filter>Header Files\inc</Filter>
     </ClInclude>
+    <ClInclude Include="..\FontCache.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp
index e5c6749f4be..ba3eca1a864 100644
--- a/src/renderer/dx/DxFontInfo.cpp
+++ b/src/renderer/dx/DxFontInfo.cpp
@@ -2,11 +2,9 @@
 // Licensed under the MIT license.
 
 #include "precomp.h"
-
 #include "DxFontInfo.h"
 
-#include "unicode.hpp"
-
+#include <unicode.hpp>
 #include <VersionHelpers.h>
 
 static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" };
@@ -14,7 +12,6 @@ static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Luci
 using namespace Microsoft::Console::Render;
 
 DxFontInfo::DxFontInfo() noexcept :
-    _familyName(),
     _weight(DWRITE_FONT_WEIGHT_NORMAL),
     _style(DWRITE_FONT_STYLE_NORMAL),
     _stretch(DWRITE_FONT_STRETCH_NORMAL),
@@ -96,11 +93,6 @@ bool DxFontInfo::GetFallback() const noexcept
     return _didFallback;
 }
 
-IDWriteFontCollection* DxFontInfo::GetNearbyCollection() const noexcept
-{
-    return _nearbyCollection.Get();
-}
-
 void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
                                const DWRITE_FONT_WEIGHT weight,
                                const DWRITE_FONT_STYLE style,
@@ -122,8 +114,7 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
 // - localeName - Locale to search for appropriate fonts
 // Return Value:
 // - Smart pointer holding interface reference for queryable font data.
-[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontInfo::ResolveFontFaceWithFallback(gsl::not_null<IDWriteFactory1*> dwriteFactory,
-                                                                                               std::wstring& localeName)
+[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontInfo::ResolveFontFaceWithFallback(IDWriteFontCollection* fontCollection, std::wstring& localeName)
 {
     // First attempt to find exactly what the user asked for.
     _didFallback = false;
@@ -134,7 +125,7 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
     // method. We still want to fall back to a font that's reasonable, below.
     try
     {
-        face = _FindFontFace(dwriteFactory, localeName, true);
+        face = _FindFontFace(fontCollection, localeName);
 
         if (!face)
         {
@@ -161,7 +152,7 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
                 _familyName = _familyName.substr(0, lastSpace);
 
                 // Try to find it with the shortened family name
-                face = _FindFontFace(dwriteFactory, localeName, true);
+                face = _FindFontFace(fontCollection, localeName);
             }
         }
     }
@@ -175,25 +166,8 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
         for (const auto fallbackFace : FALLBACK_FONT_FACES)
         {
             _familyName = fallbackFace;
-            // With these fonts, don't attempt the nearby lookup. We're looking
-            // for system fonts only. If one of the nearby fonts is causing us
-            // problems (like in GH#10211), then we don't want to go anywhere
-
-            // near it in this part.
-            face = _FindFontFace(dwriteFactory, localeName, false);
-
-            if (face)
-            {
-                _didFallback = true;
-                break;
-            }
-
-            _familyName = fallbackFace;
-            _weight = DWRITE_FONT_WEIGHT_NORMAL;
-            _stretch = DWRITE_FONT_STRETCH_NORMAL;
-            _style = DWRITE_FONT_STYLE_NORMAL;
-            face = _FindFontFace(dwriteFactory, localeName, false);
 
+            face = _FindFontFace(fontCollection, localeName);
             if (face)
             {
                 _didFallback = true;
@@ -214,27 +188,15 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
 // - localeName - Locale to search for appropriate fonts
 // Return Value:
 // - Smart pointer holding interface reference for queryable font data.
-[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontInfo::_FindFontFace(gsl::not_null<IDWriteFactory1*> dwriteFactory, std::wstring& localeName, const bool withNearbyLookup)
+#pragma warning(suppress : 26429) // C26429: Symbol 'fontCollection' is never tested for nullness, it can be marked as not_null (f.23).
+[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontInfo::_FindFontFace(IDWriteFontCollection* fontCollection, std::wstring& localeName)
 {
     Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
 
-    Microsoft::WRL::ComPtr<IDWriteFontCollection> fontCollection;
-    THROW_IF_FAILED(dwriteFactory->GetSystemFontCollection(&fontCollection, false));
-
     UINT32 familyIndex;
     BOOL familyExists;
-    THROW_IF_FAILED(fontCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists));
 
-    // If the system collection missed, try the files sitting next to our binary.
-    if (withNearbyLookup && !familyExists)
-    {
-        // May be null on OS below Windows 10. If null, just skip the attempt.
-        if (const auto nearbyCollection = _NearbyCollection(dwriteFactory))
-        {
-            THROW_IF_FAILED(nearbyCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists));
-            fontCollection = nearbyCollection;
-        }
-    }
+    THROW_IF_FAILED(fontCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists));
 
     if (familyExists)
     {
@@ -327,81 +289,3 @@ void DxFontInfo::SetFromEngine(const std::wstring_view familyName,
     // and return it.
     return retVal;
 }
-
-// Routine Description:
-// - Creates a DirectWrite font collection of font files that are sitting next to the running
-//   binary (in the same directory as the EXE).
-// Arguments:
-// - dwriteFactory - The DWrite factory to use
-// Return Value:
-// - DirectWrite font collection. May be null if one cannot be created.
-[[nodiscard]] IDWriteFontCollection* DxFontInfo::_NearbyCollection(gsl::not_null<IDWriteFactory1*> dwriteFactory)
-{
-    if (_nearbyCollection)
-    {
-        return _nearbyCollection.Get();
-    }
-
-    // The convenience interfaces for loading fonts from files
-    // are only available on Windows 10+.
-    ::Microsoft::WRL::ComPtr<IDWriteFactory6> factory6;
-    if (FAILED(dwriteFactory->QueryInterface<IDWriteFactory6>(&factory6)))
-    {
-        return nullptr;
-    }
-
-    ::Microsoft::WRL::ComPtr<IDWriteFontCollection1> systemFontCollection;
-    THROW_IF_FAILED(factory6->GetSystemFontCollection(false, &systemFontCollection, 0));
-
-    ::Microsoft::WRL::ComPtr<IDWriteFontSet> systemFontSet;
-    THROW_IF_FAILED(systemFontCollection->GetFontSet(&systemFontSet));
-
-    ::Microsoft::WRL::ComPtr<IDWriteFontSetBuilder2> fontSetBuilder2;
-    THROW_IF_FAILED(factory6->CreateFontSetBuilder(&fontSetBuilder2));
-
-    THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.Get()));
-
-    // Magic static so we only attempt to grovel the hard disk once no matter how many instances
-    // of the font collection itself we require.
-    static const auto knownPaths = s_GetNearbyFonts();
-    for (auto& p : knownPaths)
-    {
-        fontSetBuilder2->AddFontFile(p.c_str());
-    }
-
-    ::Microsoft::WRL::ComPtr<IDWriteFontSet> fontSet;
-    THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(&fontSet));
-
-    ::Microsoft::WRL::ComPtr<IDWriteFontCollection1> fontCollection;
-    THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.Get(), &fontCollection));
-
-    _nearbyCollection = fontCollection;
-    return _nearbyCollection.Get();
-}
-
-// Routine Description:
-// - Digs through the directory that the current executable is running within to find
-//   any TTF files sitting next to it.
-// Arguments:
-// - <none>
-// Return Value:
-// - Iterable collection of filesystem paths, one per font file that was found
-[[nodiscard]] std::vector<std::filesystem::path> DxFontInfo::s_GetNearbyFonts()
-{
-    std::vector<std::filesystem::path> paths;
-
-    // Find the directory we're running from then enumerate all the TTF files
-    // sitting next to us.
-    const std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
-    const auto folder{ module.parent_path() };
-
-    for (const auto& p : std::filesystem::directory_iterator(folder))
-    {
-        if (til::ends_with(p.path().native(), L".ttf"))
-        {
-            paths.push_back(p.path());
-        }
-    }
-
-    return paths;
-}
diff --git a/src/renderer/dx/DxFontInfo.h b/src/renderer/dx/DxFontInfo.h
index 9b336accd0c..0e8bbf837c2 100644
--- a/src/renderer/dx/DxFontInfo.h
+++ b/src/renderer/dx/DxFontInfo.h
@@ -41,30 +41,19 @@ namespace Microsoft::Console::Render
 
         bool GetFallback() const noexcept;
 
-        IDWriteFontCollection* GetNearbyCollection() const noexcept;
-
         void SetFromEngine(const std::wstring_view familyName,
                            const DWRITE_FONT_WEIGHT weight,
                            const DWRITE_FONT_STYLE style,
                            const DWRITE_FONT_STRETCH stretch);
 
-        [[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> ResolveFontFaceWithFallback(gsl::not_null<IDWriteFactory1*> dwriteFactory,
-                                                                                             std::wstring& localeName);
+        [[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> ResolveFontFaceWithFallback(IDWriteFontCollection* fontCollection, std::wstring& localeName);
 
     private:
-        [[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(gsl::not_null<IDWriteFactory1*> dwriteFactory,
-                                                                               std::wstring& localeName,
-                                                                               const bool withNearbyLookup);
+        [[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(IDWriteFontCollection* fontCollection, std::wstring& localeName);
 
         [[nodiscard]] std::wstring _GetFontFamilyName(gsl::not_null<IDWriteFontFamily*> const fontFamily,
                                                       std::wstring& localeName);
 
-        [[nodiscard]] IDWriteFontCollection* _NearbyCollection(gsl::not_null<IDWriteFactory1*> dwriteFactory);
-
-        [[nodiscard]] static std::vector<std::filesystem::path> s_GetNearbyFonts();
-
-        ::Microsoft::WRL::ComPtr<IDWriteFontCollection> _nearbyCollection;
-
         // The font name we should be looking for
         std::wstring _familyName;
 
diff --git a/src/renderer/dx/DxFontRenderData.cpp b/src/renderer/dx/DxFontRenderData.cpp
index c251b7bf0c6..869a1d4fc45 100644
--- a/src/renderer/dx/DxFontRenderData.cpp
+++ b/src/renderer/dx/DxFontRenderData.cpp
@@ -2,22 +2,21 @@
 // Licensed under the MIT license.
 
 #include "precomp.h"
-
 #include "DxFontRenderData.h"
 
-#include "unicode.hpp"
-
 #include <VersionHelpers.h>
 
+#include "../base/FontCache.h"
+
 static constexpr float POINTS_PER_INCH = 72.0f;
-static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" };
 static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us";
 static constexpr size_t TAG_LENGTH = 4;
 
 using namespace Microsoft::Console::Render;
 
-DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwriteFactory) noexcept :
+DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwriteFactory) :
     _dwriteFactory(dwriteFactory),
+    _nearbyCollection{ FontCache::GetCached() },
     _fontSize{},
     _glyphCell{},
     _lineMetrics{},
@@ -165,7 +164,7 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
         fontInfo.SetStretch(stretch);
 
         std::wstring fontLocaleName = UserLocaleName();
-        Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace = fontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName);
+        Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace = fontInfo.ResolveFontFaceWithFallback(_nearbyCollection.get(), fontLocaleName);
 
         _fontFaceMap.emplace(_ToMapKey(weight, style, stretch), fontFace);
         return fontFace;
@@ -711,7 +710,7 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font
     // This is the first attempt to resolve font face after `UpdateFont`.
     // Note that the following line may cause property changes _inside_ `_defaultFontInfo` because the desired font may not exist.
     // See the implementation of `ResolveFontFaceWithFallback` for details.
-    const Microsoft::WRL::ComPtr<IDWriteFontFace1> face = _defaultFontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName);
+    const Microsoft::WRL::ComPtr<IDWriteFontFace1> face = _defaultFontInfo.ResolveFontFaceWithFallback(_nearbyCollection.get(), fontLocaleName);
 
     DWRITE_FONT_METRICS1 fontMetrics;
     face->GetMetrics(&fontMetrics);
@@ -898,7 +897,7 @@ Microsoft::WRL::ComPtr<IDWriteTextFormat> DxFontRenderData::_BuildTextFormat(con
 {
     Microsoft::WRL::ComPtr<IDWriteTextFormat> format;
     THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontInfo.GetFamilyName().data(),
-                                                     fontInfo.GetNearbyCollection(),
+                                                     _nearbyCollection.get(),
                                                      fontInfo.GetWeight(),
                                                      fontInfo.GetStyle(),
                                                      fontInfo.GetStretch(),
diff --git a/src/renderer/dx/DxFontRenderData.h b/src/renderer/dx/DxFontRenderData.h
index ce69169c53a..b2a0e7053bd 100644
--- a/src/renderer/dx/DxFontRenderData.h
+++ b/src/renderer/dx/DxFontRenderData.h
@@ -39,7 +39,7 @@ namespace Microsoft::Console::Render
             float strikethroughWidth;
         };
 
-        DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwriteFactory) noexcept;
+        DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwriteFactory);
 
         // DirectWrite text analyzer from the factory
         [[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> Analyzer();
@@ -132,6 +132,7 @@ namespace Microsoft::Console::Render
         ::Microsoft::WRL::ComPtr<IDWriteFactory1> _dwriteFactory;
         ::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _dwriteTextAnalyzer;
 
+        wil::com_ptr<IDWriteFontCollection> _nearbyCollection;
         std::wstring _userLocaleName;
         DxFontInfo _defaultFontInfo;
         til::size _glyphCell;
diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp
index 68ba9ff684e..18b607fb70c 100644
--- a/src/renderer/dx/DxRenderer.cpp
+++ b/src/renderer/dx/DxRenderer.cpp
@@ -2083,10 +2083,12 @@ float DxEngine::GetScaling() const noexcept
 [[nodiscard]] HRESULT DxEngine::GetProposedFont(const FontInfoDesired& pfiFontInfoDesired,
                                                 FontInfo& pfiFontInfo,
                                                 int const iDpi) noexcept
+try
 {
     DxFontRenderData fontRenderData(_dwriteFactory);
     return fontRenderData.UpdateFont(pfiFontInfoDesired, pfiFontInfo, iDpi);
 }
+CATCH_RETURN();
 
 // Routine Description:
 // - Gets the area that we currently believe is dirty within the character cell grid


Test patch: