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 23, 2022
1 parent 9ab4abf commit c6b873f
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 136 deletions.
7 changes: 4 additions & 3 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

#include "pch.h"
#include "AppLogic.h"
#include "../inc/WindowingBehavior.h"
#include "AppLogic.g.cpp"
#include "FindTargetWindowResult.g.cpp"
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>

#include <LibraryResources.h>
#include <WtExeUtils.h>
#include <wil/token_helpers.h >
#include <wil/token_helpers.h>

#include "../inc/WindowingBehavior.h"
#include "../../renderer/atlas/FontCache.h"
#include "../../types/inc/utils.hpp"

using namespace winrt::Windows::ApplicationModel;
Expand Down Expand Up @@ -1065,6 +1065,7 @@ namespace winrt::TerminalApp::implementation
_ApplyStartupTaskStateChange();

Jumplist::UpdateJumplist(_settings);
::Microsoft::Console::Render::Atlas::FontCache::Invalidate();

_SettingsChangedHandlers(*this, nullptr);
}
Expand Down
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/atlas/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::Atlas::FontCache::Get(true);

for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i)
{
Expand Down
86 changes: 57 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 "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));
const auto fontCollection = Atlas::FontCache::Get();

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
5 changes: 4 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 "FontCache.h"
#include "../../interactivity/win32/CustomWindowMessages.h"

// #### NOTE ####
Expand Down Expand Up @@ -1015,6 +1016,8 @@ void AtlasEngine::_recreateFontDependentResources()
}
});

const auto fontCollection = Atlas::FontCache::Get();

for (auto italic = 0; italic < 2; ++italic)
{
for (auto bold = 0; bold < 2; ++bold)
Expand All @@ -1023,7 +1026,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(), 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
3 changes: 2 additions & 1 deletion src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,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
114 changes: 114 additions & 0 deletions src/renderer/atlas/FontCache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

#include <dwrite_3.h>

namespace Microsoft::Console::Render::Atlas::FontCache
{
namespace details
{
struct Cache
{
void Invalidate() noexcept
{
std::lock_guard guard{ _lock };
_fontCollection.reset();
}

wil::com_ptr<IDWriteFontCollection> Get(bool forceUpdate)
{
std::lock_guard guard{ _lock };

if (!_fontCollection || forceUpdate)
{
_init(forceUpdate);
FAIL_FAST_IF(!_fontCollection);
}

return _fontCollection;
}

private:
void _init(bool forceUpdate)
{
// DWRITE_FACTORY_TYPE_SHARED _should_ return the same instance every time.
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));

// IDWriteFactory5 is supported since Windows 10, build 15021.
const auto factory5 = factory.try_query<IDWriteFactory5>();
if (!factory5)
{
_fontCollection = systemFontCollection;
return;
}

// Cache nearby files.
if (!_nearbyFilesFound)
{
_nearbyFilesFound = true;

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())))
{
_nearbyFiles.emplace_back(std::move(fontFile));
}
}
}

_nearbyFiles.shrink_to_fit();
}

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 : _nearbyFiles)
{
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()));

_fontCollection = std::move(fontCollection);
}

std::shared_mutex _lock;
wil::com_ptr<IDWriteFontCollection> _fontCollection;
std::vector<wil::com_ptr<IDWriteFontFile>> _nearbyFiles;
bool _nearbyFilesFound = false;
};

inline static Cache cache;
}

inline void Invalidate() noexcept
{
details::cache.Invalidate();
}

inline wil::com_ptr<IDWriteFontCollection> Get(bool forceUpdate = false)
{
return details::cache.Get(forceUpdate);
}
}
Loading

0 comments on commit c6b873f

Please sign in to comment.