Skip to content

Commit

Permalink
Fix Windows 10 support for nearby font loading
Browse files Browse the repository at this point in the history
  • Loading branch information
lhecker committed Feb 28, 2022
1 parent d0d42c4 commit 418ef40
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 228 deletions.
60 changes: 3 additions & 57 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
9 changes: 9 additions & 0 deletions src/features.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
87 changes: 58 additions & 29 deletions src/renderer/atlas/AtlasEngine.api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -227,7 +229,7 @@ try
}
#endif

_resolveFontMetrics(fontInfoDesired, fontInfo);
_resolveFontMetrics(nullptr, fontInfoDesired, fontInfo);
return S_OK;
}
CATCH_RETURN()
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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);

Expand All @@ -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)
{
Expand All @@ -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()));
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <shader_ps.h>
#include <shader_vs.h>

#include "../base/FontCache.h"
#include "../../interactivity/win32/CustomWindowMessages.h"

// #### NOTE ####
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 3 additions & 1 deletion src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
96 changes: 96 additions & 0 deletions src/renderer/base/FontCache.h
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions src/renderer/base/lib/base.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/base/lib/base.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Loading

0 comments on commit 418ef40

Please sign in to comment.