mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Add support for customizing font fallback (#16821)
This adds support for specifying more than one font family using a syntax that is similar to CSS' `font-family` property. The implementation is straight-forward and is effectively just a wrapper around `IDWriteFontFallbackBuilder`. Closes #2664 ## PR Checklist * Font fallback * Write "「猫」" * Use "Consolas" and remember the shape of the glyphs * Use "Consolas, MS Gothic" and check that it changed ✅ * Settings UI autocompletion * It completes ✅ * It filters ✅ * It recognizes commas and starts a new name ✅ * All invalid font names are listed in the warning message ✅ --------- Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
parent
a67a13288c
commit
de7f931228
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@ -1155,6 +1155,7 @@ NOCONTEXTHELP
|
||||
NOCOPYBITS
|
||||
NODUP
|
||||
noexcepts
|
||||
NOFONT
|
||||
NOINTEGRALHEIGHT
|
||||
NOINTERFACE
|
||||
NOLINKINFO
|
||||
|
||||
@ -336,6 +336,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>();
|
||||
_renderer->AddRenderEngine(_renderEngine.get());
|
||||
|
||||
// Hook up the warnings callback as early as possible so that we catch everything.
|
||||
_renderEngine->SetWarningCallback([this](HRESULT hr, wil::zwstring_view parameter) {
|
||||
_rendererWarning(hr, parameter);
|
||||
});
|
||||
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
// and react accordingly.
|
||||
@ -371,12 +376,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
_terminal->CreateFromSettings(*_settings, *_renderer);
|
||||
|
||||
// IMPORTANT! Set this callback up sooner than later. If we do it
|
||||
// after Enable, then it'll be possible to paint the frame once
|
||||
// _before_ the warning handler is set up, and then warnings from
|
||||
// the first paint will be ignored!
|
||||
_renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
|
||||
|
||||
// Tell the render engine to notify us when the swap chain changes.
|
||||
// We do this after we initially set the swapchain so as to avoid
|
||||
// unnecessary callbacks (and locking problems)
|
||||
@ -1024,18 +1023,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
LOG_IF_FAILED(_renderEngine->UpdateFont(_desiredFont, _actualFont, featureMap, axesMap));
|
||||
}
|
||||
|
||||
// If the actual font isn't what was requested...
|
||||
if (_actualFont.GetFaceName() != _desiredFont.GetFaceName())
|
||||
{
|
||||
// Then warn the user that we picked something because we couldn't find their font.
|
||||
// Format message with user's choice of font and the font that was chosen instead.
|
||||
const winrt::hstring message{ fmt::format(std::wstring_view{ RS_(L"NoticeFontNotFound") },
|
||||
_desiredFont.GetFaceName(),
|
||||
_actualFont.GetFaceName()) };
|
||||
auto noticeArgs = winrt::make<NoticeEventArgs>(NoticeLevel::Warning, message);
|
||||
RaiseNotice.raise(*this, std::move(noticeArgs));
|
||||
}
|
||||
|
||||
const auto actualNewSize = _actualFont.GetSize();
|
||||
FontSizeChanged.raise(*this, winrt::make<FontSizeChangedArgs>(actualNewSize.width, actualNewSize.height));
|
||||
}
|
||||
@ -1724,9 +1711,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCore::_rendererWarning(const HRESULT hr)
|
||||
void ControlCore::_rendererWarning(const HRESULT hr, wil::zwstring_view parameter)
|
||||
{
|
||||
RendererWarning.raise(*this, winrt::make<RendererWarningArgs>(hr));
|
||||
RendererWarning.raise(*this, winrt::make<RendererWarningArgs>(hr, winrt::hstring{ parameter }));
|
||||
}
|
||||
|
||||
winrt::fire_and_forget ControlCore::_renderEngineSwapChainChanged(const HANDLE sourceHandle)
|
||||
|
||||
@ -390,7 +390,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
winrt::Windows::System::DispatcherQueueTimer _midiAudioSkipTimer{ nullptr };
|
||||
|
||||
#pragma region RendererCallbacks
|
||||
void _rendererWarning(const HRESULT hr);
|
||||
void _rendererWarning(const HRESULT hr, wil::zwstring_view parameter);
|
||||
winrt::fire_and_forget _renderEngineSwapChainChanged(const HANDLE handle);
|
||||
void _rendererBackgroundColorChanged();
|
||||
void _rendererTabColorChanged();
|
||||
|
||||
@ -148,12 +148,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
struct RendererWarningArgs : public RendererWarningArgsT<RendererWarningArgs>
|
||||
{
|
||||
public:
|
||||
RendererWarningArgs(const uint64_t hr) :
|
||||
_Result(hr)
|
||||
RendererWarningArgs(const HRESULT hr, winrt::hstring parameter) :
|
||||
_Result{ hr },
|
||||
_Parameter{ std::move(parameter) }
|
||||
{
|
||||
}
|
||||
|
||||
WINRT_PROPERTY(uint64_t, Result);
|
||||
WINRT_PROPERTY(HRESULT, Result);
|
||||
WINRT_PROPERTY(winrt::hstring, Parameter);
|
||||
};
|
||||
|
||||
struct TransparencyChangedEventArgs : public TransparencyChangedEventArgsT<TransparencyChangedEventArgs>
|
||||
|
||||
@ -77,7 +77,8 @@ namespace Microsoft.Terminal.Control
|
||||
|
||||
runtimeclass RendererWarningArgs
|
||||
{
|
||||
UInt64 Result { get; };
|
||||
HRESULT Result { get; };
|
||||
String Parameter { get; };
|
||||
}
|
||||
|
||||
runtimeclass TransparencyChangedEventArgs
|
||||
|
||||
@ -213,6 +213,14 @@ Please either install the missing font or choose another one.</value>
|
||||
<value>Renderer encountered an unexpected error: {0}</value>
|
||||
<comment>{0} is an error code.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorFontNotFound" xml:space="preserve">
|
||||
<value>Unable to find the following fonts: {0}. Please either install them or choose different fonts.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Renderer encountered an unexpected error: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
</data>
|
||||
<data name="TermControlReadOnly" xml:space="preserve">
|
||||
<value>Read-only mode is enabled.</value>
|
||||
</data>
|
||||
|
||||
@ -991,36 +991,45 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - hr: an HRESULT describing the warning
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TermControl::_RendererWarning(IInspectable /*sender*/,
|
||||
Control::RendererWarningArgs args)
|
||||
winrt::fire_and_forget TermControl::_RendererWarning(IInspectable /*sender*/, Control::RendererWarningArgs args)
|
||||
{
|
||||
const auto hr = static_cast<HRESULT>(args.Result());
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
const auto control = weakThis.get();
|
||||
if (!control)
|
||||
{
|
||||
winrt::hstring message;
|
||||
if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr ||
|
||||
HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr)
|
||||
{
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") },
|
||||
(_focused ? _core.FocusedAppearance() : _core.UnfocusedAppearance()).PixelShaderPath()) };
|
||||
}
|
||||
else if (D2DERR_SHADER_COMPILE_FAILED == hr)
|
||||
{
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) };
|
||||
}
|
||||
else
|
||||
{
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"UnexpectedRendererError") },
|
||||
hr) };
|
||||
}
|
||||
|
||||
auto noticeArgs = winrt::make<NoticeEventArgs>(NoticeLevel::Warning, std::move(message));
|
||||
control->RaiseNotice.raise(*control, std::move(noticeArgs));
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto hr = args.Result();
|
||||
const auto parameter = args.Parameter();
|
||||
winrt::hstring message;
|
||||
|
||||
switch (hr)
|
||||
{
|
||||
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
|
||||
case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") }, parameter) };
|
||||
break;
|
||||
case D2DERR_SHADER_COMPILE_FAILED:
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) };
|
||||
break;
|
||||
case DWRITE_E_NOFONT:
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorFontNotFound") }, parameter) };
|
||||
break;
|
||||
default:
|
||||
{
|
||||
wchar_t buf[512];
|
||||
const auto len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &buf[0], ARRAYSIZE(buf), nullptr);
|
||||
const std::wstring_view msg{ &buf[0], len };
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorOther") }, hr, msg) };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto noticeArgs = winrt::make<NoticeEventArgs>(NoticeLevel::Warning, std::move(message));
|
||||
control->RaiseNotice.raise(*control, std::move(noticeArgs));
|
||||
}
|
||||
|
||||
void TermControl::_AttachDxgiSwapChainToXaml(HANDLE swapChainHandle)
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
#include "Appearances.g.cpp"
|
||||
#include "AxisKeyValuePair.g.cpp"
|
||||
#include "FeatureKeyValuePair.g.cpp"
|
||||
#include "EnumEntry.h"
|
||||
|
||||
#include "EnumEntry.h"
|
||||
#include <LibraryResources.h>
|
||||
#include "..\WinRTUtils\inc\Utils.h"
|
||||
|
||||
@ -36,28 +36,6 @@ static constexpr std::array<std::wstring_view, 11> DefaultFeatures{
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
bool Font::HasPowerlineCharacters()
|
||||
{
|
||||
if (!_hasPowerlineCharacters.has_value())
|
||||
{
|
||||
try
|
||||
{
|
||||
winrt::com_ptr<IDWriteFont> font;
|
||||
THROW_IF_FAILED(_family->GetFont(0, font.put()));
|
||||
BOOL exists{};
|
||||
// We're actually checking for the "Extended" PowerLine glyph set.
|
||||
// They're more fun.
|
||||
THROW_IF_FAILED(font->HasCharacter(0xE0B6, &exists));
|
||||
_hasPowerlineCharacters = (exists == TRUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
_hasPowerlineCharacters = false;
|
||||
}
|
||||
}
|
||||
return _hasPowerlineCharacters.value_or(false);
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> Font::FontAxesTagsAndNames()
|
||||
{
|
||||
if (!_fontAxesTagsAndNames)
|
||||
@ -370,6 +348,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_refreshFontFaceDependents();
|
||||
InitializeFontAxesVector();
|
||||
InitializeFontFeaturesVector();
|
||||
|
||||
@ -382,7 +361,119 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
double AppearanceViewModel::LineHeight() const noexcept
|
||||
winrt::hstring AppearanceViewModel::FontFace() const
|
||||
{
|
||||
return _appearance.SourceProfile().FontInfo().FontFace();
|
||||
}
|
||||
|
||||
void AppearanceViewModel::FontFace(const winrt::hstring& value)
|
||||
{
|
||||
const auto fontInfo = _appearance.SourceProfile().FontInfo();
|
||||
if (fontInfo.FontFace() == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fontInfo.FontFace(value);
|
||||
_refreshFontFaceDependents();
|
||||
|
||||
_NotifyChanges(L"HasFontFace", L"FontFace");
|
||||
}
|
||||
|
||||
bool AppearanceViewModel::HasFontFace() const
|
||||
{
|
||||
return _appearance.SourceProfile().FontInfo().HasFontFace();
|
||||
}
|
||||
|
||||
void AppearanceViewModel::ClearFontFace()
|
||||
{
|
||||
const auto fontInfo = _appearance.SourceProfile().FontInfo();
|
||||
const auto hadValue = fontInfo.HasFontFace();
|
||||
|
||||
fontInfo.ClearFontFace();
|
||||
_refreshFontFaceDependents();
|
||||
|
||||
if (hadValue)
|
||||
{
|
||||
_NotifyChanges(L"HasFontFace", L"FontFace");
|
||||
}
|
||||
}
|
||||
|
||||
Model::FontConfig AppearanceViewModel::FontFaceOverrideSource() const
|
||||
{
|
||||
return _appearance.SourceProfile().FontInfo().FontFaceOverrideSource();
|
||||
}
|
||||
|
||||
void AppearanceViewModel::_refreshFontFaceDependents()
|
||||
{
|
||||
wil::com_ptr<IDWriteFactory> factory;
|
||||
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof())));
|
||||
|
||||
wil::com_ptr<IDWriteFontCollection> fontCollection;
|
||||
THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), FALSE));
|
||||
|
||||
const auto fontFace = FontFace();
|
||||
std::wstring primaryFontName;
|
||||
std::wstring missingFonts;
|
||||
std::wstring proportionalFonts;
|
||||
BOOL hasPowerlineCharacters = FALSE;
|
||||
|
||||
til::iterate_font_families(fontFace, [&](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
|
||||
{
|
||||
if (!exists)
|
||||
{
|
||||
accumulator = &missingFonts;
|
||||
break;
|
||||
}
|
||||
|
||||
if (primaryFontName.empty())
|
||||
{
|
||||
primaryFontName = name;
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
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;
|
||||
} while (false);
|
||||
|
||||
if (accumulator)
|
||||
{
|
||||
if (!accumulator->empty())
|
||||
{
|
||||
accumulator->append(L", ");
|
||||
}
|
||||
accumulator->append(name);
|
||||
}
|
||||
});
|
||||
|
||||
_primaryFontName = std::move(primaryFontName);
|
||||
MissingFontFaces(winrt::hstring{ missingFonts });
|
||||
ProportionalFontFaces(winrt::hstring{ proportionalFonts });
|
||||
HasPowerlineCharacters(hasPowerlineCharacters);
|
||||
}
|
||||
|
||||
double AppearanceViewModel::LineHeight() const
|
||||
{
|
||||
const auto fontInfo = _appearance.SourceProfile().FontInfo();
|
||||
const auto cellHeight = fontInfo.CellHeight();
|
||||
@ -526,7 +617,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
// 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(FontFace()).FontAxesTagsAndNames();
|
||||
const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames();
|
||||
for (const auto tagAndName : possibleAxesTagsAndNames)
|
||||
{
|
||||
if (!fontAxesMap.HasKey(tagAndName.Key()))
|
||||
@ -567,7 +658,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_FontAxesVector.Clear();
|
||||
if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
|
||||
{
|
||||
const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames();
|
||||
const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames();
|
||||
for (const auto axis : fontAxesMap)
|
||||
{
|
||||
// only show the axes that the font supports
|
||||
@ -585,14 +676,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// - Determines whether the currently selected font has any variable font axes
|
||||
bool AppearanceViewModel::AreFontAxesAvailable()
|
||||
{
|
||||
return ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames().Size() > 0;
|
||||
return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames().Size() > 0;
|
||||
}
|
||||
|
||||
// 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(FontFace()).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0)
|
||||
if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0)
|
||||
{
|
||||
if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes())
|
||||
{
|
||||
@ -641,7 +732,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
// 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(FontFace()).FontFeaturesTagsAndNames();
|
||||
const auto possibleFeaturesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames();
|
||||
for (const auto tagAndName : possibleFeaturesTagsAndNames)
|
||||
{
|
||||
const auto featureKey = tagAndName.Key();
|
||||
@ -684,7 +775,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_FontFeaturesVector.Clear();
|
||||
if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures())
|
||||
{
|
||||
const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames();
|
||||
const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames();
|
||||
for (const auto feature : fontFeaturesMap)
|
||||
{
|
||||
const auto featureKey = feature.Key();
|
||||
@ -703,14 +794,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
// - Determines whether the currently selected font has any font features
|
||||
bool AppearanceViewModel::AreFontFeaturesAvailable()
|
||||
{
|
||||
return ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames().Size() > 0;
|
||||
return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames().Size() > 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the currently selected font has any font features that have not already been set
|
||||
bool AppearanceViewModel::CanFontFeaturesBeAdded()
|
||||
{
|
||||
if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0)
|
||||
if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0)
|
||||
{
|
||||
if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures())
|
||||
{
|
||||
@ -775,9 +866,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
DependencyProperty Appearances::_AppearanceProperty{ nullptr };
|
||||
|
||||
Appearances::Appearances() :
|
||||
_ShowAllFonts{ false },
|
||||
_ShowProportionalFontWarning{ false }
|
||||
Appearances::Appearances()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
@ -848,61 +937,58 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Searches through our list of monospace fonts to determine if the settings model's current font face is a monospace font
|
||||
bool Appearances::UsingMonospaceFont() const noexcept
|
||||
IObservableVector<Editor::Font> Appearances::FilteredFontList()
|
||||
{
|
||||
auto result{ false };
|
||||
const auto currentFont{ Appearance().FontFace() };
|
||||
for (const auto& font : ProfileViewModel::MonospaceFontList())
|
||||
if (!_filteredFonts)
|
||||
{
|
||||
if (font.LocalizedName() == currentFont)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
_updateFilteredFontList();
|
||||
}
|
||||
return result;
|
||||
return _filteredFonts;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether we should show the list of all the fonts, or we should just show monospace fonts
|
||||
bool Appearances::ShowAllFonts() const noexcept
|
||||
{
|
||||
// - _ShowAllFonts is directly bound to the checkbox. So this is the user set value.
|
||||
// - If we are not using a monospace font, show all of the fonts so that the ComboBox is still properly bound
|
||||
return _ShowAllFonts || !UsingMonospaceFont();
|
||||
return _ShowAllFonts;
|
||||
}
|
||||
|
||||
void Appearances::ShowAllFonts(const bool& value)
|
||||
void Appearances::ShowAllFonts(const bool value)
|
||||
{
|
||||
if (_ShowAllFonts != value)
|
||||
{
|
||||
_ShowAllFonts = value;
|
||||
_filteredFonts = nullptr;
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredFontList" });
|
||||
}
|
||||
}
|
||||
|
||||
IInspectable Appearances::CurrentFontFace() const
|
||||
void Appearances::FontFaceBox_GotFocus(const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&)
|
||||
{
|
||||
const auto& appearanceVM{ Appearance() };
|
||||
const auto appearanceFontFace{ appearanceVM.FontFace() };
|
||||
return box_value(ProfileViewModel::FindFontWithLocalizedName(appearanceFontFace));
|
||||
_updateFontNameFilter({});
|
||||
sender.as<AutoSuggestBox>().IsSuggestionListOpen(true);
|
||||
}
|
||||
|
||||
void Appearances::FontFace_SelectionChanged(const IInspectable& /*sender*/, const SelectionChangedEventArgs& e)
|
||||
void Appearances::FontFaceBox_LostFocus(const IInspectable& sender, const RoutedEventArgs&)
|
||||
{
|
||||
// NOTE: We need to hook up a selection changed event handler here instead of directly binding to the appearance view model.
|
||||
// A two way binding to the view model causes an infinite loop because both combo boxes keep fighting over which one's right.
|
||||
const auto selectedItem{ e.AddedItems().GetAt(0) };
|
||||
const auto newFontFace{ unbox_value<Editor::Font>(selectedItem) };
|
||||
Appearance().FontFace(newFontFace.LocalizedName());
|
||||
if (!UsingMonospaceFont())
|
||||
const auto appearance = Appearance();
|
||||
const auto fontSpec = sender.as<AutoSuggestBox>().Text();
|
||||
|
||||
if (fontSpec.empty())
|
||||
{
|
||||
ShowProportionalFontWarning(true);
|
||||
appearance.ClearFontFace();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowProportionalFontWarning(false);
|
||||
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();
|
||||
@ -948,6 +1034,89 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void Appearances::FontFaceBox_SuggestionChosen(const AutoSuggestBox& sender, const AutoSuggestBoxSuggestionChosenEventArgs& args)
|
||||
{
|
||||
const auto font = unbox_value<Editor::Font>(args.SelectedItem());
|
||||
const auto fontName = font.Name();
|
||||
auto fontSpec = sender.Text();
|
||||
|
||||
const std::wstring_view fontSpecView{ fontSpec };
|
||||
if (const auto idx = fontSpecView.rfind(L','); idx != std::wstring_view::npos)
|
||||
{
|
||||
const auto prefix = fontSpecView.substr(0, idx);
|
||||
const auto suffix = std::wstring_view{ fontName };
|
||||
fontSpec = winrt::hstring{ fmt::format(FMT_COMPILE(L"{}, {}"), prefix, suffix) };
|
||||
}
|
||||
else
|
||||
{
|
||||
fontSpec = fontName;
|
||||
}
|
||||
|
||||
sender.Text(fontSpec);
|
||||
}
|
||||
|
||||
void Appearances::FontFaceBox_TextChanged(const AutoSuggestBox& sender, const AutoSuggestBoxTextChangedEventArgs& args)
|
||||
{
|
||||
if (args.Reason() != AutoSuggestionBoxTextChangeReason::UserInput)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto fontSpec = sender.Text();
|
||||
std::wstring_view filter{ fontSpec };
|
||||
|
||||
// Find the last font name in the font, spec, list.
|
||||
if (const auto idx = filter.rfind(L','); idx != std::wstring_view::npos)
|
||||
{
|
||||
filter = filter.substr(idx + 1);
|
||||
}
|
||||
|
||||
filter = til::trim(filter, L' ');
|
||||
_updateFontNameFilter(filter);
|
||||
}
|
||||
|
||||
void Appearances::_updateFontNameFilter(std::wstring_view filter)
|
||||
{
|
||||
if (_fontNameFilter != filter)
|
||||
{
|
||||
_filteredFonts = nullptr;
|
||||
_fontNameFilter = filter;
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FilteredFontList" });
|
||||
}
|
||||
}
|
||||
|
||||
void Appearances::_updateFilteredFontList()
|
||||
{
|
||||
_filteredFonts = _ShowAllFonts ? ProfileViewModel::CompleteFontList() : ProfileViewModel::MonospaceFontList();
|
||||
|
||||
if (_fontNameFilter.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Editor::Font> filtered;
|
||||
filtered.reserve(_filteredFonts.Size());
|
||||
|
||||
for (const auto& font : _filteredFonts)
|
||||
{
|
||||
const auto name = font.Name();
|
||||
bool match = til::contains_linguistic_insensitive(name, _fontNameFilter);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
const auto localizedName = font.LocalizedName();
|
||||
match = localizedName != name && til::contains_linguistic_insensitive(localizedName, _fontNameFilter);
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
filtered.emplace_back(font);
|
||||
}
|
||||
}
|
||||
|
||||
_filteredFonts = winrt::single_threaded_observable_vector(std::move(filtered));
|
||||
}
|
||||
|
||||
void Appearances::_ViewModelChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/)
|
||||
{
|
||||
const auto& obj{ d.as<Editor::Appearances>() };
|
||||
@ -956,21 +1125,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void Appearances::_UpdateWithNewViewModel()
|
||||
{
|
||||
if (Appearance())
|
||||
if (const auto appearance = Appearance())
|
||||
{
|
||||
const auto& biAlignmentVal{ static_cast<int32_t>(Appearance().BackgroundImageAlignment()) };
|
||||
const auto& biAlignmentVal{ static_cast<int32_t>(appearance.BackgroundImageAlignment()) };
|
||||
for (const auto& biButton : _BIAlignmentButtons)
|
||||
{
|
||||
biButton.IsChecked(biButton.Tag().as<int32_t>() == biAlignmentVal);
|
||||
}
|
||||
|
||||
FontAxesCVS().Source(Appearance().FontAxesVector());
|
||||
Appearance().AreFontAxesAvailable() ? FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")) : FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text"));
|
||||
FontAxesContainer().HelpText(appearance.AreFontAxesAvailable() ? RS_(L"Profile_FontAxesAvailable/Text") : RS_(L"Profile_FontAxesUnavailable/Text"));
|
||||
|
||||
FontFeaturesCVS().Source(Appearance().FontFeaturesVector());
|
||||
Appearance().AreFontFeaturesAvailable() ? FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesAvailable/Text")) : FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesUnavailable/Text"));
|
||||
FontFeaturesContainer().HelpText(appearance.AreFontFeaturesAvailable() ? RS_(L"Profile_FontFeaturesAvailable/Text") : RS_(L"Profile_FontFeaturesUnavailable/Text"));
|
||||
|
||||
_ViewModelChangedRevoker = Appearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
|
||||
_ViewModelChangedRevoker = appearance.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto settingName{ args.PropertyName() };
|
||||
if (settingName == L"CursorShape")
|
||||
{
|
||||
@ -987,24 +1156,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
else if (settingName == L"BackgroundImageAlignment")
|
||||
{
|
||||
_UpdateBIAlignmentControl(static_cast<int32_t>(Appearance().BackgroundImageAlignment()));
|
||||
_UpdateBIAlignmentControl(static_cast<int32_t>(appearance.BackgroundImageAlignment()));
|
||||
}
|
||||
else if (settingName == L"FontWeight")
|
||||
{
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" });
|
||||
}
|
||||
else if (settingName == L"FontFace" || settingName == L"CurrentFontList")
|
||||
{
|
||||
// notify listener that all font face related values might have changed
|
||||
if (!UsingMonospaceFont())
|
||||
{
|
||||
_ShowAllFonts = true;
|
||||
}
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontFace" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" });
|
||||
}
|
||||
else if (settingName == L"IntenseTextStyle")
|
||||
{
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" });
|
||||
@ -1040,12 +1198,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsVintageCursor" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentColorScheme" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentBackgroundImageStretchMode" });
|
||||
_UpdateBIAlignmentControl(static_cast<int32_t>(Appearance().BackgroundImageAlignment()));
|
||||
_UpdateBIAlignmentControl(static_cast<int32_t>(appearance.BackgroundImageAlignment()));
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontFace" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowAllFonts" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"UsingMonospaceFont" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentAdjustIndistinguishableColors" });
|
||||
PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"ShowProportionalFontWarning" });
|
||||
|
||||
@ -31,15 +31,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
struct Font : FontT<Font>
|
||||
{
|
||||
public:
|
||||
Font(winrt::hstring name, winrt::hstring localizedName, IDWriteFontFamily* family) :
|
||||
Font(winrt::hstring name, winrt::hstring localizedName, wil::com_ptr<IDWriteFontFamily> family) :
|
||||
_Name{ std::move(name) },
|
||||
_LocalizedName{ std::move(localizedName) }
|
||||
_LocalizedName{ std::move(localizedName) },
|
||||
_family{ std::move(family) }
|
||||
{
|
||||
_family.copy_from(family);
|
||||
}
|
||||
|
||||
hstring ToString() { return _LocalizedName; }
|
||||
bool HasPowerlineCharacters();
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> FontAxesTagsAndNames();
|
||||
Windows::Foundation::Collections::IMap<winrt::hstring, winrt::hstring> FontFeaturesTagsAndNames();
|
||||
|
||||
@ -47,14 +46,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
WINRT_PROPERTY(hstring, LocalizedName);
|
||||
|
||||
private:
|
||||
winrt::com_ptr<IDWriteFontFamily> _family;
|
||||
std::optional<bool> _hasPowerlineCharacters;
|
||||
|
||||
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>
|
||||
@ -108,11 +105,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
public:
|
||||
AppearanceViewModel(const Model::AppearanceConfig& appearance);
|
||||
|
||||
double LineHeight() const noexcept;
|
||||
winrt::hstring FontFace() const;
|
||||
void FontFace(const winrt::hstring& value);
|
||||
bool HasFontFace() const;
|
||||
void ClearFontFace();
|
||||
Model::FontConfig FontFaceOverrideSource() const;
|
||||
|
||||
double LineHeight() const;
|
||||
void LineHeight(const double value);
|
||||
bool HasLineHeight() const;
|
||||
void ClearLineHeight();
|
||||
Model::FontConfig LineHeightOverrideSource() const;
|
||||
|
||||
void SetFontWeightFromDouble(double fontWeight);
|
||||
void SetBackgroundImageOpacityFromPercentageValue(double percentageValue);
|
||||
void SetBackgroundImagePath(winrt::hstring path);
|
||||
@ -139,13 +143,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
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
|
||||
// AppearanceViewModel is so we can continue to have the 'Text' grouping
|
||||
// we currently have in xaml, since that grouping has some settings that
|
||||
// are defined in AppearanceConfig and some that are not.
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFace);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight);
|
||||
OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontAxes);
|
||||
@ -165,37 +169,43 @@ 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:
|
||||
Model::AppearanceConfig _appearance;
|
||||
winrt::hstring _lastBgImagePath;
|
||||
|
||||
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);
|
||||
|
||||
Model::AppearanceConfig _appearance;
|
||||
winrt::hstring _lastBgImagePath;
|
||||
std::wstring _primaryFontName;
|
||||
};
|
||||
|
||||
struct Appearances : AppearancesT<Appearances>
|
||||
{
|
||||
public:
|
||||
Appearances();
|
||||
static winrt::hstring FontAxisName(const winrt::hstring& key);
|
||||
static winrt::hstring FontFeatureName(const winrt::hstring& key);
|
||||
|
||||
// font face
|
||||
Windows::Foundation::IInspectable CurrentFontFace() const;
|
||||
Appearances();
|
||||
|
||||
// CursorShape visibility logic
|
||||
bool IsVintageCursor() const;
|
||||
|
||||
bool UsingMonospaceFont() const noexcept;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::Font> FilteredFontList();
|
||||
bool ShowAllFonts() const noexcept;
|
||||
void ShowAllFonts(const bool& value);
|
||||
void ShowAllFonts(bool value);
|
||||
|
||||
void FontFaceBox_GotFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
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&);
|
||||
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 FontFace_SelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& 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);
|
||||
@ -219,21 +229,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
GETSET_BINDABLE_ENUM_SETTING(BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch, Appearance().BackgroundImageStretchMode);
|
||||
|
||||
GETSET_BINDABLE_ENUM_SETTING(IntenseTextStyle, Microsoft::Terminal::Settings::Model::IntenseStyle, Appearance().IntenseTextStyle);
|
||||
WINRT_OBSERVABLE_PROPERTY(bool, ShowProportionalFontWarning, PropertyChanged.raise, nullptr);
|
||||
|
||||
private:
|
||||
bool _ShowAllFonts;
|
||||
void _UpdateBIAlignmentControl(const int32_t val);
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
||||
std::array<Windows::UI::Xaml::Controls::Primitives::ToggleButton, 9> _BIAlignmentButtons;
|
||||
|
||||
Windows::Foundation::Collections::IMap<uint16_t, Microsoft::Terminal::Settings::Editor::EnumEntry> _FontWeightMap;
|
||||
Editor::EnumEntry _CustomFontWeight{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::Font> _filteredFonts;
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::hstring> _FontAxesNames;
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::hstring> _FontFeaturesNames;
|
||||
std::wstring _fontNameFilter;
|
||||
bool _ShowAllFonts = false;
|
||||
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
||||
static void _ViewModelChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
|
||||
void _updateFontNameFilter(std::wstring_view filter);
|
||||
void _updateFilteredFontList();
|
||||
void _UpdateBIAlignmentControl(const int32_t val);
|
||||
void _UpdateWithNewViewModel();
|
||||
};
|
||||
};
|
||||
|
||||
@ -21,7 +21,6 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
String Name { get; };
|
||||
String LocalizedName { get; };
|
||||
Boolean HasPowerlineCharacters { get; };
|
||||
Windows.Foundation.Collections.IMap<String, String> FontAxesTagsAndNames { get; };
|
||||
Windows.Foundation.Collections.IMap<String, String> FontFeaturesTagsAndNames { get; };
|
||||
}
|
||||
@ -59,6 +58,10 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
ColorSchemeViewModel CurrentColorScheme;
|
||||
Windows.Foundation.Collections.IObservableVector<ColorSchemeViewModel> SchemesList;
|
||||
|
||||
String MissingFontFaces { get; };
|
||||
String ProportionalFontFaces { get; };
|
||||
Boolean HasPowerlineCharacters { get; };
|
||||
|
||||
void AddNewAxisKeyValuePair();
|
||||
void DeleteAxisKeyValuePair(String key);
|
||||
void InitializeFontAxesVector();
|
||||
@ -103,9 +106,8 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
IHostedInWindow WindowRoot;
|
||||
static Windows.UI.Xaml.DependencyProperty AppearanceProperty { get; };
|
||||
|
||||
Boolean UsingMonospaceFont { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<Font> FilteredFontList { get; };
|
||||
Boolean ShowAllFonts;
|
||||
Boolean ShowProportionalFontWarning;
|
||||
|
||||
IInspectable CurrentCursorShape;
|
||||
Boolean IsVintageCursor { get; };
|
||||
@ -121,8 +123,6 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Boolean IsCustomFontWeight { get; };
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> FontWeightList { get; };
|
||||
|
||||
IInspectable CurrentFontFace { get; };
|
||||
|
||||
IInspectable CurrentIntenseTextStyle;
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> IntenseTextStyleList { get; };
|
||||
}
|
||||
|
||||
@ -183,7 +183,6 @@
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Font Face -->
|
||||
|
||||
<local:SettingContainer x:Uid="Profile_FontFace"
|
||||
ClearSettingValue="{x:Bind Appearance.ClearFontFace}"
|
||||
HasSettingValue="{x:Bind Appearance.HasFontFace, Mode=OneWay}"
|
||||
@ -191,35 +190,29 @@
|
||||
Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}">
|
||||
|
||||
<StackPanel Margin="0,8,0,0">
|
||||
<!--
|
||||
Binding the ItemsSource to a separate variable that switches between the
|
||||
two font lists causes a crash within the ComboBox code.
|
||||
As a workaround, introduce two ComboBox controls and only display one at a time.
|
||||
-->
|
||||
<ComboBox x:Uid="Profile_FontFaceBox"
|
||||
ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
|
||||
ItemsSource="{x:Bind SourceProfile.MonospaceFontList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind CurrentFontFace, Mode=OneWay}"
|
||||
SelectionChanged="FontFace_SelectionChanged"
|
||||
Style="{StaticResource ComboBoxSettingStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.InvertedBooleanToVisibility(ShowAllFonts), Mode=OneWay}" />
|
||||
<ComboBox x:Uid="Profile_FontFaceBox"
|
||||
ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
|
||||
ItemsSource="{x:Bind SourceProfile.CompleteFontList, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind CurrentFontFace, Mode=OneWay}"
|
||||
SelectionChanged="FontFace_SelectionChanged"
|
||||
Style="{StaticResource ComboBoxSettingStyle}"
|
||||
Visibility="{x:Bind ShowAllFonts, Mode=OneWay}" />
|
||||
<AutoSuggestBox x:Name="_fontFaceBox"
|
||||
x:Uid="Profile_FontFaceBox"
|
||||
GotFocus="FontFaceBox_GotFocus"
|
||||
ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
|
||||
ItemsSource="{x:Bind FilteredFontList, Mode=OneWay}"
|
||||
LostFocus="FontFaceBox_LostFocus"
|
||||
SuggestionChosen="FontFaceBox_SuggestionChosen"
|
||||
Text="{x:Bind Appearance.FontFace, Mode=OneWay}"
|
||||
TextBoxStyle="{StaticResource TextBoxSettingStyle}"
|
||||
TextChanged="FontFaceBox_TextChanged" />
|
||||
<CheckBox x:Name="ShowAllFontsCheckbox"
|
||||
x:Uid="Profile_FontFaceShowAllFonts"
|
||||
IsChecked="{x:Bind ShowAllFonts, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind UsingMonospaceFont, Mode=OneWay}" />
|
||||
IsChecked="{x:Bind ShowAllFonts, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
<muxc:InfoBar x:Uid="Profile_FontFace_ProportionalFontWarning"
|
||||
IsOpen="{x:Bind ShowProportionalFontWarning, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
|
||||
<local:SettingContainer x:Uid="Profile_MissingFontFaces"
|
||||
HelpText="{x:Bind Appearance.MissingFontFaces, Mode=OneWay}"
|
||||
Style="{StaticResource SettingContainerErrorStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Appearance.MissingFontFaces), Mode=OneWay}" />
|
||||
<local:SettingContainer x:Uid="Profile_ProportionalFontFaces"
|
||||
HelpText="{x:Bind Appearance.ProportionalFontFaces, Mode=OneWay}"
|
||||
Style="{StaticResource SettingContainerWarningStyle}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Appearance.ProportionalFontFaces), Mode=OneWay}" />
|
||||
|
||||
<!-- Font Size -->
|
||||
<local:SettingContainer x:Uid="Profile_FontSize"
|
||||
|
||||
@ -28,16 +28,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_Profile = args.Profile();
|
||||
_windowRoot = args.WindowRoot();
|
||||
|
||||
// generate the font list, if we don't have one
|
||||
if (_Profile.CompleteFontList() || !_Profile.MonospaceFontList())
|
||||
{
|
||||
ProfileViewModel::UpdateFontList();
|
||||
}
|
||||
|
||||
if (!_previewControl)
|
||||
{
|
||||
const auto settings = _Profile.TermSettings();
|
||||
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
|
||||
_previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters());
|
||||
_previewControl = Control::TermControl(settings, settings, *_previewConnection);
|
||||
_previewControl.IsEnabled(false);
|
||||
_previewControl.AllowFocusWhenDisabled(false);
|
||||
@ -70,25 +64,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_Profile.DeleteUnfocusedAppearance();
|
||||
}
|
||||
|
||||
bool Profiles_Appearance::_looksLikePowerlineFont() const
|
||||
{
|
||||
if (_Profile && _Profile.DefaultAppearance())
|
||||
{
|
||||
if (const auto fontName = _Profile.DefaultAppearance().FontFace(); !fontName.empty())
|
||||
{
|
||||
if (const auto font = ProfileViewModel::FindFontWithLocalizedName(fontName))
|
||||
{
|
||||
return font.HasPowerlineCharacters();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const
|
||||
{
|
||||
const auto settings = _Profile.TermSettings();
|
||||
_previewConnection->DisplayPowerlineGlyphs(_looksLikePowerlineFont());
|
||||
_previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters());
|
||||
_previewControl.UpdateControlSettings(settings, settings);
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
private:
|
||||
void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const;
|
||||
bool _looksLikePowerlineFont() const;
|
||||
|
||||
winrt::com_ptr<PreviewConnection> _previewConnection{ nullptr };
|
||||
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
|
||||
|
||||
@ -137,11 +137,9 @@
|
||||
</data>
|
||||
<data name="ColorScheme_InboxSchemeDuplicate.Header" xml:space="preserve">
|
||||
<value>This color scheme is part of the built-in settings or an installed extension</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ColorScheme_InboxSchemeDuplicate.HelpText" xml:space="preserve">
|
||||
<value>To make changes to this color scheme, you must make a copy of it.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="ColorScheme_DuplicateButton.Content" xml:space="preserve">
|
||||
<value>Make a copy</value>
|
||||
@ -1810,12 +1808,12 @@
|
||||
<value>Learn more.</value>
|
||||
<comment>A hyperlink displayed near Settings_PortableModeNote.Text that the user can follow for more information.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontFace_ProportionalFontWarning.Title" xml:space="preserve">
|
||||
<value>Warning:</value>
|
||||
<comment>Title for the warning info bar used when a non monospace font face is chosen to indicate that there may be visual artifacts</comment>
|
||||
<data name="Profile_MissingFontFaces.Header" xml:space="preserve">
|
||||
<value>Missing fonts:</value>
|
||||
<comment>This is a label that is followed by a list of missing fonts.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontFace_ProportionalFontWarning.Message" xml:space="preserve">
|
||||
<value>Choosing a non-monospaced font will likely result in visual artifacts. Use at your own discretion.</value>
|
||||
<comment>Warning info bar used when a non monospace font face is chosen to indicate that there may be visual artifacts</comment>
|
||||
<data name="Profile_ProportionalFontFaces.Header" xml:space="preserve">
|
||||
<value>Non-monospace fonts:</value>
|
||||
<comment>This is a label that is followed by a list of proportional fonts.</comment>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
@ -39,20 +39,21 @@ protected:
|
||||
|
||||
#define _BASE_OBSERVABLE_PROJECTED_SETTING(target, name) \
|
||||
public: \
|
||||
auto name() const noexcept \
|
||||
auto name() const \
|
||||
{ \
|
||||
return target.name(); \
|
||||
}; \
|
||||
template<typename T> \
|
||||
void name(const T& value) \
|
||||
{ \
|
||||
if (name() != value) \
|
||||
const auto t = target; \
|
||||
if (t.name() != value) \
|
||||
{ \
|
||||
target.name(value); \
|
||||
t.name(value); \
|
||||
_NotifyChanges(L"Has" #name, L## #name); \
|
||||
} \
|
||||
} \
|
||||
bool Has##name() \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return target.Has##name(); \
|
||||
}
|
||||
@ -63,14 +64,15 @@ public: \
|
||||
_BASE_OBSERVABLE_PROJECTED_SETTING(target, name) \
|
||||
void Clear##name() \
|
||||
{ \
|
||||
const auto hadValue{ target.Has##name() }; \
|
||||
target.Clear##name(); \
|
||||
const auto t = target; \
|
||||
const auto hadValue{ t.Has##name() }; \
|
||||
t.Clear##name(); \
|
||||
if (hadValue) \
|
||||
{ \
|
||||
_NotifyChanges(L"Has" #name, L## #name); \
|
||||
} \
|
||||
} \
|
||||
auto name##OverrideSource() \
|
||||
auto name##OverrideSource() const \
|
||||
{ \
|
||||
return target.name##OverrideSource(); \
|
||||
}
|
||||
|
||||
@ -40,6 +40,11 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
return expected != actual;
|
||||
}
|
||||
|
||||
bool Converters::StringNotEmpty(const winrt::hstring& value)
|
||||
{
|
||||
return !value.empty();
|
||||
}
|
||||
|
||||
winrt::Windows::UI::Xaml::Visibility Converters::StringNotEmptyToVisibility(const winrt::hstring& value)
|
||||
{
|
||||
return value.empty() ? winrt::Windows::UI::Xaml::Visibility::Collapsed : winrt::Windows::UI::Xaml::Visibility::Visible;
|
||||
|
||||
@ -20,6 +20,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
// Strings
|
||||
static bool StringsAreNotEqual(const winrt::hstring& expected, const winrt::hstring& actual);
|
||||
static bool StringNotEmpty(const winrt::hstring& value);
|
||||
static winrt::Windows::UI::Xaml::Visibility StringNotEmptyToVisibility(const winrt::hstring& value);
|
||||
static winrt::hstring StringOrEmptyIfPlaceholder(const winrt::hstring& placeholder, const winrt::hstring& value);
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ namespace Microsoft.Terminal.UI
|
||||
|
||||
// Strings
|
||||
static Boolean StringsAreNotEqual(String expected, String actual);
|
||||
static Boolean StringNotEmpty(String value);
|
||||
static Windows.UI.Xaml.Visibility StringNotEmptyToVisibility(String value);
|
||||
static String StringOrEmptyIfPlaceholder(String placeholder, String value);
|
||||
|
||||
|
||||
@ -202,6 +202,26 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return to_ulong<>(str, base);
|
||||
}
|
||||
|
||||
// Implement to_int in terms of to_ulong by negating its result. to_ulong does not expect
|
||||
// to be passed signed numbers and will return an error accordingly. That error when
|
||||
// compared against -1 evaluates to true. We account for that by returning to_int_error if to_ulong
|
||||
// returns an error.
|
||||
constexpr int to_int(const std::wstring_view& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
auto result = to_ulong_error;
|
||||
const auto signPosition = str.find(L"-");
|
||||
const bool hasSign = signPosition != std::wstring_view::npos;
|
||||
result = hasSign ? to_ulong(str.substr(signPosition + 1), base) : to_ulong(str, base);
|
||||
|
||||
// Check that result is valid and will fit in an int.
|
||||
if (result == to_ulong_error || (result > INT_MAX))
|
||||
{
|
||||
return to_int_error;
|
||||
}
|
||||
|
||||
return hasSign ? result * -1 : result;
|
||||
}
|
||||
|
||||
// Just like std::tolower, but without annoying locales.
|
||||
template<typename T>
|
||||
constexpr T tolower_ascii(T c)
|
||||
@ -376,6 +396,81 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return { beg, end };
|
||||
}
|
||||
|
||||
// Splits a font-family list into individual font-families. It loosely follows the CSS spec for font-family.
|
||||
// It splits by comma, handles quotes and simple escape characters, and it cleans whitespace.
|
||||
//
|
||||
// This is not the right place to put this, because it's highly specialized towards font-family names.
|
||||
// But this code is needed both, in our renderer and in our settings UI. At the time I couldn't find a better place for it.
|
||||
void iterate_font_families(const std::wstring_view& families, auto&& callback)
|
||||
{
|
||||
std::wstring family;
|
||||
bool escape = false;
|
||||
bool delayedSpace = false;
|
||||
wchar_t stringType = 0;
|
||||
|
||||
for (const auto ch : families)
|
||||
{
|
||||
if (!escape)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case ' ':
|
||||
if (stringType)
|
||||
{
|
||||
// Spaces are treated literally inside strings.
|
||||
break;
|
||||
}
|
||||
delayedSpace = !family.empty();
|
||||
continue;
|
||||
case '"':
|
||||
case '\'':
|
||||
if (stringType && stringType != ch)
|
||||
{
|
||||
// Single quotes inside double quotes are treated literally and vice versa.
|
||||
break;
|
||||
}
|
||||
stringType = stringType == ch ? 0 : ch;
|
||||
continue;
|
||||
case ',':
|
||||
if (stringType)
|
||||
{
|
||||
// Commas are treated literally inside strings.
|
||||
break;
|
||||
}
|
||||
if (!family.empty())
|
||||
{
|
||||
callback(std::move(family));
|
||||
family.clear();
|
||||
delayedSpace = false;
|
||||
}
|
||||
continue;
|
||||
case '\\':
|
||||
escape = true;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The `delayedSpace` logic automatically takes care for us to
|
||||
// strip leading and trailing spaces and deduplicate them too.
|
||||
if (delayedSpace)
|
||||
{
|
||||
delayedSpace = false;
|
||||
family.push_back(L' ');
|
||||
}
|
||||
|
||||
family.push_back(ch);
|
||||
escape = false;
|
||||
}
|
||||
|
||||
// Just like the comma handler above.
|
||||
if (!stringType && !family.empty())
|
||||
{
|
||||
callback(std::move(family));
|
||||
}
|
||||
}
|
||||
|
||||
// This function is appropriate for case-insensitive equivalence testing of file paths and other "system" strings.
|
||||
// Similar to memcmp, this returns <0, 0 or >0.
|
||||
inline int compare_ordinal_insensitive(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept
|
||||
@ -426,24 +521,4 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47).
|
||||
return FindNLSStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE, str.data(), strLen, needle.data(), needleLen, nullptr, nullptr, nullptr, 0) != -1;
|
||||
}
|
||||
|
||||
// Implement to_int in terms of to_ulong by negating its result. to_ulong does not expect
|
||||
// to be passed signed numbers and will return an error accordingly. That error when
|
||||
// compared against -1 evaluates to true. We account for that by returning to_int_error if to_ulong
|
||||
// returns an error.
|
||||
constexpr int to_int(const std::wstring_view& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
auto result = to_ulong_error;
|
||||
const auto signPosition = str.find(L"-");
|
||||
const bool hasSign = signPosition != std::wstring_view::npos;
|
||||
result = hasSign ? to_ulong(str.substr(signPosition + 1), base) : to_ulong(str, base);
|
||||
|
||||
// Check that result is valid and will fit in an int.
|
||||
if (result == to_ulong_error || (result > INT_MAX))
|
||||
{
|
||||
return to_int_error;
|
||||
}
|
||||
|
||||
return hasSign ? result * -1 : result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +264,7 @@ try
|
||||
}
|
||||
#endif
|
||||
|
||||
_resolveFontMetrics(nullptr, fontInfoDesired, fontInfo);
|
||||
_resolveFontMetrics(fontInfoDesired, fontInfo);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
@ -443,7 +443,7 @@ void AtlasEngine::SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) noexcept
|
||||
void AtlasEngine::SetWarningCallback(std::function<void(HRESULT, wil::zwstring_view)> pfn) noexcept
|
||||
{
|
||||
_p.warningCallback = std::move(pfn);
|
||||
}
|
||||
@ -472,31 +472,39 @@ void AtlasEngine::SetWarningCallback(std::function<void(HRESULT)> pfn) 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
|
||||
{
|
||||
_updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Our leading theory is: When an app package has a <uap7:Extension Category="windows.sharedFonts">
|
||||
// (like Windows Terminal with Cascadia Mono/Code) and it gets updated, the system locks the file somehow.
|
||||
// DirectWrite still has some information about the font cached though, so it thinks that it still exists,
|
||||
// but using the font causes it to error out because it can't access it. This fact became apparent in
|
||||
// commit 9e86c98 (PR #16196), because it showed that it's definitely not due to FindFamilyName() failing.
|
||||
//
|
||||
// The workaround is to catch the exception and retry it with our nearby fonts manually loaded in.
|
||||
if constexpr (Feature_NearbyFontLoading::IsEnabled())
|
||||
{
|
||||
try
|
||||
{
|
||||
// _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection,
|
||||
// before falling back to using the system font collection. This way we can inject our custom one. See GH#9375.
|
||||
// Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection
|
||||
// instance across font changes, like when zooming the font size rapidly using the scroll wheel.
|
||||
_api.s.write()->font.write()->fontCollection = FontCache::GetCached();
|
||||
_updateFont(fontInfoDesired.GetFaceName().c_str(), fontInfoDesired, fontInfo, features, axes);
|
||||
_updateFont(fontInfoDesired, fontInfo, features, axes);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// _resolveFontMetrics() checks `_api.s->font->fontCollection` for a pre-existing font collection,
|
||||
// before falling back to using the system font collection. This way we can inject our custom one.
|
||||
// Doing it this way is a bit hacky, but it does have the benefit that we can cache a font collection
|
||||
// instance across font changes, like when zooming the font size rapidly using the scroll wheel.
|
||||
try
|
||||
{
|
||||
_api.s.write()->font.write()->fontCollection = FontCache::GetCached();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_updateFont(nullptr, fontInfoDesired, fontInfo, features, axes);
|
||||
_updateFont(fontInfoDesired, fontInfo, features, axes);
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -528,7 +536,7 @@ void AtlasEngine::_resolveTransparencySettings() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
void 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)
|
||||
{
|
||||
std::vector<DWRITE_FONT_FEATURE> fontFeatures;
|
||||
if (!features.empty())
|
||||
@ -605,22 +613,19 @@ void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fo
|
||||
}
|
||||
|
||||
const auto font = _api.s.write()->font.write();
|
||||
_resolveFontMetrics(faceName, fontInfoDesired, fontInfo, font);
|
||||
_resolveFontMetrics(fontInfoDesired, fontInfo, font);
|
||||
font->fontFeatures = std::move(fontFeatures);
|
||||
font->fontAxisValues = std::move(fontAxisValues);
|
||||
}
|
||||
|
||||
void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics) const
|
||||
void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics) const
|
||||
{
|
||||
const auto& faceName = fontInfoDesired.GetFaceName();
|
||||
const auto requestedFamily = fontInfoDesired.GetFamily();
|
||||
auto requestedWeight = fontInfoDesired.GetWeight();
|
||||
auto fontSize = fontInfoDesired.GetFontSize();
|
||||
auto requestedSize = fontInfoDesired.GetEngineSize();
|
||||
|
||||
if (!requestedFaceName)
|
||||
{
|
||||
requestedFaceName = L"Consolas";
|
||||
}
|
||||
if (!requestedSize.height)
|
||||
{
|
||||
fontSize = 12.0f;
|
||||
@ -641,22 +646,88 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontCollection(fontCollection.addressof(), FALSE));
|
||||
}
|
||||
|
||||
u32 index = 0;
|
||||
BOOL exists = false;
|
||||
THROW_IF_FAILED(fontCollection->FindFamilyName(requestedFaceName, &index, &exists));
|
||||
THROW_HR_IF(DWRITE_E_NOFONT, !exists);
|
||||
std::wstring primaryFontName;
|
||||
std::wstring missingFontNames;
|
||||
wil::com_ptr<IDWriteFontFamily> primaryFontFamily;
|
||||
wil::com_ptr<IDWriteFontFallbackBuilder> fontFallbackBuilder;
|
||||
|
||||
wil::com_ptr<IDWriteFontFamily> fontFamily;
|
||||
THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof()));
|
||||
// Resolves a comma-separated font list similar to CSS' font-family property. The first font in the list
|
||||
// that can be resolved successfully will be the primary font which dictates the cell size among others.
|
||||
// All remaining fonts are "secondary" fonts used for font fallback.
|
||||
til::iterate_font_families(faceName, [&](std::wstring&& fontName) {
|
||||
u32 index = 0;
|
||||
BOOL exists = false;
|
||||
THROW_IF_FAILED(fontCollection->FindFamilyName(fontName.c_str(), &index, &exists));
|
||||
|
||||
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()));
|
||||
if (!exists)
|
||||
{
|
||||
if (!missingFontNames.empty())
|
||||
{
|
||||
missingFontNames.append(L", ");
|
||||
}
|
||||
missingFontNames.append(fontName);
|
||||
return;
|
||||
}
|
||||
|
||||
wil::com_ptr<IDWriteFontFace> fontFace;
|
||||
THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof()));
|
||||
if (!primaryFontFamily)
|
||||
{
|
||||
primaryFontName = std::move(fontName);
|
||||
THROW_IF_FAILED(fontCollection->GetFontFamily(index, primaryFontFamily.addressof()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!fontFallbackBuilder)
|
||||
{
|
||||
THROW_IF_FAILED(_p.dwriteFactory->CreateFontFallbackBuilder(fontFallbackBuilder.addressof()));
|
||||
}
|
||||
|
||||
static constexpr DWRITE_UNICODE_RANGE fullRange{ 0, 0x10FFFF };
|
||||
auto fontNamePtr = fontName.c_str();
|
||||
THROW_IF_FAILED(fontFallbackBuilder->AddMapping(
|
||||
/* ranges */ &fullRange,
|
||||
/* rangesCount */ 1,
|
||||
/* targetFamilyNames */ &fontNamePtr,
|
||||
/* targetFamilyNamesCount */ 1,
|
||||
/* fontCollection */ fontCollection.get(),
|
||||
/* localeName */ nullptr,
|
||||
/* baseFamilyName */ nullptr,
|
||||
/* scale */ 1.0f));
|
||||
}
|
||||
});
|
||||
|
||||
if (!missingFontNames.empty() && _p.warningCallback)
|
||||
{
|
||||
_p.warningCallback(DWRITE_E_NOFONT, missingFontNames);
|
||||
}
|
||||
|
||||
// Fall back to Consolas if no font was found or specified.
|
||||
if (!primaryFontFamily)
|
||||
{
|
||||
primaryFontName = L"Consolas";
|
||||
|
||||
u32 index = 0;
|
||||
BOOL exists = false;
|
||||
THROW_IF_FAILED(fontCollection->FindFamilyName(primaryFontName.c_str(), &index, &exists));
|
||||
THROW_HR_IF(DWRITE_E_NOFONT, !exists);
|
||||
|
||||
THROW_IF_FAILED(fontCollection->GetFontFamily(index, primaryFontFamily.addressof()));
|
||||
}
|
||||
|
||||
auto fontFallback = _api.systemFontFallback;
|
||||
if (fontFallbackBuilder)
|
||||
{
|
||||
THROW_IF_FAILED(fontFallbackBuilder->AddMappings(_api.systemFontFallback.get()));
|
||||
THROW_IF_FAILED(fontFallbackBuilder->CreateFontFallback(fontFallback.put()));
|
||||
}
|
||||
|
||||
wil::com_ptr<IDWriteFont> primaryFont;
|
||||
THROW_IF_FAILED(primaryFontFamily->GetFirstMatchingFont(static_cast<DWRITE_FONT_WEIGHT>(requestedWeight), DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, primaryFont.addressof()));
|
||||
|
||||
wil::com_ptr<IDWriteFontFace> primaryFontFace;
|
||||
THROW_IF_FAILED(primaryFont->CreateFontFace(primaryFontFace.addressof()));
|
||||
|
||||
DWRITE_FONT_METRICS metrics{};
|
||||
fontFace->GetMetrics(&metrics);
|
||||
primaryFontFace->GetMetrics(&metrics);
|
||||
|
||||
// Point sizes are commonly treated at a 72 DPI scale
|
||||
// (including by OpenType), whereas DirectWrite uses 96 DPI.
|
||||
@ -682,12 +753,12 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
static constexpr u32 codePoint = '0';
|
||||
|
||||
u16 glyphIndex;
|
||||
THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
|
||||
THROW_IF_FAILED(primaryFontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
|
||||
|
||||
if (glyphIndex)
|
||||
{
|
||||
DWRITE_GLYPH_METRICS glyphMetrics{};
|
||||
THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE));
|
||||
THROW_IF_FAILED(primaryFontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE));
|
||||
advanceWidth = static_cast<f32>(glyphMetrics.advanceWidth) * designUnitsPerPx;
|
||||
}
|
||||
}
|
||||
@ -746,12 +817,11 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
requestedSize.width = gsl::narrow_cast<til::CoordType>(lrintf(fontSize / cellHeight * cellWidth));
|
||||
}
|
||||
|
||||
fontInfo.SetFromEngine(requestedFaceName, requestedFamily, requestedWeight, false, coordSize, requestedSize);
|
||||
fontInfo.SetFromEngine(primaryFontName, requestedFamily, requestedWeight, false, coordSize, requestedSize);
|
||||
}
|
||||
|
||||
if (fontMetrics)
|
||||
{
|
||||
std::wstring fontName{ requestedFaceName };
|
||||
const auto fontWeightU16 = gsl::narrow_cast<u16>(requestedWeight);
|
||||
const auto advanceWidthU16 = gsl::narrow_cast<u16>(lrintf(advanceWidth));
|
||||
const auto baselineU16 = gsl::narrow_cast<u16>(lrintf(baseline));
|
||||
@ -773,8 +843,9 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
|
||||
// as we might cause _api to be in an inconsistent state otherwise.
|
||||
|
||||
fontMetrics->fontCollection = std::move(fontCollection);
|
||||
fontMetrics->fontFamily = std::move(fontFamily);
|
||||
fontMetrics->fontName = std::move(fontName);
|
||||
fontMetrics->fontFallback = std::move(fontFallback);
|
||||
fontMetrics->fontFallback.try_query_to(fontMetrics->fontFallback1.put());
|
||||
fontMetrics->fontName = std::move(primaryFontName);
|
||||
fontMetrics->fontSize = fontSizeInPx;
|
||||
fontMetrics->cellSize = { cellWidth, cellHeight };
|
||||
fontMetrics->fontWeight = fontWeightU16;
|
||||
|
||||
@ -41,8 +41,7 @@ AtlasEngine::AtlasEngine()
|
||||
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(_p.dwriteFactory), reinterpret_cast<::IUnknown**>(_p.dwriteFactory.addressof())));
|
||||
_p.dwriteFactory4 = _p.dwriteFactory.try_query<IDWriteFactory4>();
|
||||
|
||||
THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontFallback(_p.systemFontFallback.addressof()));
|
||||
_p.systemFontFallback1 = _p.systemFontFallback.try_query<IDWriteFontFallback1>();
|
||||
THROW_IF_FAILED(_p.dwriteFactory->GetSystemFontFallback(_api.systemFontFallback.addressof()));
|
||||
|
||||
wil::com_ptr<IDWriteTextAnalyzer> textAnalyzer;
|
||||
THROW_IF_FAILED(_p.dwriteFactory->CreateTextAnalyzer(textAnalyzer.addressof()));
|
||||
@ -575,7 +574,7 @@ void AtlasEngine::_recreateFontDependentResources()
|
||||
{
|
||||
wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
|
||||
|
||||
if (FAILED(GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH)))
|
||||
if (!GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH))
|
||||
{
|
||||
memcpy(&localeName[0], L"en-US", 12);
|
||||
}
|
||||
@ -841,7 +840,7 @@ void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32*
|
||||
|
||||
if (textFormatAxis)
|
||||
{
|
||||
THROW_IF_FAILED(_p.systemFontFallback1->MapCharacters(
|
||||
THROW_IF_FAILED(_p.s->font->fontFallback1->MapCharacters(
|
||||
/* analysisSource */ &analysisSource,
|
||||
/* textPosition */ 0,
|
||||
/* textLength */ textLength,
|
||||
@ -859,7 +858,7 @@ void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32*
|
||||
const auto baseStyle = WI_IsFlagSet(_api.attributes, FontRelevantAttributes::Italic) ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
|
||||
wil::com_ptr<IDWriteFont> font;
|
||||
|
||||
THROW_IF_FAILED(_p.systemFontFallback->MapCharacters(
|
||||
THROW_IF_FAILED(_p.s->font->fontFallback->MapCharacters(
|
||||
/* analysisSource */ &analysisSource,
|
||||
/* textPosition */ 0,
|
||||
/* textLength */ textLength,
|
||||
|
||||
@ -76,7 +76,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
void SetSoftwareRendering(bool enable) noexcept;
|
||||
void SetDisablePartialInvalidation(bool enable) noexcept;
|
||||
void SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept;
|
||||
void SetWarningCallback(std::function<void(HRESULT)> pfn) 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;
|
||||
|
||||
@ -94,8 +94,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
|
||||
// AtlasEngine.api.cpp
|
||||
void _resolveTransparencySettings() noexcept;
|
||||
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, FontSettings* fontMetrics = nullptr) const;
|
||||
void _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);
|
||||
void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr) const;
|
||||
|
||||
// AtlasEngine.r.cpp
|
||||
ATLAS_ATTR_COLD void _recreateAdapter();
|
||||
@ -156,6 +156,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
Buffer<f32> glyphAdvances;
|
||||
Buffer<DWRITE_GLYPH_OFFSET> glyphOffsets;
|
||||
|
||||
wil::com_ptr<IDWriteFontFallback> systemFontFallback;
|
||||
wil::com_ptr<IDWriteFontFace2> replacementCharacterFontFace;
|
||||
u16 replacementCharacterGlyphIndex = 0;
|
||||
bool replacementCharacterLookedUp = false;
|
||||
|
||||
@ -65,7 +65,7 @@ catch (const wil::ResultException& exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
_p.warningCallback(hr);
|
||||
_p.warningCallback(hr, {});
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
||||
@ -432,7 +432,7 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
|
||||
}
|
||||
if (p.warningCallback)
|
||||
{
|
||||
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
|
||||
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, p.s->misc->customPixelShaderPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,7 +448,7 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
|
||||
_customPixelShader.reset();
|
||||
if (p.warningCallback)
|
||||
{
|
||||
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
|
||||
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, p.s->misc->customPixelShaderImagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1439,7 +1439,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa
|
||||
|
||||
if (BuiltinGlyphs::IsSoftFontChar(glyphIndex))
|
||||
{
|
||||
_drawSoftFontGlyph(p, r, glyphIndex);
|
||||
shadingType = _drawSoftFontGlyph(p, r, glyphIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1465,22 +1465,17 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa
|
||||
return glyphEntry;
|
||||
}
|
||||
|
||||
void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex)
|
||||
BackendD3D::ShadingType BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex)
|
||||
{
|
||||
_d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
const auto restoreD2D = wil::scope_exit([&]() {
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
});
|
||||
|
||||
const auto width = static_cast<size_t>(p.s->font->softFontCellSize.width);
|
||||
const auto height = static_cast<size_t>(p.s->font->softFontCellSize.height);
|
||||
const auto softFontIndex = glyphIndex - 0xEF20u;
|
||||
const auto data = til::clamp_slice_len(p.s->font->softFontPattern, height * softFontIndex, height);
|
||||
|
||||
// This happens if someone wrote a U+EF2x character (by accident), but we don't even have soft fonts enabled yet.
|
||||
if (data.empty() || data.size() != height)
|
||||
{
|
||||
_d2dRenderTarget->Clear();
|
||||
return;
|
||||
return ShadingType::Default;
|
||||
}
|
||||
|
||||
if (!_softFontBitmap)
|
||||
@ -1517,8 +1512,14 @@ void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F
|
||||
THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch));
|
||||
}
|
||||
|
||||
_d2dRenderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
const auto restoreD2D = wil::scope_exit([&]() {
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
});
|
||||
|
||||
const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC;
|
||||
_d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &rect, 1, interpolation, nullptr, nullptr);
|
||||
return ShadingType::TextGrayscale;
|
||||
}
|
||||
|
||||
void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect)
|
||||
|
||||
@ -215,7 +215,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y);
|
||||
[[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
AtlasGlyphEntry* _drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
void _drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex);
|
||||
ShadingType _drawSoftFontGlyph(const RenderingPayload& p, const D2D1_RECT_F& rect, u32 glyphIndex);
|
||||
void _drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect);
|
||||
static AtlasGlyphEntry* _drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex);
|
||||
static void _splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry);
|
||||
|
||||
@ -345,7 +345,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
struct FontSettings
|
||||
{
|
||||
wil::com_ptr<IDWriteFontCollection> fontCollection;
|
||||
wil::com_ptr<IDWriteFontFamily> fontFamily;
|
||||
wil::com_ptr<IDWriteFontFallback> fontFallback;
|
||||
wil::com_ptr<IDWriteFontFallback1> fontFallback1; // optional, might be nullptr
|
||||
std::wstring fontName;
|
||||
std::vector<DWRITE_FONT_FEATURE> fontFeatures;
|
||||
std::vector<DWRITE_FONT_AXIS_VALUE> fontAxisValues;
|
||||
@ -488,10 +489,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
wil::com_ptr<ID2D1Factory> d2dFactory;
|
||||
wil::com_ptr<IDWriteFactory2> dwriteFactory;
|
||||
wil::com_ptr<IDWriteFactory4> dwriteFactory4; // optional, might be nullptr
|
||||
wil::com_ptr<IDWriteFontFallback> systemFontFallback;
|
||||
wil::com_ptr<IDWriteFontFallback1> systemFontFallback1; // optional, might be nullptr
|
||||
wil::com_ptr<IDWriteTextAnalyzer1> textAnalyzer;
|
||||
std::function<void(HRESULT)> warningCallback;
|
||||
std::function<void(HRESULT, wil::zwstring_view)> warningCallback;
|
||||
std::function<void(HANDLE)> swapChainChangedCallback;
|
||||
|
||||
//// Parameters which are constant for the existence of the backend.
|
||||
|
||||
@ -7,6 +7,25 @@ using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
template<>
|
||||
class WEX::TestExecution::VerifyOutputTraits<std::vector<std::wstring>>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const std::vector<std::wstring>& vec)
|
||||
{
|
||||
WEX::Common::NoThrowString str;
|
||||
str.Append(L"{ ");
|
||||
for (size_t i = 0; i < vec.size(); ++i)
|
||||
{
|
||||
str.Append(i == 0 ? L"\"" : L", \"");
|
||||
str.Append(vec[i].c_str());
|
||||
str.Append(L"\"");
|
||||
}
|
||||
str.Append(L" }");
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
class StringTests
|
||||
{
|
||||
TEST_CLASS(StringTests);
|
||||
@ -199,4 +218,24 @@ class StringTests
|
||||
VERIFY_IS_TRUE(til::is_legal_path(LR"(C:\Users\Documents and Settings\Users\;\Why not)"));
|
||||
VERIFY_IS_FALSE(til::is_legal_path(LR"(C:\Users\Documents and Settings\"Quote-un-quote users")"));
|
||||
}
|
||||
|
||||
TEST_METHOD(IterateFontFamilies)
|
||||
{
|
||||
static constexpr auto expected = [](auto&&... args) {
|
||||
return std::vector<std::wstring>{ std::forward<decltype(args)>(args)... };
|
||||
};
|
||||
static constexpr auto actual = [](std::wstring_view families) {
|
||||
std::vector<std::wstring> split;
|
||||
til::iterate_font_families(families, [&](std::wstring&& str) {
|
||||
split.emplace_back(std::move(str));
|
||||
});
|
||||
return split;
|
||||
};
|
||||
|
||||
VERIFY_ARE_EQUAL(expected(L"foo", L" b a r ", LR"(b"az)"), actual(LR"( foo ," b a r ",b\"az)"));
|
||||
VERIFY_ARE_EQUAL(expected(LR"(foo, bar)"), actual(LR"("foo, bar")"));
|
||||
VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"('"foo"', "'bar'")"));
|
||||
VERIFY_ARE_EQUAL(expected(LR"("foo")", LR"('bar')"), actual(LR"("\"foo\"", '\'bar\'')"));
|
||||
VERIFY_ARE_EQUAL(expected(L"foo"), actual(LR"(,,,,foo,,,,)"));
|
||||
}
|
||||
};
|
||||
|
||||
76
tools/Lock-CascadiaFont.ps1
Normal file
76
tools/Lock-CascadiaFont.ps1
Normal file
@ -0,0 +1,76 @@
|
||||
# This script is a failed attempt to lock the Cascadia Mono/Code font files in order to reproduce an issue with the font
|
||||
# cache service, where it says a font exists, but then fails to use it (see GH#9375). The script doesn't work because
|
||||
# for some reason DirectWrite is still able to fully use the fonts. It's left here for reference.
|
||||
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
Add-Type -TypeDefinition @"
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public class Win32LockFile {
|
||||
public const uint LOCKFILE_FAIL_IMMEDIATELY = 0x00000001;
|
||||
public const uint LOCKFILE_EXCLUSIVE_LOCK = 0x00000002;
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool LockFileEx(IntPtr hFile, uint dwFlags, uint dwReserved, uint nNumberOfBytesToLockLow, uint nNumberOfBytesToLockHigh, ref OVERLAPPED lpOverlapped);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct OVERLAPPED {
|
||||
public uint Internal;
|
||||
public uint InternalHigh;
|
||||
public uint Offset;
|
||||
public uint OffsetHigh;
|
||||
public IntPtr hEvent;
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
function Lock-File {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$file = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
|
||||
$overlapped = New-Object Win32LockFile+OVERLAPPED
|
||||
$result = [Win32LockFile]::LockFileEx(
|
||||
$file.SafeFileHandle.DangerousGetHandle(), # hFile
|
||||
[Win32LockFile]::LOCKFILE_EXCLUSIVE_LOCK, # dwFlags
|
||||
0, # dwReserved
|
||||
[UInt32]::MaxValue, # nNumberOfBytesToLockLow
|
||||
[UInt32]::MaxValue, # nNumberOfBytesToLockHigh
|
||||
[ref]$overlapped # lpOverlapped
|
||||
)
|
||||
|
||||
if (-not $result) {
|
||||
throw "Failed to lock file"
|
||||
}
|
||||
|
||||
return $file
|
||||
}
|
||||
|
||||
$fonts = Get-ItemProperty "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Fonts\*"
|
||||
| ForEach-Object { $_.PSobject.Properties }
|
||||
| Where-Object { $_.Name.StartsWith("Cascadia") }
|
||||
| ForEach-Object { $_.Value }
|
||||
|
||||
$fonts += @("CascadiaCode.ttf", "CascadiaCodeItalic.ttf", "CascadiaMono.ttf", "CascadiaMonoItalic.ttf")
|
||||
| ForEach-Object { "C:\Windows\Fonts\$_" }
|
||||
| Where-Object { Test-Path $_ }
|
||||
|
||||
try {
|
||||
$handles = $fonts | ForEach-Object {
|
||||
try {
|
||||
Lock-File $_
|
||||
}
|
||||
catch {
|
||||
Write-Error $_
|
||||
}
|
||||
}
|
||||
Restart-Service FontCache
|
||||
Write-Host "Press any key to unlock the font files..."
|
||||
$null = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
} finally {
|
||||
$handles | Where-Object { $_ } | ForEach-Object { $_.Close() }
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user