diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index ab2ea7db67..0dd8125bfe 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -7,55 +7,8 @@ #include "EnumEntry.h" #include -#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 NearbyCollection(IDWriteFactory* dwriteFactory) -{ - // The convenience interfaces for loading fonts from files - // are only available on Windows 10+. - wil::com_ptr 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 systemFontCollection; - THROW_IF_FAILED(factory6->GetSystemFontCollection(false, systemFontCollection.addressof(), true)); - - wil::com_ptr systemFontSet; - THROW_IF_FAILED(systemFontCollection->GetFontSet(systemFontSet.addressof())); - - wil::com_ptr fontSetBuilder2; - THROW_IF_FAILED(factory6->CreateFontSetBuilder(fontSetBuilder2.addressof())); - - THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.get())); - - { - const std::filesystem::path module{ wil::GetModuleFileNameW(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 fontSet; - THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(fontSet.addressof())); - - wil::com_ptr 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 fontList; std::vector monospaceFontList; - // get a DWriteFactory - com_ptr 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 464cd54727..9f7e3a1eef 100644 --- a/src/features.xml +++ b/src/features.xml @@ -85,4 +85,13 @@ + + + Feature_NearbyFontLoading + 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. + AlwaysEnabled + + WindowsInbox + + diff --git a/src/renderer/base/FontCache.h b/src/renderer/base/FontCache.h new file mode 100644 index 0000000000..e7a5c4134c --- /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>& getNearbyFontFiles(IDWriteFactory5* factory5) + { + static const auto fontFiles = [=]() { + std::vector> files; + + const std::filesystem::path module{ wil::GetModuleFileNameW(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 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 getFontCollection(bool forceUpdate) + { + wil::com_ptr factory; + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof()))); + + wil::com_ptr 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(); + if (!factory5) + { + return systemFontCollection; + } + + const auto& nearbyFontFiles = getNearbyFontFiles(factory5.get()); + if (nearbyFontFiles.empty()) + { + return systemFontCollection; + } + + wil::com_ptr systemFontSet; + // IDWriteFontCollection1 is supported since Windows 7. + THROW_IF_FAILED(systemFontCollection.query()->GetFontSet(systemFontSet.addressof())); + + wil::com_ptr 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 fontSet; + THROW_IF_FAILED(fontSetBuilder->CreateFontSet(fontSet.addressof())); + + wil::com_ptr fontCollection; + THROW_IF_FAILED(factory5->CreateFontCollectionFromFontSet(fontSet.get(), fontCollection.addressof())); + + return std::move(fontCollection); + } + else + { + return systemFontCollection; + } + } + } + + inline wil::com_ptr GetCached() + { + return details::getFontCollection(false); + } + + inline wil::com_ptr GetFresh() + { + return details::getFontCollection(true); + } +} diff --git a/src/renderer/base/lib/base.vcxproj b/src/renderer/base/lib/base.vcxproj index 7cf585346d..b397b5070c 100644 --- a/src/renderer/base/lib/base.vcxproj +++ b/src/renderer/base/lib/base.vcxproj @@ -35,6 +35,7 @@ + diff --git a/src/renderer/base/lib/base.vcxproj.filters b/src/renderer/base/lib/base.vcxproj.filters index f20d768118..83941943b6 100644 --- a/src/renderer/base/lib/base.vcxproj.filters +++ b/src/renderer/base/lib/base.vcxproj.filters @@ -92,8 +92,11 @@ Header Files\inc + + Header Files + - \ No newline at end of file + diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp index e5c6749f4b..ba3eca1a86 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 #include 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 DxFontInfo::ResolveFontFaceWithFallback(gsl::not_null dwriteFactory, - std::wstring& localeName) +[[nodiscard]] Microsoft::WRL::ComPtr 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 DxFontInfo::_FindFontFace(gsl::not_null 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 DxFontInfo::_FindFontFace(IDWriteFontCollection* fontCollection, std::wstring& localeName) { Microsoft::WRL::ComPtr fontFace; - Microsoft::WRL::ComPtr 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 dwriteFactory) -{ - if (_nearbyCollection) - { - return _nearbyCollection.Get(); - } - - // The convenience interfaces for loading fonts from files - // are only available on Windows 10+. - ::Microsoft::WRL::ComPtr factory6; - if (FAILED(dwriteFactory->QueryInterface(&factory6))) - { - return nullptr; - } - - ::Microsoft::WRL::ComPtr systemFontCollection; - THROW_IF_FAILED(factory6->GetSystemFontCollection(false, &systemFontCollection, 0)); - - ::Microsoft::WRL::ComPtr systemFontSet; - THROW_IF_FAILED(systemFontCollection->GetFontSet(&systemFontSet)); - - ::Microsoft::WRL::ComPtr 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 fontSet; - THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(&fontSet)); - - ::Microsoft::WRL::ComPtr 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: -// - -// Return Value: -// - Iterable collection of filesystem paths, one per font file that was found -[[nodiscard]] std::vector DxFontInfo::s_GetNearbyFonts() -{ - std::vector 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(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 9b336accd0..0e8bbf837c 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 ResolveFontFaceWithFallback(gsl::not_null dwriteFactory, - std::wstring& localeName); + [[nodiscard]] ::Microsoft::WRL::ComPtr ResolveFontFaceWithFallback(IDWriteFontCollection* fontCollection, std::wstring& localeName); private: - [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(gsl::not_null dwriteFactory, - std::wstring& localeName, - const bool withNearbyLookup); + [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(IDWriteFontCollection* fontCollection, std::wstring& localeName); [[nodiscard]] std::wstring _GetFontFamilyName(gsl::not_null const fontFamily, std::wstring& localeName); - [[nodiscard]] IDWriteFontCollection* _NearbyCollection(gsl::not_null dwriteFactory); - - [[nodiscard]] static std::vector s_GetNearbyFonts(); - - ::Microsoft::WRL::ComPtr _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 96c44ccb69..e2716a7404 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 +#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 dwriteFactory) noexcept : +DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory) : _dwriteFactory(dwriteFactory), + _nearbyCollection{ FontCache::GetCached() }, _fontSize{}, _glyphCell{}, _lineMetrics{}, @@ -165,7 +164,7 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwr fontInfo.SetStretch(stretch); std::wstring fontLocaleName = UserLocaleName(); - Microsoft::WRL::ComPtr fontFace = fontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName); + Microsoft::WRL::ComPtr 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 face = _defaultFontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName); + const Microsoft::WRL::ComPtr face = _defaultFontInfo.ResolveFontFaceWithFallback(_nearbyCollection.get(), fontLocaleName); DWRITE_FONT_METRICS1 fontMetrics; face->GetMetrics(&fontMetrics); @@ -898,7 +897,7 @@ Microsoft::WRL::ComPtr DxFontRenderData::_BuildTextFormat(con { Microsoft::WRL::ComPtr 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 ce69169c53..b2a0e7053b 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 dwriteFactory) noexcept; + DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory); // DirectWrite text analyzer from the factory [[nodiscard]] Microsoft::WRL::ComPtr Analyzer(); @@ -132,6 +132,7 @@ namespace Microsoft::Console::Render ::Microsoft::WRL::ComPtr _dwriteFactory; ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; + wil::com_ptr _nearbyCollection; std::wstring _userLocaleName; DxFontInfo _defaultFontInfo; til::size _glyphCell; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index bbbdb8b205..96a94433cb 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -2095,10 +2095,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