From 9c10575c73daaf506d767567d3266bdb687345e8 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 11 Oct 2023 11:40:40 -0500 Subject: [PATCH 01/50] Don't end the current mark, if we get one of the same kind (#16107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you're already in the "output" state, then an app requesting an "output" mark probably shouldn't end the current mark and start a new one. It should just keep on keepin' on. The decision to end the previous one was arbitrary in the first place, so let's arbitrarily change it back. Especially noticable if you hit Enter during a command, because the auto-mark prompt work will do a CommandEnd, so long-running commands will get broken into multiple marks 🥲 (cherry picked from commit 0144cdd7bcb6d18a36dce12b783ffde9bd62020a) Service-Card-Id: 91707294 Service-Version: 1.19 --- src/cascadia/TerminalCore/TerminalApi.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index efdb9f8ee77..9a5ea2221ac 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -341,6 +341,13 @@ void Terminal::MarkCommandStart() // //We can just do the work below safely. } + else if (_currentPromptState == PromptState::Command) + { + // We're already in the command state. We don't want to end the current + // mark. We don't want to make a new one. We want to just leave the + // current command going. + return; + } else { // If there was no last mark, or we're in a weird state, @@ -369,6 +376,13 @@ void Terminal::MarkOutputStart() // //We can just do the work below safely. } + else if (_currentPromptState == PromptState::Output) + { + // We're already in the output state. We don't want to end the current + // mark. We don't want to make a new one. We want to just leave the + // current output going. + return; + } else { // If there was no last mark, or we're in a weird state, From 04edb112ea06719c187fed7a9da4347bc7028ca5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 29 Jan 2024 23:01:18 +0100 Subject: [PATCH 02/50] Pump the message queue on frozen windows (#16588) This changeset ensures that the message queue of frozen windows is always being serviced. This should ensure that it won't fill up and lead to deadlocks, freezes, or similar. I've tried _a lot_ of different approaches before settling on this one. Introducing a custom `WM_APP` message has the benefit of being the least intrusive to the existing code base. The approach that I would have favored the most would be to never destroy the `AppHost` instance in the first place, as I imagined that this would be more robust in general and resolve other (rare) bugs. However, I found that this requires rewriting some substantial parts of the code base around `AppHost` and it could be something that may be of interest in the future. Closes #16332 Depends on #16587 and #16575 (cherry picked from commit 5d2fa4782fcb7658996ea4f2078577bc27a6a6d5) Service-Card-Id: 91642479 Service-Version: 1.19 --- .github/actions/spelling/expect/expect.txt | 1 + src/cascadia/WindowsTerminal/AppHost.cpp | 20 ++--- src/cascadia/WindowsTerminal/AppHost.h | 2 + .../WindowsTerminal/NonClientIslandWindow.cpp | 5 ++ .../WindowsTerminal/NonClientIslandWindow.h | 1 + src/cascadia/WindowsTerminal/WindowThread.cpp | 73 +++++++++---------- src/cascadia/WindowsTerminal/WindowThread.h | 3 - 7 files changed, 50 insertions(+), 55 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index c141433d7af..bc1633f6908 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -503,6 +503,7 @@ directio DIRECTX DISABLEDELAYEDEXPANSION DISABLENOSCROLL +DISPATCHNOTIFY DISPLAYATTRIBUTE DISPLAYATTRIBUTEPROPERTY DISPLAYCHANGE diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 3168011a491..6a0e0ed698d 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -535,23 +535,19 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se { _windowLogic.ClearPersistedWindowState(); } - - // If the user closes the last tab, in the last window, _by closing the tab_ - // (not by closing the whole window), we need to manually persist an empty - // window state here. That will cause the terminal to re-open with the usual - // settings (not the persisted state) - if (args.ClearPersistedState() && - _windowManager.GetNumberOfPeasants() == 1) - { - _windowLogic.ClearPersistedWindowState(); - } - // Remove ourself from the list of peasants so that we aren't included in // any future requests. This will also mean we block until any existing // event handler finishes. _windowManager.SignalClose(_peasant); - PostQuitMessage(0); + if (Utils::IsWindows11()) + { + PostQuitMessage(0); + } + else + { + PostMessageW(_window->GetInteropHandle(), WM_REFRIGERATE, 0, 0); + } } LaunchPosition AppHost::_GetWindowLaunchPosition() diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 992a4402874..60b96103124 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -11,6 +11,8 @@ class AppHost : public std::enable_shared_from_this { public: + static constexpr DWORD WM_REFRIGERATE = WM_APP + 0; + AppHost(const winrt::TerminalApp::AppLogic& logic, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, const winrt::Microsoft::Terminal::Remoting::WindowManager& manager, diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index ee538bfa71a..bab65132266 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -26,6 +26,11 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) { } +NonClientIslandWindow::~NonClientIslandWindow() +{ + Close(); +} + void NonClientIslandWindow::Close() { // Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index d173ad68c2d..b2cbb5a8a29 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -30,6 +30,7 @@ class NonClientIslandWindow : public IslandWindow static constexpr const int topBorderVisibleHeight = 1; NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; + ~NonClientIslandWindow() override; void Refrigerate() noexcept override; diff --git a/src/cascadia/WindowsTerminal/WindowThread.cpp b/src/cascadia/WindowsTerminal/WindowThread.cpp index 2c70741ad1c..bafe6ef97e5 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.cpp +++ b/src/cascadia/WindowsTerminal/WindowThread.cpp @@ -4,6 +4,8 @@ #include "pch.h" #include "WindowThread.h" +using namespace winrt::Microsoft::Terminal::Remoting; + WindowThread::WindowThread(winrt::TerminalApp::AppLogic logic, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, winrt::Microsoft::Terminal::Remoting::WindowManager manager, @@ -81,17 +83,6 @@ void WindowThread::RundownForExit() _pumpRemainingXamlMessages(); } -void WindowThread::ThrowAway() -{ - // raise the signal to unblock KeepWarm. We won't have a host, so we'll drop - // out of the message loop to eventually RundownForExit. - // - // This should only be called when the app is fully quitting. After this is - // called on any thread, on win10, we won't be able to call into XAML - // anymore. - _microwaveBuzzer.notify_one(); -} - // Method Description: // - Check if we should keep this window alive, to try it's message loop again. // If we were refrigerated for later, then this will block the thread on the @@ -108,29 +99,24 @@ bool WindowThread::KeepWarm() return true; } - // If we're refrigerated, then wait on the microwave signal, which will be - // raised when we get re-heated by another thread to reactivate us. - - if (_warmWindow != nullptr) + // Even when the _host has been destroyed the HWND will continue receiving messages, in particular WM_DISPATCHNOTIFY at least once a second. This is important to Windows as it keeps your room warm. + MSG msg; + for (;;) { - std::unique_lock lock(_microwave); - _microwaveBuzzer.wait(lock); - - // If ThrowAway() was called, then the buzzer will be signalled without - // setting a new _host. In that case, the app is quitting, for real. We - // just want to exit with false. - const bool reheated = _host != nullptr; - if (reheated) + if (!GetMessageW(&msg, nullptr, 0, 0)) + { + return false; + } + // We're using a single window message (WM_REFRIGERATE) to indicate both + // state transitions. In this case, the window is actually being woken up. + if (msg.message == AppHost::WM_REFRIGERATE) { _UpdateSettingsRequestedToken = _host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); }); // Re-initialize the host here, on the window thread _host->Initialize(); + return true; } - return reheated; - } - else - { - return false; + DispatchMessageW(&msg); } } @@ -154,24 +140,22 @@ void WindowThread::Refrigerate() // Method Description: // - "Reheat" this thread for reuse. We'll build a new AppHost, and pass in the -// existing window to it. We'll then trigger the _microwaveBuzzer, so KeepWarm -// (which is on the UI thread) will get unblocked, and we can initialize this -// window. -void WindowThread::Microwave( - winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, - winrt::Microsoft::Terminal::Remoting::Peasant peasant) +// existing window to it. We'll then wake up the thread stuck in KeepWarm(). +void WindowThread::Microwave(WindowRequestedArgs args, Peasant peasant) { + const auto hwnd = _warmWindow->GetInteropHandle(); + _peasant = std::move(peasant); _args = std::move(args); - _host = std::make_shared<::AppHost>(_appLogic, - _args, - _manager, - _peasant, - std::move(_warmWindow)); + _host = std::make_shared(_appLogic, + _args, + _manager, + _peasant, + std::move(_warmWindow)); // raise the signal to unblock KeepWarm and start the window message loop again. - _microwaveBuzzer.notify_one(); + PostMessageW(hwnd, AppHost::WM_REFRIGERATE, 0, 0); } winrt::TerminalApp::TerminalWindow WindowThread::Logic() @@ -198,6 +182,15 @@ int WindowThread::_messagePump() while (GetMessageW(&message, nullptr, 0, 0)) { + // We're using a single window message (WM_REFRIGERATE) to indicate both + // state transitions. In this case, the window is actually being refrigerated. + // This will break us out of our main message loop we'll eventually start + // the loop in WindowThread::KeepWarm to await a call to Microwave(). + if (message.message == AppHost::WM_REFRIGERATE) + { + break; + } + // GH#638 (Pressing F7 brings up both the history AND a caret browsing message) // The Xaml input stack doesn't allow an application to suppress the "caret browsing" // dialog experience triggered when you press F7. Official recommendation from the Xaml diff --git a/src/cascadia/WindowsTerminal/WindowThread.h b/src/cascadia/WindowsTerminal/WindowThread.h index a1af2db6500..c536e4297aa 100644 --- a/src/cascadia/WindowsTerminal/WindowThread.h +++ b/src/cascadia/WindowsTerminal/WindowThread.h @@ -22,7 +22,6 @@ class WindowThread : public std::enable_shared_from_this void Microwave( winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args, winrt::Microsoft::Terminal::Remoting::Peasant peasant); - void ThrowAway(); uint64_t PeasantID(); @@ -43,8 +42,6 @@ class WindowThread : public std::enable_shared_from_this winrt::event_token _UpdateSettingsRequestedToken; std::unique_ptr<::IslandWindow> _warmWindow{ nullptr }; - std::mutex _microwave; - std::condition_variable _microwaveBuzzer; int _messagePump(); void _pumpRemainingXamlMessages(); From 29895e1c2d7c00655f683b405da6c87b7ee378a9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 5 Dec 2023 03:02:46 +0100 Subject: [PATCH 03/50] Improve conhost's scrolling performance (#16333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `EnableScrollbar()` and especially `SetScrollInfo()` are prohibitively expensive functions nowadays. This improves throughput of good old `type` in cmd.exe by ~10x, by briefly releasing the console lock. ## Validation Steps Performed * `type`ing a file in `cmd` is as fast while the window is scrolling as it is while it isn't scrolling ✅ * Scrollbar pops in and out when scroll-forward is disabled ✅ (cherry picked from commit 71a6f26e6ece656084e87de1a528c4a8072eeabd) Service-Card-Id: 91152166 Service-Version: 1.19 --- src/host/consoleInformation.cpp | 5 ++ src/host/renderData.cpp | 6 ++- src/host/screenInfo.cpp | 54 +++++---------------- src/host/screenInfo.hpp | 9 +++- src/host/server.h | 1 + src/interactivity/inc/IConsoleWindow.hpp | 7 --- src/interactivity/onecore/ConsoleWindow.cpp | 14 ------ src/interactivity/onecore/ConsoleWindow.hpp | 3 -- src/interactivity/win32/window.cpp | 49 ++++++++++--------- src/interactivity/win32/window.hpp | 8 +-- src/interactivity/win32/windowproc.cpp | 13 ++++- 11 files changed, 68 insertions(+), 101 deletions(-) diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp index 4722cebe2b9..3d74bab83bd 100644 --- a/src/host/consoleInformation.cpp +++ b/src/host/consoleInformation.cpp @@ -35,6 +35,11 @@ void CONSOLE_INFORMATION::UnlockConsole() noexcept _lock.unlock(); } +til::recursive_ticket_lock_suspension CONSOLE_INFORMATION::SuspendLock() noexcept +{ + return _lock.suspend(); +} + ULONG CONSOLE_INFORMATION::GetCSRecursionCount() const noexcept { return _lock.recursion_depth(); diff --git a/src/host/renderData.cpp b/src/host/renderData.cpp index d327c0b9d61..5c89edae9a9 100644 --- a/src/host/renderData.cpp +++ b/src/host/renderData.cpp @@ -87,14 +87,16 @@ std::vector RenderData::GetSelectionRects() noexcept // they're done with any querying they need to do. void RenderData::LockConsole() noexcept { - ::LockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.LockConsole(); } // Method Description: // - Unlocks the console after a call to RenderData::LockConsole. void RenderData::UnlockConsole() noexcept { - ::UnlockConsole(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + gci.UnlockConsole(); } // Method Description: diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index d02bbaf3d67..3654bd3cb71 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -34,7 +34,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( const TextAttribute popupAttributes, const FontInfo fontInfo) : OutputMode{ ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT }, - ResizingWindow{ 0 }, WheelDelta{ 0 }, HWheelDelta{ 0 }, _textBuffer{ nullptr }, @@ -641,64 +640,35 @@ VOID SCREEN_INFORMATION::UpdateScrollBars() return; } - if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS) + if (gci.Flags & CONSOLE_UPDATING_SCROLL_BARS || ServiceLocator::LocateConsoleWindow() == nullptr) { return; } gci.Flags |= CONSOLE_UPDATING_SCROLL_BARS; - - if (ServiceLocator::LocateConsoleWindow() != nullptr) - { - ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars(); - } + LOG_IF_WIN32_BOOL_FALSE(ServiceLocator::LocateConsoleWindow()->PostUpdateScrollBars()); } -VOID SCREEN_INFORMATION::InternalUpdateScrollBars() +SCREEN_INFORMATION::ScrollBarState SCREEN_INFORMATION::FetchScrollBarState() { auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto pWindow = ServiceLocator::LocateConsoleWindow(); - WI_ClearFlag(gci.Flags, CONSOLE_UPDATING_SCROLL_BARS); - if (!IsActiveScreenBuffer()) - { - return; - } - - ResizingWindow++; - - if (pWindow != nullptr) - { - const auto buffer = GetBufferSize(); - - // If this is the main buffer, make sure we enable both of the scroll bars. - // The alt buffer likely disabled the scroll bars, this is the only - // way to re-enable it. - if (!_IsAltBuffer()) - { - pWindow->EnableBothScrollBars(); - } - - pWindow->UpdateScrollBar(true, - _IsAltBuffer(), - _viewport.Height(), - gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(), - _viewport.Top()); - pWindow->UpdateScrollBar(false, - _IsAltBuffer(), - _viewport.Width(), - buffer.RightInclusive(), - _viewport.Left()); - } - // Fire off an event to let accessibility apps know the layout has changed. if (_pAccessibilityNotifier) { _pAccessibilityNotifier->NotifyConsoleLayoutEvent(); } - ResizingWindow--; + const auto buffer = GetBufferSize(); + const auto isAltBuffer = _IsAltBuffer(); + const auto maxSizeVer = gci.IsTerminalScrolling() ? _virtualBottom : buffer.BottomInclusive(); + const auto maxSizeHor = buffer.RightInclusive(); + return ScrollBarState{ + .maxSize = { maxSizeHor, maxSizeVer }, + .viewport = _viewport.ToExclusive(), + .isAltBuffer = isAltBuffer, + }; } // Routine Description: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 09fca19809b..c0351050566 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -99,8 +99,14 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool HasAccessibilityEventing() const noexcept; void NotifyAccessibilityEventing(const til::CoordType sStartX, const til::CoordType sStartY, const til::CoordType sEndX, const til::CoordType sEndY); + struct ScrollBarState + { + til::size maxSize; + til::rect viewport; + bool isAltBuffer = false; + }; void UpdateScrollBars(); - void InternalUpdateScrollBars(); + ScrollBarState FetchScrollBarState(); bool IsMaximizedBoth() const; bool IsMaximizedX() const; @@ -158,7 +164,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool CursorIsDoubleWidth() const; DWORD OutputMode; - WORD ResizingWindow; // > 0 if we should ignore WM_SIZE messages short WheelDelta; short HWheelDelta; diff --git a/src/host/server.h b/src/host/server.h index 3332cc6b383..cfa1ba14dc6 100644 --- a/src/host/server.h +++ b/src/host/server.h @@ -102,6 +102,7 @@ class CONSOLE_INFORMATION : void LockConsole() noexcept; void UnlockConsole() noexcept; + til::recursive_ticket_lock_suspension SuspendLock() noexcept; bool IsConsoleLocked() const noexcept; ULONG GetCSRecursionCount() const noexcept; diff --git a/src/interactivity/inc/IConsoleWindow.hpp b/src/interactivity/inc/IConsoleWindow.hpp index 232957ffb6c..fba098ea78d 100644 --- a/src/interactivity/inc/IConsoleWindow.hpp +++ b/src/interactivity/inc/IConsoleWindow.hpp @@ -25,13 +25,6 @@ namespace Microsoft::Console::Types public: virtual ~IConsoleWindow() = default; - virtual BOOL EnableBothScrollBars() = 0; - virtual int UpdateScrollBar(_In_ bool isVertical, - _In_ bool isAltBuffer, - _In_ UINT pageSize, - _In_ int maxSize, - _In_ int viewportPosition) = 0; - virtual bool IsInFullscreen() const = 0; virtual void SetIsFullscreen(const bool fFullscreenEnabled) = 0; diff --git a/src/interactivity/onecore/ConsoleWindow.cpp b/src/interactivity/onecore/ConsoleWindow.cpp index f6b7fac3c89..8d8fb9dcdc4 100644 --- a/src/interactivity/onecore/ConsoleWindow.cpp +++ b/src/interactivity/onecore/ConsoleWindow.cpp @@ -11,20 +11,6 @@ using namespace Microsoft::Console::Interactivity::OneCore; using namespace Microsoft::Console::Types; -BOOL ConsoleWindow::EnableBothScrollBars() noexcept -{ - return FALSE; -} - -int ConsoleWindow::UpdateScrollBar(bool /*isVertical*/, - bool /*isAltBuffer*/, - UINT /*pageSize*/, - int /*maxSize*/, - int /*viewportPosition*/) noexcept -{ - return 0; -} - bool ConsoleWindow::IsInFullscreen() const noexcept { return true; diff --git a/src/interactivity/onecore/ConsoleWindow.hpp b/src/interactivity/onecore/ConsoleWindow.hpp index 4ab28d8626d..530aafd13cb 100644 --- a/src/interactivity/onecore/ConsoleWindow.hpp +++ b/src/interactivity/onecore/ConsoleWindow.hpp @@ -24,9 +24,6 @@ namespace Microsoft::Console::Interactivity::OneCore { public: // Inherited via IConsoleWindow - BOOL EnableBothScrollBars() noexcept override; - int UpdateScrollBar(bool isVertical, bool isAltBuffer, UINT pageSize, int maxSize, int viewportPosition) noexcept override; - bool IsInFullscreen() const noexcept override; void SetIsFullscreen(const bool fFullscreenEnabled) noexcept override; void ChangeViewport(const til::inclusive_rect& NewWindow) override; diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 5b0dde18949..ebf30198217 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -438,7 +438,7 @@ void Window::_CloseWindow() const ShowWindow(hWnd, wShowWindow); auto& siAttached = GetScreenInfo(); - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } return status; @@ -591,7 +591,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (WI_IsFlagClear(gci.Flags, CONSOLE_IS_ICONIC)) { - ScreenInfo.InternalUpdateScrollBars(); + ScreenInfo.UpdateScrollBars(); SetWindowPos(GetWindowHandle(), nullptr, @@ -621,7 +621,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) if (!IsInFullscreen() && !IsInMaximized()) { // Figure out how big to make the window, given the desired client area size. - siAttached.ResizingWindow++; + _resizingWindow++; // First get the buffer viewport size const auto WindowDimensions = siAttached.GetViewport().Dimensions(); @@ -691,7 +691,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // If the change wasn't substantial, we may still need to update scrollbar positions. Note that PSReadLine // scrolls the window via Console.SetWindowPosition, which ultimately calls down to SetConsoleWindowInfo, // which ends up in this function. - siAttached.InternalUpdateScrollBars(); + siAttached.UpdateScrollBars(); } // MSFT: 12092729 @@ -716,7 +716,7 @@ void Window::_UpdateWindowSize(const til::size sizeNew) // an additional Buffer message with the same size again and do nothing special. ScreenBufferSizeChange(siAttached.GetActiveBuffer().GetBufferSize().Dimensions()); - siAttached.ResizingWindow--; + _resizingWindow--; } LOG_IF_FAILED(ConsoleImeResizeCompStrView()); @@ -879,26 +879,29 @@ void Window::HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteCha LOG_IF_FAILED(ScreenInfo.SetViewportOrigin(true, NewOrigin, false)); } -BOOL Window::EnableBothScrollBars() +void Window::UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state) { - return EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); -} + // If this is the main buffer, make sure we enable both of the scroll bars. + // The alt buffer likely disabled the scroll bars, this is the only way to re-enable it. + if (!state.isAltBuffer) + { + EnableScrollBar(_hWnd, SB_BOTH, ESB_ENABLE_BOTH); + } -int Window::UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition) -{ - SCROLLINFO si; - si.cbSize = sizeof(si); - si.fMask = isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL; - si.nPage = pageSize; - si.nMin = 0; - si.nMax = maxSize; - si.nPos = viewportPosition; - - return SetScrollInfo(_hWnd, isVertical ? SB_VERT : SB_HORZ, &si, TRUE); + SCROLLINFO si{ + .cbSize = sizeof(SCROLLINFO), + .fMask = static_cast(state.isAltBuffer ? SIF_ALL | SIF_DISABLENOSCROLL : SIF_ALL), + }; + + si.nMax = state.maxSize.width; + si.nPage = state.viewport.width(); + si.nPos = state.viewport.left; + SetScrollInfo(_hWnd, SB_HORZ, &si, TRUE); + + si.nMax = state.maxSize.height; + si.nPage = state.viewport.height(); + si.nPos = state.viewport.top; + SetScrollInfo(_hWnd, SB_VERT, &si, TRUE); } // Routine Description: diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index 45db4e7fbe6..7550b18c9d8 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -65,12 +65,7 @@ namespace Microsoft::Console::Interactivity::Win32 void HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange); - BOOL EnableBothScrollBars(); - int UpdateScrollBar(bool isVertical, - bool isAltBuffer, - UINT pageSize, - int maxSize, - int viewportPosition); + void UpdateScrollBars(const SCREEN_INFORMATION::ScrollBarState& state); void UpdateWindowSize(const til::size coordSizeInChars); void UpdateWindowPosition(_In_ const til::point ptNewPos) const; @@ -185,6 +180,7 @@ namespace Microsoft::Console::Interactivity::Win32 static void s_ReinitializeFontsForDPIChange(); + WORD _resizingWindow = 0; // > 0 if we should ignore WM_SIZE messages bool _fInDPIChange = false; static void s_ConvertWindowPosToWindowRect(const LPWINDOWPOS lpWindowPos, diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 376e93fe031..7367c8a7c45 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -681,7 +681,16 @@ using namespace Microsoft::Console::Types; case CM_UPDATE_SCROLL_BARS: { - ScreenInfo.InternalUpdateScrollBars(); + const auto state = ScreenInfo.FetchScrollBarState(); + + // EnableScrollbar() and especially SetScrollInfo() are prohibitively expensive functions nowadays. + // Unlocking early here improves throughput of good old `type` in cmd.exe by ~10x. + UnlockConsole(); + Unlock = FALSE; + + _resizingWindow++; + UpdateScrollBars(state); + _resizingWindow--; break; } @@ -792,7 +801,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam) // CONSOLE_IS_ICONIC bit appropriately. doing so in the WM_SIZE handler is incorrect because the WM_SIZE // comes after the WM_ERASEBKGND during SetWindowPos() processing, and the WM_ERASEBKGND needs to know if // the console window is iconic or not. - if (!ScreenInfo.ResizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) + if (!_resizingWindow && (lpWindowPos->cx || lpWindowPos->cy) && !IsIconic(hWnd)) { // calculate the dimensions for the newly proposed window rectangle til::rect rcNew; From bc48eda0223a63a2645f846db831613d107af0d8 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 29 Jan 2024 23:49:42 +0100 Subject: [PATCH 04/50] Reset _wrapForced when erasing scrollback (#16610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #15541 changed `AdaptDispatch::_FillRect` which caused it to not affect the `ROW::_wrapForced` flag anymore. This change in behavior was not noticeable as `TextBuffer::GetLastNonSpaceCharacter` had a bug where rows of only whitespace text would always be treated as empty. This would then affect `AdaptDispatch::_EraseAll` to accidentally correctly guess the last row with text despite the `_FillRect` change. #15701 then fixed `GetLastNonSpaceCharacter` indirectly by fixing `ROW::MeasureRight` which now made the previous change apparent. `_EraseAll` would now guess the last row of text incorrectly, because it would find the rows that `_FillRect` cleared but still had `_wrapForced` set to `true`. This PR fixes the issue by replacing the `_FillRect` usage to clear rows with direct calls to `ROW::Reset()`. In the future this could be extended by also `MEM_DECOMMIT`ing the now unused underlying memory. Closes #16603 ## Validation Steps Performed * Enter WSL and resize the window to <40 columns * Execute ```sh cd /bin ls -la printf "\e[3J" ls -la printf "\e[3J" printf "\e[2J" ``` * Only one viewport-height-many lines of whitespace exist between the current prompt line and the previous scrollback contents ✅ (cherry picked from commit 5f71cf3e94c35fb052632aa8368f01cae77b89c6) Service-Card-Id: 91707937 Service-Version: 1.19 --- src/buffer/out/textBuffer.cpp | 57 +++++++++++++++++++++----- src/buffer/out/textBuffer.hpp | 1 + src/host/ut_host/ScreenBufferTests.cpp | 3 +- src/terminal/adapter/adaptDispatch.cpp | 13 +----- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 52a5c6e3b72..600f1b182ff 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -126,6 +126,8 @@ void TextBuffer::_reserve(til::size screenBufferSize, const TextAttribute& defau // The compiler doesn't understand the likelihood of our branches. (PGO does, but that's imperfect.) __declspec(noinline) void TextBuffer::_commit(const std::byte* row) { + assert(row >= _commitWatermark); + const auto rowEnd = row + _bufferRowStride; const auto remaining = gsl::narrow_cast(_bufferEnd - _commitWatermark); const auto minimum = gsl::narrow_cast(rowEnd - _commitWatermark); @@ -146,7 +148,7 @@ void TextBuffer::_decommit() noexcept _commitWatermark = _buffer.get(); } -// Constructs ROWs up to (excluding) the ROW pointed to by `until`. +// Constructs ROWs between [_commitWatermark,until). void TextBuffer::_construct(const std::byte* until) noexcept { for (; _commitWatermark < until; _commitWatermark += _bufferRowStride) @@ -158,8 +160,7 @@ void TextBuffer::_construct(const std::byte* until) noexcept } } -// Destroys all previously constructed ROWs. -// Be careful! This doesn't reset any of the members, in particular the _commitWatermark. +// Destructs ROWs between [_buffer,_commitWatermark). void TextBuffer::_destroy() const noexcept { for (auto it = _buffer.get(); it < _commitWatermark; it += _bufferRowStride) @@ -168,9 +169,8 @@ void TextBuffer::_destroy() const noexcept } } -// This function is "direct" because it trusts the caller to properly wrap the "offset" -// parameter modulo the _height of the buffer, etc. But keep in mind that a offset=0 -// is the GetScratchpadRow() and not the GetRowByOffset(0). That one is offset=1. +// This function is "direct" because it trusts the caller to properly +// wrap the "offset" parameter modulo the _height of the buffer. ROW& TextBuffer::_getRowByOffsetDirect(size_t offset) { const auto row = _buffer.get() + _bufferRowStride * offset; @@ -184,6 +184,7 @@ ROW& TextBuffer::_getRowByOffsetDirect(size_t offset) return *reinterpret_cast(row); } +// See GetRowByOffset(). ROW& TextBuffer::_getRow(til::CoordType y) const { // Rows are stored circularly, so the index you ask for is offset by the start position and mod the total of rows. @@ -197,6 +198,7 @@ ROW& TextBuffer::_getRow(til::CoordType y) const } // We add 1 to the row offset, because row "0" is the one returned by GetScratchpadRow(). + // See GetScratchpadRow() for more explanation. #pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). return const_cast(this)->_getRowByOffsetDirect(gsl::narrow_cast(offset) + 1); } @@ -238,6 +240,9 @@ ROW& TextBuffer::GetScratchpadRow() // Returns a row filled with whitespace and the given attributes, for you to freely use. ROW& TextBuffer::GetScratchpadRow(const TextAttribute& attributes) { + // The scratchpad row is mapped to the underlying index 0, whereas all regular rows are mapped to + // index 1 and up. We do it this way instead of the other way around (scratchpad row at index _height), + // because that would force us to MEM_COMMIT the entire buffer whenever this function is called. auto& r = _getRowByOffsetDirect(0); r.Reset(attributes); return r; @@ -902,15 +907,14 @@ til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) co // If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text. const auto viewportTop = viewport.Top(); - auto fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); // this row is empty, and we're not at the top - while (fDoBackUp) + + // while (this row is empty, and we're not at the top) + while (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop) { coordEndOfText.y--; const auto& backupRow = GetRowByOffset(coordEndOfText.y); // We need to back up to the previous row if this line is empty, AND there are more rows - coordEndOfText.x = backupRow.MeasureRight() - 1; - fDoBackUp = (coordEndOfText.x < 0 && coordEndOfText.y > viewportTop); } // don't allow negative results @@ -1146,6 +1150,39 @@ void TextBuffer::Reset() noexcept _initialAttributes = _currentAttributes; } +void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordType height) +{ + if (start <= 0) + { + return; + } + + if (height <= 0) + { + _decommit(); + return; + } + + // Our goal is to move the viewport to the absolute start of the underlying memory buffer so that we can + // MEM_DECOMMIT the remaining memory. _firstRow is used to make the TextBuffer behave like a circular buffer. + // The start parameter is relative to the _firstRow. The trick to get the content to the absolute start + // is to simply add _firstRow ourselves and then reset it to 0. This causes ScrollRows() to write into + // the absolute start while reading from relative coordinates. This works because GetRowByOffset() + // operates modulo the buffer height and so the possibly-too-large startAbsolute won't be an issue. + const auto startAbsolute = _firstRow + start; + _firstRow = 0; + ScrollRows(startAbsolute, height, -startAbsolute); + + const auto end = _estimateOffsetOfLastCommittedRow(); + for (auto y = height; y <= end; ++y) + { + GetMutableRowByOffset(y).Reset(_initialAttributes); + } + + ScrollMarks(-start); + ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height }); +} + // Routine Description: // - This is the legacy screen resize with minimal changes // Arguments: diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 8ba16f97e75..39cc9fe9a59 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -194,6 +194,7 @@ class TextBuffer final til::point BufferToScreenPosition(const til::point position) const; void Reset() noexcept; + void ClearScrollback(const til::CoordType start, const til::CoordType height); void ResizeTraditional(const til::size newSize); diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index d6687e19bd6..98a6f09b2e5 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -4515,6 +4515,7 @@ void ScreenBufferTests::EraseScrollbackTests() auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); auto& stateMachine = si.GetStateMachine(); const auto& cursor = si.GetTextBuffer().GetCursor(); + const auto initialAttributes = si.GetAttributes(); WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); const auto bufferWidth = si.GetBufferSize().Width(); @@ -4571,7 +4572,7 @@ void ScreenBufferTests::EraseScrollbackTests() } Log::Comment(L"The rest of the buffer should be cleared with default attributes."); - VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', TextAttribute{})); + VERIFY_IS_TRUE(_ValidateLinesContain(viewportLine, bufferHeight, L' ', initialAttributes)); } void ScreenBufferTests::EraseTests() diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index b6e07b77ffb..fb504c20200 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3200,18 +3200,7 @@ bool AdaptDispatch::_EraseScrollback() auto& cursor = textBuffer.GetCursor(); const auto row = cursor.GetPosition().y; - // Clear all the marks below the new viewport position. - textBuffer.ClearMarksInRange(til::point{ 0, height }, - til::point{ bufferSize.width, bufferSize.height }); - // Then scroll all the remaining marks up. This will trim ones that are now "outside" the buffer - textBuffer.ScrollMarks(-top); - - // Scroll the viewport content to the top of the buffer. - textBuffer.ScrollRows(top, height, -top); - // Clear everything after the viewport. - _FillRect(textBuffer, { 0, height, bufferSize.width, bufferSize.height }, whitespace, {}); - // Also reset the line rendition for all of the cleared rows. - textBuffer.ResetLineRenditionRange(height, bufferSize.height); + textBuffer.ClearScrollback(top, height); // Move the viewport _api.SetViewportPosition({ viewport.left, 0 }); // Move the cursor to the same relative location. From add1632d633ad6721e02276d27bcf89030635df4 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 30 Jan 2024 16:52:11 +0100 Subject: [PATCH 05/50] Fix conhost clipboard handling bugs (#16618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit conhost has 2 bugs related to clipboard handling: * Missing retry on `OpenClipboard`: When copying to the clipboard explorer.exe is very eager to open the clipboard and peek into it. I'm not sure why it happens, but I can see `CFSDropTarget` in the call stack. It uses COM RPC and so this takes ~20ms every time. That breaks conhost's clipboard randomly during `ConsoleBench`. During non-benchmarks I expect this to break during RDP. * Missing null-terminator check during paste: `CF_UNICODETEXT` is documented to be a null-terminated string, which conhost v2 failed to handle as it relied entirely on `GlobalSize`. Additionally, this changeset simplifies the `HGLOBAL` code slightly by adding `_copyToClipboard` to abstract it away. * `ConsoleBench` (#16453) doesn't fail randomly anymore ✅ (cherry picked from commit 86c30bd) --- src/interactivity/win32/Clipboard.cpp | 190 ++++++++++++-------------- src/interactivity/win32/clipboard.hpp | 8 +- 2 files changed, 90 insertions(+), 108 deletions(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 684eb6d70a1..f8960792b39 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -52,33 +52,36 @@ contents and writing them to the console's input buffer --*/ void Clipboard::Paste() { - HANDLE ClipboardDataHandle; - - // Clear any selection or scrolling that may be active. - Selection::Instance().ClearSelection(); - Scrolling::s_ClearScroll(); - - // Get paste data from clipboard - if (!OpenClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle())) + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) { + LOG_LAST_ERROR(); return; } - ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT); - if (ClipboardDataHandle == nullptr) + const auto handle = GetClipboardData(CF_UNICODETEXT); + if (!handle) { - CloseClipboard(); return; } - auto pwstr = (PWCHAR)GlobalLock(ClipboardDataHandle); - StringPaste(pwstr, (ULONG)GlobalSize(ClipboardDataHandle) / sizeof(WCHAR)); - - // WIP auditing if user is enrolled + // Clear any selection or scrolling that may be active. + Selection::Instance().ClearSelection(); + Scrolling::s_ClearScroll(); - GlobalUnlock(ClipboardDataHandle); + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return; + } - CloseClipboard(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> Use wcsnlen() to determine the actual length. + // NOTE: Some applications don't add a trailing null character. This includes past conhost versions. + const auto maxLen = GlobalSize(handle) / sizeof(WCHAR); + StringPaste(str, wcsnlen(str, maxLen)); } Clipboard& Clipboard::Instance() @@ -121,6 +124,52 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData, #pragma region Private Methods +Clipboard::unique_close_clipboard_call Clipboard::_openClipboard(HWND hwnd) +{ + bool success = false; + + // OpenClipboard may fail to acquire the internal lock --> retry. + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + success = true; + break; + } + // 10 iterations + if (sleep > 10000) + { + break; + } + Sleep(sleep); + } + + return Clipboard::unique_close_clipboard_call{ success }; +} + +void Clipboard::_copyToClipboard(const UINT format, const void* src, const size_t bytes) +{ + wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) }; + + const auto locked = GlobalLock(handle.get()); + memcpy(locked, src, bytes); + GlobalUnlock(handle.get()); + + THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get())); + handle.release(); +} + +void Clipboard::_copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes) +{ + const auto id = RegisterClipboardFormatW(format); + if (!id) + { + LOG_LAST_ERROR(); + return; + } + _copyToClipboard(id, src, bytes); +} + // Routine Description: // - converts a wchar_t* into a series of KeyEvents as if it was typed // from the keyboard @@ -242,108 +291,39 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting) includeCRLF = trimTrailingWhitespace = true; } - const auto text = buffer.GetText(includeCRLF, + const auto rows = buffer.GetText(includeCRLF, trimTrailingWhitespace, selectionRects, GetAttributeColors, !selection.IsLineSelection()); - CopyTextToSystemClipboard(text, copyFormatting); -} - -// Routine Description: -// - Copies the text given onto the global system clipboard. -// Arguments: -// - rows - Rows of text data to copy -// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise -void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, const bool fAlsoCopyFormatting) -{ - std::wstring finalString; - - // Concatenate strings into one giant string to put onto the clipboard. + std::wstring text; for (const auto& str : rows.text) { - finalString += str; + text += str; } - // allocate the final clipboard data - const auto cchNeeded = finalString.size() + 1; - const auto cbNeeded = sizeof(wchar_t) * cchNeeded; - wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded)); - THROW_LAST_ERROR_IF_NULL(globalHandle.get()); - - auto pwszClipboard = (PWSTR)GlobalLock(globalHandle.get()); - THROW_LAST_ERROR_IF_NULL(pwszClipboard); - - // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. - // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const auto hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data()); - GlobalUnlock(globalHandle.get()); - THROW_IF_FAILED(hr); - - // Set global data to clipboard - THROW_LAST_ERROR_IF(!OpenClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle())); - - { // Clipboard Scope - auto clipboardCloser = wil::scope_exit([]() { - THROW_LAST_ERROR_IF(!CloseClipboard()); - }); - - THROW_LAST_ERROR_IF(!EmptyClipboard()); - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get())); - - if (fAlsoCopyFormatting) - { - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - const auto& fontData = gci.GetActiveOutputBuffer().GetCurrentFont(); - const auto iFontHeightPoints = fontData.GetUnscaledSize().height * 72 / ServiceLocator::LocateGlobals().dpi; - const auto bgColor = gci.GetRenderSettings().GetAttributeColors({}).second; - - auto HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format"); + std::string htmlData, rtfData; + if (copyFormatting) + { + const auto& fontData = gci.GetActiveOutputBuffer().GetCurrentFont(); + const auto iFontHeightPoints = fontData.GetUnscaledSize().height * 72 / ServiceLocator::LocateGlobals().dpi; + const auto bgColor = gci.GetRenderSettings().GetAttributeColors({}).second; - auto RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); - CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format"); - } + htmlData = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); + rtfData = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); } - // only free if we failed. - // the memory has to remain allocated if we successfully placed it on the clipboard. - // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandle.release(); -} + EmptyClipboard(); + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> We add +1 to the length. This works because .c_str() is null-terminated. + _copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t)); -// Routine Description: -// - Copies the given string onto the global system clipboard in the specified format -// Arguments: -// - stringToCopy - The string to copy -// - lpszFormat - the name of the format -void Clipboard::CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) -{ - const auto cbData = stringToCopy.size() + 1; // +1 for '\0' - if (cbData) + if (copyFormatting) { - wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData)); - THROW_LAST_ERROR_IF_NULL(globalHandleData.get()); - - auto pszClipboardHTML = (PSTR)GlobalLock(globalHandleData.get()); - THROW_LAST_ERROR_IF_NULL(pszClipboardHTML); - - // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. - // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const auto hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data()); - GlobalUnlock(globalHandleData.get()); - THROW_IF_FAILED(hr2); - - const auto CF_FORMAT = RegisterClipboardFormatW(lpszFormat); - THROW_LAST_ERROR_IF(0 == CF_FORMAT); - - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get())); - - // only free if we failed. - // the memory has to remain allocated if we successfully placed it on the clipboard. - // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandleData.release(); + _copyToClipboardRegisteredFormat(L"HTML Format", htmlData.data(), htmlData.size()); + _copyToClipboardRegisteredFormat(L"Rich Text Format", rtfData.data(), rtfData.size()); } } diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index f6bab9254cf..72ee1544246 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -35,15 +35,17 @@ namespace Microsoft::Console::Interactivity::Win32 void Paste(); private: + using unique_close_clipboard_call = wil::unique_call; + static unique_close_clipboard_call _openClipboard(HWND hwnd); + static void _copyToClipboard(UINT format, const void* src, size_t bytes); + static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes); + InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, const size_t cchData, const bool bracketedPaste = false); void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting); - void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ const bool copyFormatting); - void CopyToSystemClipboard(std::string stringToPlaceOnClip, LPCWSTR lpszFormat); - bool FilterCharacterOnPaste(_Inout_ WCHAR* const pwch); #ifdef UNIT_TESTING From 26d35c3ac55caee801584658d1bc56711b5ae596 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 30 Jan 2024 17:34:27 -0600 Subject: [PATCH 06/50] build: remove symbols' dependency on the Package phase (#16625) Due to things outside our control, sometimes the Package phase fails when VPack publication is enabled. Because of this, symbols won't be published. We still want these builds to be considered "golden" and we are still shipping them, so we *must* publish symbols. (cherry picked from commit bcca7aac1be1672a4a28814a3543fe9892910c89) Service-Card-Id: 91719595 Service-Version: 1.19 --- .../templates-v2/pipeline-onebranch-full-release-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index a466e987f08..ab0f5e2dc00 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -239,7 +239,7 @@ extends: - stage: Publish displayName: Publish - dependsOn: [Build, Package] + dependsOn: [Build] jobs: - template: ./build/pipelines/templates-v2/job-publish-symbols.yml@self parameters: From 01868978b38482fe87c795302c458bc84caca850 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 31 Jan 2024 01:16:24 +0100 Subject: [PATCH 07/50] Fix a bug caused by #16592 (#16624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #16592 passes the return value of `GetEnvironmentStringsW` directly to the `hstring` constructor even though the former returns a double-null terminated string and the latter expects a regular one. This PR fixes the issue by using a basic strlen() loop to compute the length ourselves. It's still theoretically beneficial over the previous code, but now it's rather bitter since the code isn't particularly short anymore and so the biggest benefit is gone. Closes #16623 ## Validation Steps Performed * Validated the `env` string in a debugger ✅ It's 1 character shorter than the old `til::env` string. That's fine however, since any `HSTRING` is always null-terminated anyways and so we get an extra null-terminator for free. * `wt powershell` works ✅ (cherry picked from commit c669afe2a06c6db9a103e81a48cdc5b040d6fcff) Service-Card-Id: 91719862 Service-Version: 1.19 --- src/cascadia/WindowsTerminal/WindowEmperor.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index e81ccf049b8..3ff3f06f546 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -74,7 +74,23 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow) } } - const Remoting::CommandlineArgs eventArgs{ args, cwd, gsl::narrow_cast(nCmdShow), GetEnvironmentStringsW() }; + // GetEnvironmentStringsW() returns a double-null terminated string. + // The hstring(wchar_t*) constructor however only works for regular null-terminated strings. + // Due to that we need to manually search for the terminator. + winrt::hstring env; + { + const wil::unique_environstrings_ptr strings{ GetEnvironmentStringsW() }; + const auto beg = strings.get(); + auto end = beg; + + for (; *end; end += wcsnlen(end, SIZE_T_MAX) + 1) + { + } + + env = winrt::hstring{ beg, gsl::narrow(end - beg) }; + } + + const Remoting::CommandlineArgs eventArgs{ args, cwd, gsl::narrow_cast(nCmdShow), std::move(env) }; const auto isolatedMode{ _app.Logic().IsolatedMode() }; const auto result = _manager.ProposeCommandline(eventArgs, isolatedMode); int exitCode = 0; From 438571bd7fc032d06aaa58f476929818ed466248 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 2 Feb 2024 00:49:57 +0100 Subject: [PATCH 08/50] Restore support for pasting files (#16634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TIL: You could Ctrl+V files into Windows Terminal and here I am, always opening the context menu and selecting "Copy as path"... smh This restores the support by adding a very rudimentary HDROP handler. The flip side of the regression is that I learned about this and so conhost also gets this now, because why not! Closes #16627 * Single files can be pasted in WT and conhost ✅ (cherry picked from commit ef96e225da6b0df496390eed9fe31dc7e434a939) Service-Card-Id: 91727725 Service-Version: 1.19 --- src/cascadia/TerminalApp/TerminalPage.cpp | 114 ++++++++++++++-------- src/interactivity/win32/Clipboard.cpp | 78 ++++++++++++--- src/interactivity/win32/clipboard.hpp | 4 +- src/interactivity/win32/windowproc.cpp | 25 +---- 4 files changed, 138 insertions(+), 83 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index d8673377ccf..25098213491 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -2631,6 +2631,75 @@ namespace winrt::TerminalApp::implementation CATCH_LOG(); } + static wil::unique_close_clipboard_call _openClipboard(HWND hwnd) + { + bool success = false; + + // OpenClipboard may fail to acquire the internal lock --> retry. + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + success = true; + break; + } + // 10 iterations + if (sleep > 10000) + { + break; + } + Sleep(sleep); + } + + return wil::unique_close_clipboard_call{ success }; + } + + static winrt::hstring _extractClipboard() + { + // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. + if (const auto handle = GetClipboardData(CF_UNICODETEXT)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return {}; + } + + const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); + const auto len = wcsnlen(str, maxLen); + return winrt::hstring{ str, gsl::narrow_cast(len) }; + } + + // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). + if (const auto handle = GetClipboardData(CF_HDROP)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto drop = static_cast(lock.get()); + if (!drop) + { + return {}; + } + + const auto cap = DragQueryFileW(drop, 0, nullptr, 0); + if (cap == 0) + { + return {}; + } + + auto buffer = winrt::impl::hstring_builder{ cap }; + const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1); + if (len == 0) + { + return {}; + } + + return buffer.to_hstring(); + } + + return {}; + } + // Function Description: // - This function is called when the `TermControl` requests that we send // it the clipboard's content. @@ -2650,53 +2719,14 @@ namespace winrt::TerminalApp::implementation const auto weakThis = get_weak(); const auto dispatcher = Dispatcher(); const auto globalSettings = _settings.GlobalSettings(); - winrt::hstring text; // GetClipboardData might block for up to 30s for delay-rendered contents. co_await winrt::resume_background(); + winrt::hstring text; + if (const auto clipboard = _openClipboard(nullptr)) { - // According to various reports on the internet, OpenClipboard might - // fail to acquire the internal lock, for instance due to rdpclip.exe. - for (int attempts = 1;;) - { - if (OpenClipboard(nullptr)) - { - break; - } - - if (attempts > 5) - { - co_return; - } - - attempts++; - Sleep(10 * attempts); - } - - const auto clipboardCleanup = wil::scope_exit([]() { - CloseClipboard(); - }); - - const auto data = GetClipboardData(CF_UNICODETEXT); - if (!data) - { - co_return; - } - - const auto str = static_cast(GlobalLock(data)); - if (!str) - { - co_return; - } - - const auto dataCleanup = wil::scope_exit([&]() { - GlobalUnlock(data); - }); - - const auto maxLength = GlobalSize(data) / sizeof(wchar_t); - const auto length = wcsnlen(str, maxLength); - text = winrt::hstring{ str, gsl::narrow_cast(length) }; + text = _extractClipboard(); } if (globalSettings.TrimPaste()) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index f8960792b39..6d938166089 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -59,29 +59,73 @@ void Clipboard::Paste() return; } - const auto handle = GetClipboardData(CF_UNICODETEXT); - if (!handle) + // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. + if (const auto handle = GetClipboardData(CF_UNICODETEXT)) { - return; + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return; + } + + // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + // CF_UNICODETEXT: [...] A null character signals the end of the data. + // --> Use wcsnlen() to determine the actual length. + // NOTE: Some applications don't add a trailing null character. This includes past conhost versions. + const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); + StringPaste(str, wcsnlen(str, maxLen)); } - // Clear any selection or scrolling that may be active. - Selection::Instance().ClearSelection(); - Scrolling::s_ClearScroll(); + // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). + if (const auto handle = GetClipboardData(CF_HDROP)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto drop = static_cast(lock.get()); + if (!drop) + { + return; + } - const wil::unique_hglobal_locked lock{ handle }; - const auto str = static_cast(lock.get()); - if (!str) + PasteDrop(drop); + } +} + +void Clipboard::PasteDrop(HDROP drop) +{ + // NOTE: When asking DragQueryFileW for the required capacity it returns a length without trailing \0, + // but then expects a capacity that includes it. If you don't make space for a trailing \0 + // then it will silently (!) cut off the end of the string. A somewhat disappointing API design. + const auto expectedLength = DragQueryFileW(drop, 0, nullptr, 0); + if (expectedLength == 0) { return; } - // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats - // CF_UNICODETEXT: [...] A null character signals the end of the data. - // --> Use wcsnlen() to determine the actual length. - // NOTE: Some applications don't add a trailing null character. This includes past conhost versions. - const auto maxLen = GlobalSize(handle) / sizeof(WCHAR); - StringPaste(str, wcsnlen(str, maxLen)); + // If the path contains spaces, we'll wrap it in quotes and so this allocates +2 characters ahead of time. + // We'll first make DragQueryFileW copy its contents in the middle and then check if that contains spaces. + // If it does, only then we'll add the quotes at the start and end. + // This is preferable over calling StringPaste 3x (an alternative, simpler approach), + // because the pasted content should be treated as a single atomic unit by the InputBuffer. + const auto buffer = std::make_unique_for_overwrite(expectedLength + 2); + auto str = buffer.get() + 1; + size_t len = expectedLength; + + const auto actualLength = DragQueryFileW(drop, 0, str, expectedLength + 1); + if (actualLength != expectedLength) + { + return; + } + + if (wmemchr(str, L' ', len)) + { + str = buffer.get(); + len += 2; + til::at(str, 0) = L'"'; + til::at(str, len - 1) = L'"'; + } + + StringPaste(str, len); } Clipboard& Clipboard::Instance() @@ -109,6 +153,10 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData, try { + // Clear any selection or scrolling that may be active. + Selection::Instance().ClearSelection(); + Scrolling::s_ClearScroll(); + const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode(); const auto bracketedPasteMode = gci.GetBracketedPasteMode(); auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode); diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index 72ee1544246..0cee4a7b5d0 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -30,9 +30,8 @@ namespace Microsoft::Console::Interactivity::Win32 static Clipboard& Instance(); void Copy(_In_ const bool fAlsoCopyFormatting = false); - void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, - const size_t cchData); void Paste(); + void PasteDrop(HDROP drop); private: using unique_close_clipboard_call = wil::unique_call; @@ -40,6 +39,7 @@ namespace Microsoft::Console::Interactivity::Win32 static void _copyToClipboard(UINT format, const void* src, size_t bytes); static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes); + void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, const size_t cchData); InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, const size_t cchData, const bool bracketedPaste = false); diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 7367c8a7c45..eacc99128ca 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -869,30 +869,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam) // - void Window::_HandleDrop(const WPARAM wParam) const { - WCHAR szPath[MAX_PATH]; - BOOL fAddQuotes; - - if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0) - { - // Log a telemetry flag saying the user interacted with the Console - // Only log when DragQueryFile succeeds, because if we don't when the console starts up, we're seeing - // _HandleDrop get called multiple times (and DragQueryFile fail), - // which can incorrectly mark this console session as interactive. - Telemetry::Instance().SetUserInteractive(); - - fAddQuotes = (wcschr(szPath, L' ') != nullptr); - if (fAddQuotes) - { - Clipboard::Instance().StringPaste(L"\"", 1); - } - - Clipboard::Instance().StringPaste(szPath, wcslen(szPath)); - - if (fAddQuotes) - { - Clipboard::Instance().StringPaste(L"\"", 1); - } - } + Clipboard::Instance().PasteDrop((HDROP)wParam); } [[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam) From bef234081a7d5e5b35988b704d20240c4a3b7937 Mon Sep 17 00:00:00 2001 From: e82eric Date: Fri, 9 Feb 2024 15:30:02 -0500 Subject: [PATCH 09/50] Fix for search selections highlight going backward when the selection wraps to next line (#16691) Hi, I realized I had a bug in my pull request for search selections where when the highlight is wrapped it causes the selection to select back to start of the current line instead of wrapping to the next line. PR where I introduced this bug: #16227 --- src/cascadia/TerminalCore/TerminalSelection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalCore/TerminalSelection.cpp b/src/cascadia/TerminalCore/TerminalSelection.cpp index fd440470f86..ae1db3507b4 100644 --- a/src/cascadia/TerminalCore/TerminalSelection.cpp +++ b/src/cascadia/TerminalCore/TerminalSelection.cpp @@ -83,7 +83,7 @@ std::vector Terminal::_GetSearchSelectionRects(Microsoft::C for (auto selection = lowerIt; selection != upperIt; ++selection) { const auto start = til::point{ selection->left, selection->top }; - const auto end = til::point{ selection->right, selection->top }; + const auto end = til::point{ selection->right, selection->bottom }; const auto adj = _activeBuffer().GetTextRects(start, end, _blockSelection, false); for (auto a : adj) { From d3a18b904123b95a7b5a1ea2307f064cf3e28c94 Mon Sep 17 00:00:00 2001 From: Sarim Khan Date: Sat, 10 Feb 2024 02:31:29 +0600 Subject: [PATCH 10/50] schema: add experimental.repositionCursorWithMouse (#16652) Add experimental.repositionCursorWithMouse to profiles.schema.json. So when editing settings.json with vscode, it autocompletes and don't complain. --- doc/cascadia/profiles.schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 0bc403498cf..d82242b55b8 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2782,6 +2782,11 @@ "description": "When set to true, marks added to the buffer via the addMark action will appear on the scrollbar.", "type": "boolean" }, + "experimental.repositionCursorWithMouse": { + "default": false, + "description": "When set to true, you can move the text cursor by clicking with the mouse on the current commandline. This is an experimental feature - there are lots of edge cases where this will not work as expected.", + "type": "boolean" + }, "experimental.pixelShaderPath": { "description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.", "type": "string" From f30cbef34df5624173a45bdb53667a2cf145f4a9 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Tue, 13 Feb 2024 11:25:09 -0800 Subject: [PATCH 11/50] Implement MVVM for the Actions page (#14292) ## Summary of the Pull Request Implements an `ActionsViewModel` for the `Actions` page in the SUI ## References ## PR Checklist * [ ] Closes #xxx * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [x] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx ## Detailed Description of the Pull Request / Additional comments A few annoyances: - Because of the shifts in the UI when switching between edit mode/regular mode on the `KeyBindingViewModel`s, the page used to manually handle moving focus accordingly so that focus does not get lost. However, the page no longer owns the `KeyBindingViewModel`, instead the `ActionsViewModel` owns them but the `ActionsViewModel` cannot manually move focus around since it does not own the UI Element. So, the `ActionsViewModel` emits an event for the page to catch that tells the page to move focus (`FocusContainer`). - Similarly, the page used to manually update the `ContainerBackground` of the `KeyBindingViewModel` when the kbdVM enters `EditMode`. The `ActionsViewModel` does not have access to the page's resources though (to determine the correct background brush to use). So, the `ActionsViewModel` emits another event for the page to catch (`UpdateBackground`). ## Validation Steps Performed Actions page still works as before --------- Co-authored-by: Dustin L. Howett --- .../TerminalSettingsEditor/Actions.cpp | 376 ++--------------- src/cascadia/TerminalSettingsEditor/Actions.h | 116 +----- .../TerminalSettingsEditor/Actions.idl | 54 +-- .../TerminalSettingsEditor/Actions.xaml | 2 +- .../ActionsViewModel.cpp | 380 ++++++++++++++++++ .../TerminalSettingsEditor/ActionsViewModel.h | 131 ++++++ .../ActionsViewModel.idl | 60 +++ .../TerminalSettingsEditor/MainPage.cpp | 2 +- ...Microsoft.Terminal.Settings.Editor.vcxproj | 9 + ...t.Terminal.Settings.Editor.vcxproj.filters | 1 + 10 files changed, 618 insertions(+), 513 deletions(-) create mode 100644 src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/ActionsViewModel.h create mode 100644 src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index 8e3a06a071d..dafc7b51da2 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -4,108 +4,14 @@ #include "pch.h" #include "Actions.h" #include "Actions.g.cpp" -#include "KeyBindingViewModel.g.cpp" -#include "ActionsPageNavigationState.g.cpp" #include "LibraryResources.h" #include "../TerminalSettingsModel/AllShortcutActions.h" -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Windows::System; -using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Xaml::Controls; -using namespace winrt::Windows::UI::Xaml::Data; using namespace winrt::Windows::UI::Xaml::Navigation; -using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - KeyBindingViewModel::KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector& availableActions) : - KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {} - - KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector& availableActions) : - _CurrentKeys{ keys }, - _KeyChordText{ KeyChordSerialization::ToString(keys) }, - _CurrentAction{ actionName }, - _ProposedAction{ box_value(actionName) }, - _AvailableActions{ availableActions } - { - // Add a property changed handler to our own property changed event. - // This propagates changes from the settings model to anybody listening to our - // unique view model members. - PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { - const auto viewModelProperty{ args.PropertyName() }; - if (viewModelProperty == L"CurrentKeys") - { - _KeyChordText = KeyChordSerialization::ToString(_CurrentKeys); - _NotifyChanges(L"KeyChordText"); - } - else if (viewModelProperty == L"IsContainerFocused" || - viewModelProperty == L"IsEditButtonFocused" || - viewModelProperty == L"IsHovered" || - viewModelProperty == L"IsAutomationPeerAttached" || - viewModelProperty == L"IsInEditMode") - { - _NotifyChanges(L"ShowEditButton"); - } - else if (viewModelProperty == L"CurrentAction") - { - _NotifyChanges(L"Name"); - } - }); - } - - hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - - bool KeyBindingViewModel::ShowEditButton() const noexcept - { - return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode(); - } - - void KeyBindingViewModel::ToggleEditMode() - { - // toggle edit mode - IsInEditMode(!_IsInEditMode); - if (_IsInEditMode) - { - // if we're in edit mode, - // - pre-populate the text box with the current keys - // - reset the combo box with the current action - ProposedKeys(_CurrentKeys); - ProposedAction(box_value(_CurrentAction)); - } - } - - void KeyBindingViewModel::AttemptAcceptChanges() - { - AttemptAcceptChanges(_ProposedKeys); - } - - void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys) - { - const auto args{ make_self(_CurrentKeys, // OldKeys - newKeys, // NewKeys - _IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction - unbox_value(_ProposedAction)) }; // NewAction - _ModifyKeyBindingRequestedHandlers(*this, *args); - } - - void KeyBindingViewModel::CancelChanges() - { - if (_IsNewlyAdded) - { - _DeleteNewlyAddedKeyBindingHandlers(*this, nullptr); - } - else - { - ToggleEditMode(); - } - } - Actions::Actions() { InitializeComponent(); @@ -115,277 +21,55 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Automation::Peers::AutomationPeer Actions::OnCreateAutomationPeer() { - _AutomationPeerAttached = true; - for (const auto& kbdVM : _KeyBindingList) - { - // To create a more accessible experience, we want the "edit" buttons to _always_ - // appear when a screen reader is attached. This ensures that the edit buttons are - // accessible via the UIA tree. - get_self(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached); - } + _ViewModel.OnAutomationPeerAttached(); return nullptr; } void Actions::OnNavigatedTo(const NavigationEventArgs& e) { - _State = e.Parameter().as(); - - // Populate AvailableActionAndArgs - _AvailableActionMap = single_threaded_map(); - std::vector availableActionAndArgs; - for (const auto& [name, actionAndArgs] : _State.Settings().ActionMap().AvailableActions()) - { - availableActionAndArgs.push_back(name); - _AvailableActionMap.Insert(name, actionAndArgs); - } - std::sort(begin(availableActionAndArgs), end(availableActionAndArgs)); - _AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs)); + _ViewModel = e.Parameter().as(); - // Convert the key bindings from our settings into a view model representation - const auto& keyBindingMap{ _State.Settings().ActionMap().KeyBindings() }; - std::vector keyBindingList; - keyBindingList.reserve(keyBindingMap.Size()); - for (const auto& [keys, cmd] : keyBindingMap) - { - // convert the cmd into a KeyBindingViewModel - auto container{ make_self(keys, cmd.Name(), _AvailableActionAndArgs) }; - _RegisterEvents(container); - keyBindingList.push_back(*container); - } - - std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{}); - _KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList)); - } - - void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) - { - // Create the new key binding and register all of the event handlers. - auto kbdVM{ make_self(_AvailableActionAndArgs) }; - _RegisterEvents(kbdVM); - kbdVM->DeleteNewlyAddedKeyBinding({ this, &Actions::_ViewModelDeleteNewlyAddedKeyBindingHandler }); - - // Manually add the editing background. This needs to be done in Actions not the view model. - // We also have to do this manually because it hasn't been added to the list yet. - kbdVM->IsInEditMode(true); - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as() }; - kbdVM->ContainerBackground(containerBackground); - - // IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately - // by the PropertyChangedHandler below (where we delete any IsNewlyAdded items) - kbdVM->IsNewlyAdded(true); - _KeyBindingList.InsertAt(0, *kbdVM); - } - - void Actions::_ViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) - { - const auto senderVM{ sender.as() }; - const auto propertyName{ args.PropertyName() }; - if (propertyName == L"IsInEditMode") - { - if (senderVM.IsInEditMode()) + // Subscribe to the view model's FocusContainer event. + // Use the KeyBindingViewModel or index provided in the event to focus the corresponding container + _ViewModel.FocusContainer([this](const auto& /*sender*/, const auto& args) { + if (auto kbdVM{ args.try_as() }) { - // Ensure that... - // 1. we move focus to the edit mode controls - // 2. any actions that were newly added are removed - // 3. this is the only entry that is in edit mode - for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i) + if (const auto& container = KeyBindingsListView().ContainerFromItem(*kbdVM)) { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (senderVM == kbdVM) - { - // This is the view model entry that went into edit mode. - // Move focus to the edit mode controls by - // extracting the list view item container. - const auto& container{ KeyBindingsListView().ContainerFromIndex(i).try_as() }; - container.Focus(FocusState::Programmatic); - } - else if (kbdVM.IsNewlyAdded()) - { - // Remove any actions that were newly added - _KeyBindingList.RemoveAt(i); - } - else - { - // Exit edit mode for all other containers - get_self(kbdVM)->DisableEditMode(); - } + container.as().Focus(FocusState::Programmatic); } - - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as() }; - get_self(senderVM)->ContainerBackground(containerBackground); - } - else - { - // Focus on the list view item - KeyBindingsListView().ContainerFromItem(senderVM).as().Focus(FocusState::Programmatic); - - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as() }; - get_self(senderVM)->ContainerBackground(containerBackground); - } - } - } - - void Actions::_ViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys) - { - // Update the settings model - _State.Settings().ActionMap().DeleteKeyBinding(keys); - - // Find the current container in our list and remove it. - // This is much faster than rebuilding the entire ActionMap. - uint32_t index; - if (_KeyBindingList.IndexOf(senderVM, index)) - { - _KeyBindingList.RemoveAt(index); - - // Focus the new item at this index - if (_KeyBindingList.Size() != 0) - { - const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) }; - KeyBindingsListView().ContainerFromIndex(newFocusedIndex).as().Focus(FocusState::Programmatic); } - } - } - - void Actions::_ViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args) - { - const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() }; - - auto applyChangesToSettingsModel = [=]() { - // If the key chord was changed, - // update the settings model and view model appropriately - // NOTE: we still need to update the view model if we're working with a newly added action - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) + else if (const auto& index = args.try_as()) { - if (!isNewAction) + if (const auto& container = KeyBindingsListView().ContainerFromIndex(*index)) { - // update settings model - _State.Settings().ActionMap().RebindKeys(args.OldKeys(), args.NewKeys()); + container.as().Focus(FocusState::Programmatic); } - - // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentKeys(args.NewKeys()); } + }); - // If the action was changed, - // update the settings model and view model appropriately - // NOTE: no need to check for "isNewAction" here. != already. - if (args.OldActionName() != args.NewActionName()) - { - // convert the action's name into a view model. - const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) }; - - // update settings model - _State.Settings().ActionMap().RegisterKeyBinding(args.NewKeys(), newAction); - - // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentAction(args.NewActionName()); - senderVMImpl->IsNewlyAdded(false); - } - }; - - // Check for this special case: - // we're changing the key chord, - // but the new key chord is already in use - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) - { - const auto& conflictingCmd{ _State.Settings().ActionMap().GetActionByKeyChord(args.NewKeys()) }; - if (conflictingCmd) - { - // We're about to overwrite another key chord. - // Display a confirmation dialog. - TextBlock errorMessageTB{}; - errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); - - const auto conflictingCmdName{ conflictingCmd.Name() }; - TextBlock conflictingCommandNameTB{}; - conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); - conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); - - TextBlock confirmationQuestionTB{}; - confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); - - Button acceptBTN{}; - acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); - acceptBTN.Click([=](auto&, auto&) { - // remove conflicting key binding from list view - const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) }; - _KeyBindingList.RemoveAt(*containerIndex); - - // remove flyout - senderVM.AcceptChangesFlyout().Hide(); - senderVM.AcceptChangesFlyout(nullptr); - - // update settings model and view model - applyChangesToSettingsModel(); - senderVM.ToggleEditMode(); - }); - - StackPanel flyoutStack{}; - flyoutStack.Children().Append(errorMessageTB); - flyoutStack.Children().Append(conflictingCommandNameTB); - flyoutStack.Children().Append(confirmationQuestionTB); - flyoutStack.Children().Append(acceptBTN); - - Flyout acceptChangesFlyout{}; - acceptChangesFlyout.Content(flyoutStack); - senderVM.AcceptChangesFlyout(acceptChangesFlyout); - return; - } - } - - // update settings model and view model - applyChangesToSettingsModel(); - - // We NEED to toggle the edit mode here, - // so that if nothing changed, we still exit - // edit mode. - senderVM.ToggleEditMode(); - } - - void Actions::_ViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/) - { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) - { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (kbdVM == senderVM) - { - _KeyBindingList.RemoveAt(i); - return; - } - } - } - - // Method Description: - // - performs a search on KeyBindingList by key chord. - // Arguments: - // - keys - the associated key chord of the command we're looking for - // Return Value: - // - the index of the view model referencing the command. If the command doesn't exist, nullopt - std::optional Actions::_GetContainerIndexByKeyChord(const Control::KeyChord& keys) - { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) - { - const auto kbdVM{ get_self(_KeyBindingList.GetAt(i)) }; - const auto& otherKeys{ kbdVM->CurrentKeys() }; - if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey()) + // Subscribe to the view model's UpdateBackground event. + // The view model does not have access to the page resources, so it asks us + // to update the key binding's container background + _ViewModel.UpdateBackground([this](const auto& /*sender*/, const auto& args) { + if (auto kbdVM{ args.try_as() }) { - return i; + if (kbdVM->IsInEditMode()) + { + const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as() }; + kbdVM->ContainerBackground(containerBackground); + } + else + { + const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as() }; + kbdVM->ContainerBackground(containerBackground); + } } - } - - // TODO GH #6900: - // an expedited search can be done if we use cmd.Name() - // to quickly search through the sorted list. - return std::nullopt; + }); } - void Actions::_RegisterEvents(com_ptr& kbdVM) + void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) { - kbdVM->PropertyChanged({ this, &Actions::_ViewModelPropertyChangedHandler }); - kbdVM->DeleteKeyBindingRequested({ this, &Actions::_ViewModelDeleteKeyBindingHandler }); - kbdVM->ModifyKeyBindingRequested({ this, &Actions::_ViewModelModifyKeyBindingHandler }); - kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached); + _ViewModel.AddNewKeybinding(); } } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.h b/src/cascadia/TerminalSettingsEditor/Actions.h index 231d3de87d7..9ffe7bf9a05 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.h +++ b/src/cascadia/TerminalSettingsEditor/Actions.h @@ -4,109 +4,12 @@ #pragma once #include "Actions.g.h" -#include "KeyBindingViewModel.g.h" -#include "ActionsPageNavigationState.g.h" -#include "ModifyKeyBindingEventArgs.g.h" +#include "ActionsViewModel.h" #include "Utils.h" #include "ViewModelHelpers.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - struct KeyBindingViewModelComparator - { - bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const - { - return lhs.Name() < rhs.Name(); - } - }; - - struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT - { - public: - ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) : - _OldKeys{ oldKeys }, - _NewKeys{ newKeys }, - _OldActionName{ std::move(oldActionName) }, - _NewActionName{ std::move(newActionName) } {} - - WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr); - WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr); - WINRT_PROPERTY(hstring, OldActionName); - WINRT_PROPERTY(hstring, NewActionName); - }; - - struct KeyBindingViewModel : KeyBindingViewModelT, ViewModelHelper - { - public: - KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector& availableActions); - KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector& availableActions); - - hstring Name() const { return _CurrentAction; } - hstring KeyChordText() const { return _KeyChordText; } - - // UIA Text - hstring EditButtonName() const noexcept; - hstring CancelButtonName() const noexcept; - hstring AcceptButtonName() const noexcept; - hstring DeleteButtonName() const noexcept; - - void EnterHoverMode() { IsHovered(true); }; - void ExitHoverMode() { IsHovered(false); }; - void ActionGotFocus() { IsContainerFocused(true); }; - void ActionLostFocus() { IsContainerFocused(false); }; - void EditButtonGettingFocus() { IsEditButtonFocused(true); }; - void EditButtonLosingFocus() { IsEditButtonFocused(false); }; - bool ShowEditButton() const noexcept; - void ToggleEditMode(); - void DisableEditMode() { IsInEditMode(false); } - void AttemptAcceptChanges(); - void AttemptAcceptChanges(const Control::KeyChord newKeys); - void CancelChanges(); - void DeleteKeyBinding() { _DeleteKeyBindingRequestedHandlers(*this, _CurrentKeys); } - - // ProposedAction: the entry selected by the combo box; may disagree with the settings model. - // CurrentAction: the combo box item that maps to the settings model value. - // AvailableActions: the list of options in the combo box; both actions above must be in this list. - // NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place. - // Current Action serves as... - // 1 - a record of what to set ProposedAction to on a cancellation - // 2 - a form of translation between ProposedAction and the settings model - // We would also need an ActionMap reference to remove this, but this is a better separation - // of responsibilities. - VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction); - VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction); - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableActions, nullptr); - - // ProposedKeys: the keys proposed by the control; may disagree with the settings model. - // CurrentKeys: the key chord bound in the settings model. - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys); - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr); - - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr); - TYPED_EVENT(ModifyKeyBindingRequested, Editor::KeyBindingViewModel, Editor::ModifyKeyBindingEventArgs); - TYPED_EVENT(DeleteKeyBindingRequested, Editor::KeyBindingViewModel, Terminal::Control::KeyChord); - TYPED_EVENT(DeleteNewlyAddedKeyBinding, Editor::KeyBindingViewModel, IInspectable); - - private: - hstring _KeyChordText{}; - }; - - struct ActionsPageNavigationState : ActionsPageNavigationStateT - { - public: - ActionsPageNavigationState(const Model::CascadiaSettings& settings) : - _Settings{ settings } {} - - WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr) - }; - struct Actions : public HasScrollViewer, ActionsT { public: @@ -114,24 +17,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); + void AddNew_Click(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - WINRT_PROPERTY(Editor::ActionsPageNavigationState, State, nullptr); - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyBindingList); - - private: - void _ViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); - void _ViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args); - void _ViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args); - void _ViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args); - - std::optional _GetContainerIndexByKeyChord(const Control::KeyChord& keys); - void _RegisterEvents(com_ptr& kbdVM); - - bool _AutomationPeerAttached{ false }; - Windows::Foundation::Collections::IObservableVector _AvailableActionAndArgs; - Windows::Foundation::Collections::IMap _AvailableActionMap; + WINRT_OBSERVABLE_PROPERTY(Editor::ActionsViewModel, ViewModel, _PropertyChangedHandlers, nullptr); }; } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.idl b/src/cascadia/TerminalSettingsEditor/Actions.idl index 31c8570cce9..ec7444d2410 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.idl +++ b/src/cascadia/TerminalSettingsEditor/Actions.idl @@ -1,63 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "EnumEntry.idl"; +import "ActionsViewModel.idl"; namespace Microsoft.Terminal.Settings.Editor { - runtimeclass ModifyKeyBindingEventArgs - { - Microsoft.Terminal.Control.KeyChord OldKeys { get; }; - Microsoft.Terminal.Control.KeyChord NewKeys { get; }; - String OldActionName { get; }; - String NewActionName { get; }; - } - - runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged - { - // Settings Model side - String Name { get; }; - String KeyChordText { get; }; - - // UI side - Boolean ShowEditButton { get; }; - Boolean IsInEditMode { get; }; - Boolean IsNewlyAdded { get; }; - Microsoft.Terminal.Control.KeyChord ProposedKeys; - Object ProposedAction; - Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout; - String EditButtonName { get; }; - String CancelButtonName { get; }; - String AcceptButtonName { get; }; - String DeleteButtonName { get; }; - Windows.UI.Xaml.Media.Brush ContainerBackground { get; }; - - void EnterHoverMode(); - void ExitHoverMode(); - void ActionGotFocus(); - void ActionLostFocus(); - void EditButtonGettingFocus(); - void EditButtonLosingFocus(); - IObservableVector AvailableActions { get; }; - void ToggleEditMode(); - void AttemptAcceptChanges(); - void CancelChanges(); - void DeleteKeyBinding(); - - event Windows.Foundation.TypedEventHandler ModifyKeyBindingRequested; - event Windows.Foundation.TypedEventHandler DeleteKeyBindingRequested; - } - - runtimeclass ActionsPageNavigationState - { - Microsoft.Terminal.Settings.Model.CascadiaSettings Settings; - }; - [default_interface] runtimeclass Actions : Windows.UI.Xaml.Controls.Page { Actions(); - ActionsPageNavigationState State { get; }; - - IObservableVector KeyBindingList { get; }; + ActionsViewModel ViewModel { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 0c48e710e7f..82e074eca01 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -347,7 +347,7 @@ diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp new file mode 100644 index 00000000000..c3f12541369 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp @@ -0,0 +1,380 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ActionsViewModel.h" +#include "ActionsViewModel.g.cpp" +#include "KeyBindingViewModel.g.cpp" +#include "LibraryResources.h" +#include "../TerminalSettingsModel/AllShortcutActions.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::System; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Data; +using namespace winrt::Windows::UI::Xaml::Navigation; +using namespace winrt::Microsoft::Terminal::Settings::Model; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + KeyBindingViewModel::KeyBindingViewModel(const IObservableVector& availableActions) : + KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {} + + KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector& availableActions) : + _CurrentKeys{ keys }, + _KeyChordText{ KeyChordSerialization::ToString(keys) }, + _CurrentAction{ actionName }, + _ProposedAction{ box_value(actionName) }, + _AvailableActions{ availableActions } + { + // Add a property changed handler to our own property changed event. + // This propagates changes from the settings model to anybody listening to our + // unique view model members. + PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { + const auto viewModelProperty{ args.PropertyName() }; + if (viewModelProperty == L"CurrentKeys") + { + _KeyChordText = KeyChordSerialization::ToString(_CurrentKeys); + _NotifyChanges(L"KeyChordText"); + } + else if (viewModelProperty == L"IsContainerFocused" || + viewModelProperty == L"IsEditButtonFocused" || + viewModelProperty == L"IsHovered" || + viewModelProperty == L"IsAutomationPeerAttached" || + viewModelProperty == L"IsInEditMode") + { + _NotifyChanges(L"ShowEditButton"); + } + else if (viewModelProperty == L"CurrentAction") + { + _NotifyChanges(L"Name"); + } + }); + } + + hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + + bool KeyBindingViewModel::ShowEditButton() const noexcept + { + return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode(); + } + + void KeyBindingViewModel::ToggleEditMode() + { + // toggle edit mode + IsInEditMode(!_IsInEditMode); + if (_IsInEditMode) + { + // if we're in edit mode, + // - pre-populate the text box with the current keys + // - reset the combo box with the current action + ProposedKeys(_CurrentKeys); + ProposedAction(box_value(_CurrentAction)); + } + } + + void KeyBindingViewModel::AttemptAcceptChanges() + { + AttemptAcceptChanges(_ProposedKeys); + } + + void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys) + { + const auto args{ make_self(_CurrentKeys, // OldKeys + newKeys, // NewKeys + _IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction + unbox_value(_ProposedAction)) }; // NewAction + _ModifyKeyBindingRequestedHandlers(*this, *args); + } + + void KeyBindingViewModel::CancelChanges() + { + if (_IsNewlyAdded) + { + _DeleteNewlyAddedKeyBindingHandlers(*this, nullptr); + } + else + { + ToggleEditMode(); + } + } + + ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) : + _Settings{ settings } + { + // Populate AvailableActionAndArgs + _AvailableActionMap = single_threaded_map(); + std::vector availableActionAndArgs; + for (const auto& [name, actionAndArgs] : _Settings.ActionMap().AvailableActions()) + { + availableActionAndArgs.push_back(name); + _AvailableActionMap.Insert(name, actionAndArgs); + } + std::sort(begin(availableActionAndArgs), end(availableActionAndArgs)); + _AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs)); + + // Convert the key bindings from our settings into a view model representation + const auto& keyBindingMap{ _Settings.ActionMap().KeyBindings() }; + std::vector keyBindingList; + keyBindingList.reserve(keyBindingMap.Size()); + for (const auto& [keys, cmd] : keyBindingMap) + { + // convert the cmd into a KeyBindingViewModel + auto container{ make_self(keys, cmd.Name(), _AvailableActionAndArgs) }; + _RegisterEvents(container); + keyBindingList.push_back(*container); + } + + std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{}); + _KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList)); + } + + void ActionsViewModel::OnAutomationPeerAttached() + { + _AutomationPeerAttached = true; + for (const auto& kbdVM : _KeyBindingList) + { + // To create a more accessible experience, we want the "edit" buttons to _always_ + // appear when a screen reader is attached. This ensures that the edit buttons are + // accessible via the UIA tree. + get_self(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached); + } + } + + void ActionsViewModel::AddNewKeybinding() + { + // Create the new key binding and register all of the event handlers. + auto kbdVM{ make_self(_AvailableActionAndArgs) }; + _RegisterEvents(kbdVM); + kbdVM->DeleteNewlyAddedKeyBinding({ this, &ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler }); + + // Manually add the editing background. This needs to be done in Actions not the view model. + // We also have to do this manually because it hasn't been added to the list yet. + kbdVM->IsInEditMode(true); + // Emit an event to let the page know to update the background of this key binding VM + _UpdateBackgroundHandlers(*this, *kbdVM); + + // IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately + // by the PropertyChangedHandler below (where we delete any IsNewlyAdded items) + kbdVM->IsNewlyAdded(true); + _KeyBindingList.InsertAt(0, *kbdVM); + _FocusContainerHandlers(*this, *kbdVM); + } + + void ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) + { + const auto senderVM{ sender.as() }; + const auto propertyName{ args.PropertyName() }; + if (propertyName == L"IsInEditMode") + { + if (senderVM.IsInEditMode()) + { + // Ensure that... + // 1. we move focus to the edit mode controls + // 2. any actions that were newly added are removed + // 3. this is the only entry that is in edit mode + for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i) + { + const auto& kbdVM{ _KeyBindingList.GetAt(i) }; + if (senderVM == kbdVM) + { + // This is the view model entry that went into edit mode. + // Emit an event to let the page know to move focus to + // this VM's container. + _FocusContainerHandlers(*this, senderVM); + } + else if (kbdVM.IsNewlyAdded()) + { + // Remove any actions that were newly added + _KeyBindingList.RemoveAt(i); + } + else + { + // Exit edit mode for all other containers + get_self(kbdVM)->DisableEditMode(); + } + } + } + else + { + // Emit an event to let the page know to move focus to + // this VM's container. + _FocusContainerHandlers(*this, senderVM); + } + + // Emit an event to let the page know to update the background of this key binding VM + _UpdateBackgroundHandlers(*this, senderVM); + } + } + + void ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys) + { + // Update the settings model + _Settings.ActionMap().DeleteKeyBinding(keys); + + // Find the current container in our list and remove it. + // This is much faster than rebuilding the entire ActionMap. + uint32_t index; + if (_KeyBindingList.IndexOf(senderVM, index)) + { + _KeyBindingList.RemoveAt(index); + + // Focus the new item at this index + if (_KeyBindingList.Size() != 0) + { + const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) }; + // Emit an event to let the page know to move focus to + // this VM's container. + _FocusContainerHandlers(*this, winrt::box_value(newFocusedIndex)); + } + } + } + + void ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args) + { + const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() }; + + auto applyChangesToSettingsModel = [=]() { + // If the key chord was changed, + // update the settings model and view model appropriately + // NOTE: we still need to update the view model if we're working with a newly added action + if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) + { + if (!isNewAction) + { + // update settings model + _Settings.ActionMap().RebindKeys(args.OldKeys(), args.NewKeys()); + } + + // update view model + auto senderVMImpl{ get_self(senderVM) }; + senderVMImpl->CurrentKeys(args.NewKeys()); + } + + // If the action was changed, + // update the settings model and view model appropriately + // NOTE: no need to check for "isNewAction" here. != already. + if (args.OldActionName() != args.NewActionName()) + { + // convert the action's name into a view model. + const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) }; + + // update settings model + _Settings.ActionMap().RegisterKeyBinding(args.NewKeys(), newAction); + + // update view model + auto senderVMImpl{ get_self(senderVM) }; + senderVMImpl->CurrentAction(args.NewActionName()); + senderVMImpl->IsNewlyAdded(false); + } + }; + + // Check for this special case: + // we're changing the key chord, + // but the new key chord is already in use + if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) + { + const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(args.NewKeys()) }; + if (conflictingCmd) + { + // We're about to overwrite another key chord. + // Display a confirmation dialog. + TextBlock errorMessageTB{}; + errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); + + const auto conflictingCmdName{ conflictingCmd.Name() }; + TextBlock conflictingCommandNameTB{}; + conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); + conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); + + TextBlock confirmationQuestionTB{}; + confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); + + Button acceptBTN{}; + acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); + acceptBTN.Click([=](auto&, auto&) { + // remove conflicting key binding from list view + const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) }; + _KeyBindingList.RemoveAt(*containerIndex); + + // remove flyout + senderVM.AcceptChangesFlyout().Hide(); + senderVM.AcceptChangesFlyout(nullptr); + + // update settings model and view model + applyChangesToSettingsModel(); + senderVM.ToggleEditMode(); + }); + + StackPanel flyoutStack{}; + flyoutStack.Children().Append(errorMessageTB); + flyoutStack.Children().Append(conflictingCommandNameTB); + flyoutStack.Children().Append(confirmationQuestionTB); + flyoutStack.Children().Append(acceptBTN); + + Flyout acceptChangesFlyout{}; + acceptChangesFlyout.Content(flyoutStack); + senderVM.AcceptChangesFlyout(acceptChangesFlyout); + } + } + + // update settings model and view model + applyChangesToSettingsModel(); + + // We NEED to toggle the edit mode here, + // so that if nothing changed, we still exit + // edit mode. + senderVM.ToggleEditMode(); + } + + void ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/) + { + for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) + { + const auto& kbdVM{ _KeyBindingList.GetAt(i) }; + if (kbdVM == senderVM) + { + _KeyBindingList.RemoveAt(i); + return; + } + } + } + + // Method Description: + // - performs a search on KeyBindingList by key chord. + // Arguments: + // - keys - the associated key chord of the command we're looking for + // Return Value: + // - the index of the view model referencing the command. If the command doesn't exist, nullopt + std::optional ActionsViewModel::_GetContainerIndexByKeyChord(const Control::KeyChord& keys) + { + for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) + { + const auto kbdVM{ get_self(_KeyBindingList.GetAt(i)) }; + const auto& otherKeys{ kbdVM->CurrentKeys() }; + if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey()) + { + return i; + } + } + + // TODO GH #6900: + // an expedited search can be done if we use cmd.Name() + // to quickly search through the sorted list. + return std::nullopt; + } + + void ActionsViewModel::_RegisterEvents(com_ptr& kbdVM) + { + kbdVM->PropertyChanged({ this, &ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler }); + kbdVM->DeleteKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler }); + kbdVM->ModifyKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler }); + kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h new file mode 100644 index 00000000000..673e0d34f24 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "ActionsViewModel.g.h" +#include "KeyBindingViewModel.g.h" +#include "ModifyKeyBindingEventArgs.g.h" +#include "Utils.h" +#include "ViewModelHelpers.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct KeyBindingViewModelComparator + { + bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const + { + return lhs.Name() < rhs.Name(); + } + }; + + struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT + { + public: + ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) : + _OldKeys{ oldKeys }, + _NewKeys{ newKeys }, + _OldActionName{ std::move(oldActionName) }, + _NewActionName{ std::move(newActionName) } {} + + WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr); + WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr); + WINRT_PROPERTY(hstring, OldActionName); + WINRT_PROPERTY(hstring, NewActionName); + }; + + struct KeyBindingViewModel : KeyBindingViewModelT, ViewModelHelper + { + public: + KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector& availableActions); + KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector& availableActions); + + hstring Name() const { return _CurrentAction; } + hstring KeyChordText() const { return _KeyChordText; } + + // UIA Text + hstring EditButtonName() const noexcept; + hstring CancelButtonName() const noexcept; + hstring AcceptButtonName() const noexcept; + hstring DeleteButtonName() const noexcept; + + void EnterHoverMode() { IsHovered(true); }; + void ExitHoverMode() { IsHovered(false); }; + void ActionGotFocus() { IsContainerFocused(true); }; + void ActionLostFocus() { IsContainerFocused(false); }; + void EditButtonGettingFocus() { IsEditButtonFocused(true); }; + void EditButtonLosingFocus() { IsEditButtonFocused(false); }; + bool ShowEditButton() const noexcept; + void ToggleEditMode(); + void DisableEditMode() { IsInEditMode(false); } + void AttemptAcceptChanges(); + void AttemptAcceptChanges(const Control::KeyChord newKeys); + void CancelChanges(); + void DeleteKeyBinding() { _DeleteKeyBindingRequestedHandlers(*this, _CurrentKeys); } + + // ProposedAction: the entry selected by the combo box; may disagree with the settings model. + // CurrentAction: the combo box item that maps to the settings model value. + // AvailableActions: the list of options in the combo box; both actions above must be in this list. + // NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place. + // Current Action serves as... + // 1 - a record of what to set ProposedAction to on a cancellation + // 2 - a form of translation between ProposedAction and the settings model + // We would also need an ActionMap reference to remove this, but this is a better separation + // of responsibilities. + VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction); + VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableActions, nullptr); + + // ProposedKeys: the keys proposed by the control; may disagree with the settings model. + // CurrentKeys: the key chord bound in the settings model. + VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys); + VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr); + + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr); + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr); + TYPED_EVENT(ModifyKeyBindingRequested, Editor::KeyBindingViewModel, Editor::ModifyKeyBindingEventArgs); + TYPED_EVENT(DeleteKeyBindingRequested, Editor::KeyBindingViewModel, Terminal::Control::KeyChord); + TYPED_EVENT(DeleteNewlyAddedKeyBinding, Editor::KeyBindingViewModel, IInspectable); + + private: + hstring _KeyChordText{}; + }; + + struct ActionsViewModel : ActionsViewModelT, ViewModelHelper + { + public: + ActionsViewModel(Model::CascadiaSettings settings); + + void OnAutomationPeerAttached(); + void AddNewKeybinding(); + + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyBindingList); + TYPED_EVENT(FocusContainer, IInspectable, IInspectable); + TYPED_EVENT(UpdateBackground, IInspectable, IInspectable); + + private: + bool _AutomationPeerAttached{ false }; + Model::CascadiaSettings _Settings; + Windows::Foundation::Collections::IObservableVector _AvailableActionAndArgs; + Windows::Foundation::Collections::IMap _AvailableActionMap; + + std::optional _GetContainerIndexByKeyChord(const Control::KeyChord& keys); + void _RegisterEvents(com_ptr& kbdVM); + + void _KeyBindingViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); + void _KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args); + void _KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args); + void _KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args); + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(ActionsViewModel); +} diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl new file mode 100644 index 00000000000..f091149d778 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Editor +{ + runtimeclass ModifyKeyBindingEventArgs + { + Microsoft.Terminal.Control.KeyChord OldKeys { get; }; + Microsoft.Terminal.Control.KeyChord NewKeys { get; }; + String OldActionName { get; }; + String NewActionName { get; }; + } + + runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + // Settings Model side + String Name { get; }; + String KeyChordText { get; }; + + // UI side + Boolean ShowEditButton { get; }; + Boolean IsInEditMode { get; }; + Boolean IsNewlyAdded { get; }; + Microsoft.Terminal.Control.KeyChord ProposedKeys; + Object ProposedAction; + Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout; + String EditButtonName { get; }; + String CancelButtonName { get; }; + String AcceptButtonName { get; }; + String DeleteButtonName { get; }; + Windows.UI.Xaml.Media.Brush ContainerBackground { get; }; + + void EnterHoverMode(); + void ExitHoverMode(); + void ActionGotFocus(); + void ActionLostFocus(); + void EditButtonGettingFocus(); + void EditButtonLosingFocus(); + IObservableVector AvailableActions { get; }; + void ToggleEditMode(); + void AttemptAcceptChanges(); + void CancelChanges(); + void DeleteKeyBinding(); + + event Windows.Foundation.TypedEventHandler ModifyKeyBindingRequested; + event Windows.Foundation.TypedEventHandler DeleteKeyBindingRequested; + } + + runtimeclass ActionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); + + void OnAutomationPeerAttached(); + void AddNewKeybinding(); + + IObservableVector KeyBindingList { get; }; + event Windows.Foundation.TypedEventHandler FocusContainer; + event Windows.Foundation.TypedEventHandler UpdateBackground; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index fd2ba5107a9..797c207ef88 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -374,7 +374,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == actionsTag) { - contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); + contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); } diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 93db4ca918e..d8c3fbcb452 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -82,6 +82,10 @@ ProfileViewModel.idl Code + + ActionsViewModel.idl + Code + ColorSchemeViewModel.idl Code @@ -225,6 +229,10 @@ ProfileViewModel.idl Code + + ActionsViewModel.idl + Code + ColorSchemeViewModel.idl Code @@ -320,6 +328,7 @@ MainPage.xaml + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index c999254e35f..c27e78965ce 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -18,6 +18,7 @@ + From e3ff44bb82699178c00dd46db70030ea4a777353 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Sun, 18 Feb 2024 23:22:41 -0600 Subject: [PATCH 12/50] build: add a tsa configuration for asyncSdl (#16728) --- .../templates-v2/pipeline-onebranch-full-release-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index 159eb6d080d..d3d77fee51c 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -78,6 +78,9 @@ extends: cloudvault: # https://aka.ms/obpipelines/cloudvault enabled: false globalSdl: # https://aka.ms/obpipelines/sdl + asyncSdl: + enabled: true + tsaOptionsFile: 'build/config/tsa.json' tsa: enabled: true configFile: '$(Build.SourcesDirectory)\build\config\tsa.json' From 23580749a4b607b8ae96716fe25bd8e6a1b05b08 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Mon, 19 Feb 2024 14:58:20 +0000 Subject: [PATCH 13/50] Merged PR 10310193: [Git2Git] Merged PR 10300003: conhost: extract icons ourselves to remove shell32 dependency Changes recently landed in `ge_release_we_adept_dev` that result in conhost loading `Windows.Storage.dll` during startup, rather than `shell32`. This causes a memory usage regression. The only thing we need from `shell32` during startup is `ExtractIconExW`. Fortunately, that function is relatively simple and it relies only on things from `user32` (and one thing from `comctl32`). Enclosed herein is an implementation of `ExtractIconExW` that is tuned for conhost's specific use case and tidied up. Related work items: MSFT-48947348 Retrieved from https://microsoft.visualstudio.com os.2020 OS official/ge_release_we_adept_dev c54c102a362c3dbc7a64cc148b45b993b4154ead --- src/interactivity/win32/Clipboard.cpp | 7 + src/interactivity/win32/icon.cpp | 250 +++++++++++++++++++++++++- src/interactivity/win32/precomp.h | 3 + 3 files changed, 259 insertions(+), 1 deletion(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 6d938166089..5c8c7b80ded 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -362,6 +362,13 @@ void Clipboard::StoreSelectionToClipboard(const bool copyFormatting) rtfData = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); } + const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle()); + if (!clipboard) + { + LOG_LAST_ERROR(); + return; + } + EmptyClipboard(); // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats // CF_UNICODETEXT: [...] A null character signals the end of the data. diff --git a/src/interactivity/win32/icon.cpp b/src/interactivity/win32/icon.cpp index 352d50da996..f25e7af13e1 100644 --- a/src/interactivity/win32/icon.cpp +++ b/src/interactivity/win32/icon.cpp @@ -11,6 +11,254 @@ using namespace Microsoft::Console::Interactivity::Win32; +// This region contains excerpts from ExtractIconExW and all callees, tuned for the Console use case. +// Including this here helps us avoid a load-time dependency on shell32 or Windows.Storage.dll. +static constexpr uint32_t ILD_HIGHQUALITYSCALE{ 0x10000 }; +static constexpr uint32_t LR_EXACTSIZEONLY{ 0x10000 }; + +// System icons come in several standard sizes. +// This function returns the snap size best suited for an arbitrary size. Note that +// larger than 256 returns the input value, which would result in the 256px icon being used. +static int SnapIconSize(int cx) +{ + static constexpr int rgSizes[] = { 16, 32, 48, 256 }; + for (auto sz : rgSizes) + { + if (cx <= sz) + { + return sz; + } + } + return cx; +} + +static HRESULT GetIconSize(HICON hIcon, PSIZE pSize) +{ + pSize->cx = pSize->cy = 32; + + // If it's a cursor, we'll fail so that we end up using our fallback case instead. + ICONINFO iconInfo; + if (GetIconInfo(hIcon, &iconInfo)) + { + auto cleanup = wil::scope_exit([&] { + DeleteObject(iconInfo.hbmMask); + if (iconInfo.hbmColor) + { + DeleteObject(iconInfo.hbmColor); + } + }); + + if (iconInfo.fIcon) + { + BITMAP bmp; + if (GetObject(iconInfo.hbmColor, sizeof(bmp), &bmp)) + { + pSize->cx = bmp.bmWidth; + pSize->cy = bmp.bmHeight; + return S_OK; + } + } + } + + return E_FAIL; +} + +#define SET_HR_AND_BREAK_IF_FAILED(_hr) \ + { \ + const auto __hrRet = (_hr); \ + hr = __hrRet; \ + if (FAILED(hr)) \ + { \ + break; \ + } \ + } + +static HRESULT CreateSmallerIcon(HICON hicon, UINT cx, UINT cy, HICON* phico) +{ + *phico = nullptr; + HRESULT hr = S_OK; + + do + { + SIZE size; + SET_HR_AND_BREAK_IF_FAILED(GetIconSize(hicon, &size)); + + if ((size.cx == (int)cx) && (size.cy == (int)cy)) + { + *phico = hicon; + hr = S_FALSE; + break; + } + + if ((size.cx < (int)cx) || (size.cy < (int)cy)) + { + // If we're passed in a smaller icon than desired, we have a choice; we can either fail altogether, or we could scale it up. + // Failing would make it the client's responsibility to figure out what to do, which sounds like more work + // So instead, we just create an icon the best we can. + // We'll use the fallback below for this. + break; + } + + wil::unique_hmodule comctl32{ LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }; + if (auto ILCoCreateInstance = GetProcAddressByFunctionDeclaration(comctl32.get(), ImageList_CoCreateInstance)) + { + wil::com_ptr_nothrow pimlOriginal; + wil::com_ptr_nothrow pimlIcon; + int fRet = -1; + + SET_HR_AND_BREAK_IF_FAILED(ILCoCreateInstance(CLSID_ImageList, NULL, IID_PPV_ARGS(&pimlOriginal))); + SET_HR_AND_BREAK_IF_FAILED(pimlOriginal->Initialize(size.cx, size.cy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, 1, 1)); + SET_HR_AND_BREAK_IF_FAILED(pimlOriginal->ReplaceIcon(-1, hicon, &fRet)); + SET_HR_AND_BREAK_IF_FAILED(ILCoCreateInstance(CLSID_ImageList, NULL, IID_PPV_ARGS(&pimlIcon))); + SET_HR_AND_BREAK_IF_FAILED(pimlIcon->Initialize(cx, cy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, 1, 1)); + SET_HR_AND_BREAK_IF_FAILED(pimlIcon->SetImageCount(1)); + SET_HR_AND_BREAK_IF_FAILED(pimlIcon->ReplaceFromImageList(0, pimlOriginal.get(), 0, NULL, 0)); + SET_HR_AND_BREAK_IF_FAILED(pimlIcon->GetIcon(0, ILD_HIGHQUALITYSCALE, phico)); + } + } while (0); + + if (!*phico) + { + // For whatever reason, we still don't have an icon. Maybe we have a cursor. At any rate, + // we'll use CopyImage as a last-ditch effort. + *phico = (HICON)CopyImage(hicon, IMAGE_ICON, cx, cy, LR_COPYFROMRESOURCE); + hr = *phico ? S_OK : E_FAIL; + } + + return hr; +} + +static UINT ConExtractIcons(PCWSTR szFileName, int nIconIndex, int cxIcon, int cyIcon, HICON* phicon, UINT nIcons, UINT lrFlags) +{ + UINT result = (UINT)-1; + std::wstring expandedPath, finalPath; + + std::fill_n(phicon, nIcons, nullptr); + + if (FAILED(wil::ExpandEnvironmentStringsW(szFileName, expandedPath))) + { + return result; + } + + if (FAILED(wil::SearchPathW(nullptr, expandedPath.c_str(), nullptr, finalPath))) + { + return result; + } + + int snapcx = SnapIconSize(LOWORD(cxIcon)); + int snapcy = SnapIconSize(LOWORD(cyIcon)); + + // PrivateExtractIconsW can extract two sizes of icons, and does this by having the client + // pass in both in one argument. If that's the case, we have to compute the snap sizes for + // both requested sizes. + + if (nIcons == 2) + { + snapcx = MAKELONG(snapcx, SnapIconSize(HIWORD(cxIcon))); + snapcy = MAKELONG(snapcy, SnapIconSize(HIWORD(cyIcon))); + } + + // When we're in high dpi mode, we need to get larger icons and scale them down, rather than + // the default user32 behaviour of taking the smaller icon and scaling it up. + if ((cxIcon != 0) && (cyIcon != 0) && (nIcons <= 2) && (!((snapcx == cxIcon) && (snapcy == cyIcon)))) + { + // The icon asked for doesn't match one of the standard snap sizes but the file may have + // that size in it anyway - eg 20x20, 64x64, etc. Try to get the requested size and if + // it's not present get the snap size and scale it down to the requested size. + + // PrivateExtractIconsW will fail if you ask for 2 icons and only 1 size if there is only 1 icon in the + // file, even if the one in there matches the one you want. So, if the caller only specified one + // size, only ask for 1 icon. + UINT nIconsActual = (HIWORD(cxIcon)) ? nIcons : 1; + result = PrivateExtractIconsW(finalPath.c_str(), nIconIndex, cxIcon, cyIcon, phicon, nullptr, nIconsActual, lrFlags | LR_EXACTSIZEONLY); + if (nIconsActual != result) + { + HICON ahTempIcon[2] = { 0 }; + + // If there is no exact match the API can return 0 but phicon[0] set to a valid hicon. In that + // case destroy the icon and reset the entry. + UINT i; + for (i = 0; i < nIcons; i++) + { + if (phicon[i]) + { + DestroyIcon(phicon[i]); + phicon[i] = nullptr; + } + } + + // The size we want is not present, go ahead and scale the image. + result = PrivateExtractIconsW(finalPath.c_str(), nIconIndex, snapcx, snapcy, ahTempIcon, nullptr, nIcons, lrFlags | LR_EXACTSIZEONLY); + + if ((int)result > 0) + { + // If CreateSmallerIcon returns S_FALSE, it means the passed in copy is already the correct size + // We don't want to destroy it, so null it out as appropriate + HRESULT hr = CreateSmallerIcon(ahTempIcon[0], LOWORD(cxIcon), LOWORD(cyIcon), phicon); + if (FAILED(hr)) + { + result = (UINT)-1; + } + else if (hr == S_FALSE) + { + ahTempIcon[0] = nullptr; + } + + if (SUCCEEDED(hr) && ((int)result > 1)) + { + __analysis_assume(result < nIcons); // if PrivateExtractIconsW could be annotated with a range + error value(-1) this wouldn't be needed + hr = CreateSmallerIcon(ahTempIcon[1], HIWORD(cxIcon), HIWORD(cyIcon), phicon + 1); + if (FAILED(hr)) + { + DestroyIcon(phicon[0]); + phicon[0] = nullptr; + + result = (UINT)-1; + } + else if (hr == S_FALSE) + { + ahTempIcon[1] = nullptr; + } + } + } + if (ahTempIcon[0]) + { + DestroyIcon(ahTempIcon[0]); + } + if (ahTempIcon[1]) + { + DestroyIcon(ahTempIcon[1]); + } + } + } + + if (phicon[0] == nullptr) + { + // Okay, now get USER to do the extraction if all else failed + result = PrivateExtractIconsW(finalPath.c_str(), nIconIndex, cxIcon, cyIcon, phicon, nullptr, nIcons, lrFlags); + } + return result; +} + +static UINT ConExtractIconInBothSizesW(PCWSTR szFileName, int nIconIndex, HICON* phiconLarge, HICON* phiconSmall) +{ + HICON ahicon[2] = { nullptr, nullptr }; + auto result = ConExtractIcons( + szFileName, + nIconIndex, + MAKELONG(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXSMICON)), + MAKELONG(GetSystemMetrics(SM_CYICON), GetSystemMetrics(SM_CYSMICON)), + ahicon, + 2, + 0); + + *phiconLarge = ahicon[0]; + *phiconSmall = ahicon[1]; + + return result; +} +// Excerpted Region Ends + Icon::Icon() : _fInitialized(false), _hDefaultIcon(nullptr), @@ -123,7 +371,7 @@ Icon& Icon::Instance() // Return value is count of icons extracted, which is redundant with filling the pointers. // http://msdn.microsoft.com/en-us/library/windows/desktop/ms648069(v=vs.85).aspx - ExtractIconExW(pwszIconLocation, nIconIndex, &_hIcon, &_hSmIcon, 1); + ConExtractIconInBothSizesW(pwszIconLocation, nIconIndex, &_hIcon, &_hSmIcon); // If the large icon failed, then ensure that we use the defaults. if (_hIcon == nullptr) diff --git a/src/interactivity/win32/precomp.h b/src/interactivity/win32/precomp.h index 65a79d60e9a..ace6e182451 100644 --- a/src/interactivity/win32/precomp.h +++ b/src/interactivity/win32/precomp.h @@ -2,3 +2,6 @@ // Licensed under the MIT license. #include "../../host/precomp.h" + +#include +#include From 5e9f223a6c86b37c3beacb901215bb592d760567 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 18:53:30 +0100 Subject: [PATCH 14/50] COOKED_READ: Fix tab not erasing the prompt (#16718) Write " foo" in cmd.exe, move yours cursor past the and press tab. The "foo" will still be there but will be inaccessible. This commit fixes the issue. As far as I can tell, this never worked in any conhost version ever. Closes #16704 --- src/host/readDataCooked.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index a05bed8a46a..4aa458b1cdf 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -442,10 +442,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. // + // The old implementation also failed to clear the end of the prompt if you pressed tab in the middle of it. + // You can reproduce this issue by launching cmd in an old conhost build and writing " foo", + // moving your cursor to the space past the and pressing tab. Nothing will happen but the "foo" will be inaccessible. + // I've now fixed this behavior by adding an additional Replace() before the _flushBuffer() call that removes the tail end. + // // It is important that we don't actually print that character out though, as it's only for the calling application to see. // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early. + const auto cursor = _buffer.GetCursorPosition(); + _buffer.Replace(cursor, npos, nullptr, 0); _flushBuffer(); - _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); + _buffer.Replace(cursor, 0, &wch, 1); _buffer.MarkAsClean(); _controlKeyState = modifiers; From 78da9bd96585d2c14c05371676455ac5641308d9 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 18:53:30 +0100 Subject: [PATCH 15/50] COOKED_READ: Fix tab not erasing the prompt (#16718) Write " foo" in cmd.exe, move yours cursor past the and press tab. The "foo" will still be there but will be inaccessible. This commit fixes the issue. As far as I can tell, this never worked in any conhost version ever. Closes #16704 (cherry picked from commit 5e9f223a6c86b37c3beacb901215bb592d760567) Service-Card-Id: 91851714 Service-Version: 1.19 --- src/host/readDataCooked.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 4869e2ad91a..01a30455a8f 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -442,10 +442,17 @@ void COOKED_READ_DATA::_handleChar(wchar_t wch, const DWORD modifiers) // It's unclear whether the original intention was to write at the end of the buffer at all times or to implement an insert mode. // I went with insert mode. // + // The old implementation also failed to clear the end of the prompt if you pressed tab in the middle of it. + // You can reproduce this issue by launching cmd in an old conhost build and writing " foo", + // moving your cursor to the space past the and pressing tab. Nothing will happen but the "foo" will be inaccessible. + // I've now fixed this behavior by adding an additional Replace() before the _flushBuffer() call that removes the tail end. + // // It is important that we don't actually print that character out though, as it's only for the calling application to see. // That's why we flush the contents before the insertion and then ensure that the _flushBuffer() call in Read() exits early. + const auto cursor = _buffer.GetCursorPosition(); + _buffer.Replace(cursor, npos, nullptr, 0); _flushBuffer(); - _buffer.Replace(_buffer.GetCursorPosition(), 0, &wch, 1); + _buffer.Replace(cursor, 0, &wch, 1); _buffer.MarkAsClean(); _controlKeyState = modifiers; From d3ec47a7fc3d4d5d50d2b66bae5f1a872ce3322d Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 18:54:35 +0100 Subject: [PATCH 16/50] Fix an apparent x86 miscompilation on MSVC 19.38 (#16742) There's an apparent miscompilation of `dynamic_bitset` on x86 with MSVC 19.38, where the following code: ```cpp dynamic_bitset bits(80 * 24, 0); bits.set(0, 3 * 80, true); ``` is expected to set the first 3.75 blocks to 1 which should produce the following blocks: ``` 0xffffffffffffffff 0xffffffffffffffff 0xffffffffffffffff 0x0000ffffffffffff ``` but it actually produces: ``` 0xffffffffffffffff 0x00000000ffffffff 0xffffffffffffffff 0x0000ffffffffffff ``` The weird thing here is that this only happens if `til::bitmap` uses a `dynamic_bitset`. As soon as it uses `` any other instantiation of `` is magically fixed. Conclusion: Use `size_t` until we know what's going on. Last known good CL version: 14.37.32822 Current broken CL version: 14.38.33130 ## Validation Steps Performed The following test completes successfully again: ```cpp til::bitmap map{ { 80, 24 } }; map.translate({ 0, 3 }, true); VERIFY_ARE_EQUAL(3u, map.runs().size()); ``` --- src/inc/til/bitmap.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h index 38213500477..3934af878cc 100644 --- a/src/inc/til/bitmap.h +++ b/src/inc/til/bitmap.h @@ -23,7 +23,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" using pointer = const til::rect*; using reference = const til::rect&; - _bitmap_const_iterator(const dynamic_bitset& values, til::rect rc, ptrdiff_t pos) : + _bitmap_const_iterator(const dynamic_bitset& values, til::rect rc, ptrdiff_t pos) : _values(values), _rc(rc), _pos(pos), @@ -77,7 +77,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } private: - const dynamic_bitset& _values; + const dynamic_bitset& _values; const til::rect _rc; size_t _pos; size_t _nextPos; @@ -133,7 +133,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } }; - template> + template> class bitmap { public: @@ -538,7 +538,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" allocator_type _alloc; til::size _sz; til::rect _rc; - dynamic_bitset _bits; + dynamic_bitset _bits; mutable std::optional> _runs; @@ -553,7 +553,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" namespace pmr { - using bitmap = ::til::details::bitmap>; + using bitmap = ::til::details::bitmap>; } } From 0ec73b1a6ec3ef0a83e58643ef7d511c096f1e4b Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 18:54:35 +0100 Subject: [PATCH 17/50] Fix an apparent x86 miscompilation on MSVC 19.38 (#16742) There's an apparent miscompilation of `dynamic_bitset` on x86 with MSVC 19.38, where the following code: ```cpp dynamic_bitset bits(80 * 24, 0); bits.set(0, 3 * 80, true); ``` is expected to set the first 3.75 blocks to 1 which should produce the following blocks: ``` 0xffffffffffffffff 0xffffffffffffffff 0xffffffffffffffff 0x0000ffffffffffff ``` but it actually produces: ``` 0xffffffffffffffff 0x00000000ffffffff 0xffffffffffffffff 0x0000ffffffffffff ``` The weird thing here is that this only happens if `til::bitmap` uses a `dynamic_bitset`. As soon as it uses `` any other instantiation of `` is magically fixed. Conclusion: Use `size_t` until we know what's going on. Last known good CL version: 14.37.32822 Current broken CL version: 14.38.33130 ## Validation Steps Performed The following test completes successfully again: ```cpp til::bitmap map{ { 80, 24 } }; map.translate({ 0, 3 }, true); VERIFY_ARE_EQUAL(3u, map.runs().size()); ``` (cherry picked from commit d3ec47a7fc3d4d5d50d2b66bae5f1a872ce3322d) Service-Card-Id: 91885583 Service-Version: 1.19 --- src/inc/til/bitmap.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h index 38213500477..3934af878cc 100644 --- a/src/inc/til/bitmap.h +++ b/src/inc/til/bitmap.h @@ -23,7 +23,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" using pointer = const til::rect*; using reference = const til::rect&; - _bitmap_const_iterator(const dynamic_bitset& values, til::rect rc, ptrdiff_t pos) : + _bitmap_const_iterator(const dynamic_bitset& values, til::rect rc, ptrdiff_t pos) : _values(values), _rc(rc), _pos(pos), @@ -77,7 +77,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } private: - const dynamic_bitset& _values; + const dynamic_bitset& _values; const til::rect _rc; size_t _pos; size_t _nextPos; @@ -133,7 +133,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } }; - template> + template> class bitmap { public: @@ -538,7 +538,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" allocator_type _alloc; til::size _sz; til::rect _rc; - dynamic_bitset _bits; + dynamic_bitset _bits; mutable std::optional> _runs; @@ -553,7 +553,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" namespace pmr { - using bitmap = ::til::details::bitmap>; + using bitmap = ::til::details::bitmap>; } } From 3f27765861521048d4254ec1008f3483dc7d3949 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 21 Feb 2024 13:31:00 -0600 Subject: [PATCH 18/50] hygiene: remove derelict ARM build configurations (#16746) --- OpenConsole.sln | 364 ------------------ .../MonarchPeasantPackage.wapproj | 8 - 2 files changed, 372 deletions(-) diff --git a/OpenConsole.sln b/OpenConsole.sln index 5c2b55ccd4b..004f8dfe82a 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -425,36 +425,28 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU - AuditMode|ARM = AuditMode|ARM AuditMode|ARM64 = AuditMode|ARM64 AuditMode|x64 = AuditMode|x64 AuditMode|x86 = AuditMode|x86 Debug|Any CPU = Debug|Any CPU - Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Fuzzing|Any CPU = Fuzzing|Any CPU - Fuzzing|ARM = Fuzzing|ARM Fuzzing|ARM64 = Fuzzing|ARM64 Fuzzing|x64 = Fuzzing|x64 Fuzzing|x86 = Fuzzing|x86 Release|Any CPU = Release|Any CPU - Release|ARM = Release|ARM Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|Any CPU.ActiveCfg = Debug|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM.ActiveCfg = Debug|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM.Build.0 = Debug|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM.Deploy.0 = Debug|x64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.AuditMode|x86.ActiveCfg = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|Any CPU.ActiveCfg = Debug|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM.ActiveCfg = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|ARM64.Deploy.0 = Debug|ARM64 @@ -465,12 +457,10 @@ Global {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Build.0 = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Deploy.0 = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Fuzzing|Any CPU.ActiveCfg = Debug|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Fuzzing|ARM.ActiveCfg = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Fuzzing|x64.ActiveCfg = Debug|x64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Fuzzing|x86.ActiveCfg = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|Any CPU.ActiveCfg = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|ARM.ActiveCfg = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|ARM64.Deploy.0 = Release|ARM64 @@ -481,12 +471,10 @@ Global {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Build.0 = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Deploy.0 = Release|x86 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.ActiveCfg = Release|x64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.ActiveCfg = Release|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM.ActiveCfg = Debug|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.Build.0 = Debug|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.ActiveCfg = Debug|x64 @@ -494,12 +482,10 @@ Global {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.ActiveCfg = Debug|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.Build.0 = Debug|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|Any CPU.ActiveCfg = Release|Win32 - {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM.ActiveCfg = Release|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM64.ActiveCfg = Release|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM64.Build.0 = Release|ARM64 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|x64.ActiveCfg = Release|x64 @@ -507,12 +493,10 @@ Global {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.ActiveCfg = Release|Win32 {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.Build.0 = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x64.ActiveCfg = Release|x64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.AuditMode|x86.ActiveCfg = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|ARM.ActiveCfg = Debug|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|ARM64.ActiveCfg = Debug|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|ARM64.Build.0 = Debug|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|x64.ActiveCfg = Debug|x64 @@ -520,13 +504,11 @@ Global {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|x86.ActiveCfg = Debug|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Debug|x86.Build.0 = Debug|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|x64.Build.0 = Fuzzing|x64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|Any CPU.ActiveCfg = Release|Win32 - {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|ARM.ActiveCfg = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|ARM64.ActiveCfg = Release|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|ARM64.Build.0 = Release|ARM64 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|x64.ActiveCfg = Release|x64 @@ -534,12 +516,10 @@ Global {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|x86.ActiveCfg = Release|Win32 {345FD5A4-B32B-4F29-BD1C-B033BD2C35CC}.Release|x86.Build.0 = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x64.ActiveCfg = Release|x64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.AuditMode|x86.ActiveCfg = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|ARM.ActiveCfg = Debug|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|ARM64.Build.0 = Debug|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|x64.ActiveCfg = Debug|x64 @@ -547,13 +527,11 @@ Global {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|x86.ActiveCfg = Debug|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Debug|x86.Build.0 = Debug|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|x64.Build.0 = Fuzzing|x64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|Any CPU.ActiveCfg = Release|Win32 - {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|ARM.ActiveCfg = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|ARM64.ActiveCfg = Release|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|ARM64.Build.0 = Release|ARM64 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|x64.ActiveCfg = Release|x64 @@ -561,13 +539,11 @@ Global {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|x86.ActiveCfg = Release|Win32 {2FD12FBB-1DDB-46D8-B818-1023C624CACA}.Release|x86.Build.0 = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x64.ActiveCfg = AuditMode|x64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x64.Build.0 = AuditMode|x64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.AuditMode|x86.ActiveCfg = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|ARM.ActiveCfg = Debug|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|ARM64.Build.0 = Debug|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|x64.ActiveCfg = Debug|x64 @@ -575,13 +551,11 @@ Global {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|x86.ActiveCfg = Debug|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Debug|x86.Build.0 = Debug|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|x64.Build.0 = Fuzzing|x64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|Any CPU.ActiveCfg = Release|Win32 - {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|ARM.ActiveCfg = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|ARM64.ActiveCfg = Release|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|ARM64.Build.0 = Release|ARM64 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|x64.ActiveCfg = Release|x64 @@ -589,13 +563,11 @@ Global {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|x86.ActiveCfg = Release|Win32 {3AE13314-1939-4DFA-9C14-38CA0834050C}.Release|x86.Build.0 = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.ActiveCfg = AuditMode|x64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.Build.0 = AuditMode|x64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.ActiveCfg = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM.ActiveCfg = Debug|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.Build.0 = Debug|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x64.ActiveCfg = Debug|x64 @@ -603,13 +575,11 @@ Global {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x86.ActiveCfg = Debug|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x86.Build.0 = Debug|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x64.Build.0 = Fuzzing|x64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|Any CPU.ActiveCfg = Release|Win32 - {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM.ActiveCfg = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM64.ActiveCfg = Release|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM64.Build.0 = Release|ARM64 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|x64.ActiveCfg = Release|x64 @@ -617,13 +587,11 @@ Global {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.ActiveCfg = Release|Win32 {DCF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.Build.0 = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.ActiveCfg = AuditMode|x64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x64.Build.0 = AuditMode|x64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.AuditMode|x86.ActiveCfg = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM.ActiveCfg = Debug|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|ARM64.Build.0 = Debug|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x64.ActiveCfg = Debug|x64 @@ -631,13 +599,11 @@ Global {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x86.ActiveCfg = Debug|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Debug|x86.Build.0 = Debug|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x64.Build.0 = Fuzzing|x64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|Any CPU.ActiveCfg = Release|Win32 - {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM.ActiveCfg = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM64.ActiveCfg = Release|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|ARM64.Build.0 = Release|ARM64 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|x64.ActiveCfg = Release|x64 @@ -645,13 +611,11 @@ Global {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.ActiveCfg = Release|Win32 {1CF55140-EF6A-4736-A403-957E4F7430BB}.Release|x86.Build.0 = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x64.ActiveCfg = Release|x64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x64.Build.0 = Release|x64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.AuditMode|x86.ActiveCfg = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|ARM.ActiveCfg = Debug|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|ARM64.Build.0 = Debug|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|x64.ActiveCfg = Debug|x64 @@ -659,13 +623,11 @@ Global {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|x86.ActiveCfg = Debug|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Debug|x86.Build.0 = Debug|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|x64.Build.0 = Fuzzing|x64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|Any CPU.ActiveCfg = Release|Win32 - {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|ARM.ActiveCfg = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|ARM64.ActiveCfg = Release|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|ARM64.Build.0 = Release|ARM64 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|x64.ActiveCfg = Release|x64 @@ -673,12 +635,10 @@ Global {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|x86.ActiveCfg = Release|Win32 {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F}.Release|x86.Build.0 = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x64.ActiveCfg = Release|x64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.AuditMode|x86.ActiveCfg = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|ARM.ActiveCfg = Debug|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|ARM64.Build.0 = Debug|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|x64.ActiveCfg = Debug|x64 @@ -686,13 +646,11 @@ Global {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|x86.ActiveCfg = Debug|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Debug|x86.Build.0 = Debug|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|x64.Build.0 = Fuzzing|x64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|Any CPU.ActiveCfg = Release|Win32 - {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|ARM.ActiveCfg = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|ARM64.ActiveCfg = Release|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|ARM64.Build.0 = Release|ARM64 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|x64.ActiveCfg = Release|x64 @@ -700,12 +658,10 @@ Global {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|x86.ActiveCfg = Release|Win32 {1C959542-BAC2-4E55-9A6D-13251914CBB9}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = Release|x64 {06EC74CB-9A12-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|ARM.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -713,13 +669,11 @@ Global {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|x86.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Debug|x86.Build.0 = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|x64.Build.0 = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8562EC954746}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|Any CPU.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC954746}.Release|ARM.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|x64.ActiveCfg = Release|x64 @@ -727,12 +681,10 @@ Global {06EC74CB-9A12-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x64.ActiveCfg = Release|x64 {06EC74CB-9A12-429C-B551-8562EC954747}.AuditMode|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|ARM.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|x64.ActiveCfg = Debug|x64 @@ -740,12 +692,10 @@ Global {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|x86.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Debug|x86.Build.0 = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {06EC74CB-9A12-429C-B551-8562EC954747}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8562EC954747}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|Any CPU.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC954747}.Release|ARM.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|x64.ActiveCfg = Release|x64 @@ -753,12 +703,10 @@ Global {06EC74CB-9A12-429C-B551-8562EC954747}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC954747}.Release|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x64.ActiveCfg = Release|x64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.AuditMode|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|ARM.ActiveCfg = Debug|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|ARM64.Build.0 = Debug|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|x64.ActiveCfg = Debug|x64 @@ -766,12 +714,10 @@ Global {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|x86.ActiveCfg = Debug|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Debug|x86.Build.0 = Debug|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|Any CPU.ActiveCfg = Release|Win32 - {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|ARM.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|ARM64.ActiveCfg = Release|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|ARM64.Build.0 = Release|ARM64 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|x64.ActiveCfg = Release|x64 @@ -779,12 +725,10 @@ Global {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8AAD-04164CB628C9}.Release|x86.Build.0 = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x64.ActiveCfg = Release|x64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.AuditMode|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|ARM.ActiveCfg = Debug|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|ARM64.Build.0 = Debug|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|x64.ActiveCfg = Debug|x64 @@ -792,12 +736,10 @@ Global {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|x86.ActiveCfg = Debug|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Debug|x86.Build.0 = Debug|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|Any CPU.ActiveCfg = Release|Win32 - {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|ARM.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|ARM64.ActiveCfg = Release|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|ARM64.Build.0 = Release|ARM64 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|x64.ActiveCfg = Release|x64 @@ -805,36 +747,30 @@ Global {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|x86.ActiveCfg = Release|Win32 {531C23E7-4B76-4C08-8BBD-04164CB628C9}.Release|x86.Build.0 = Release|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x64.ActiveCfg = Release|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.AuditMode|x86.ActiveCfg = Release|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|ARM.ActiveCfg = Debug|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|ARM64.ActiveCfg = Debug|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|ARM64.Build.0 = Debug|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|x64.ActiveCfg = Debug|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|x64.Build.0 = Debug|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Debug|x86.ActiveCfg = Debug|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|Any CPU.ActiveCfg = Release|Win32 - {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|ARM.ActiveCfg = Release|Win32 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|ARM64.ActiveCfg = Release|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|ARM64.Build.0 = Release|ARM64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|x64.ActiveCfg = Release|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|x64.Build.0 = Release|x64 {8CDB8850-7484-4EC7-B45B-181F85B2EE54}.Release|x86.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x64.ActiveCfg = Release|x64 {12144E07-FE63-4D33-9231-748B8D8C3792}.AuditMode|x86.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|ARM.ActiveCfg = Debug|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|ARM64.ActiveCfg = Debug|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|ARM64.Build.0 = Debug|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|x64.ActiveCfg = Debug|x64 @@ -842,12 +778,10 @@ Global {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|x86.ActiveCfg = Debug|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Debug|x86.Build.0 = Debug|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {12144E07-FE63-4D33-9231-748B8D8C3792}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|Any CPU.ActiveCfg = Release|Win32 - {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|ARM.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|ARM64.ActiveCfg = Release|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|ARM64.Build.0 = Release|ARM64 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|x64.ActiveCfg = Release|x64 @@ -855,12 +789,10 @@ Global {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|x86.ActiveCfg = Release|Win32 {12144E07-FE63-4D33-9231-748B8D8C3792}.Release|x86.Build.0 = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x64.ActiveCfg = Release|x64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.AuditMode|x86.ActiveCfg = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|ARM.ActiveCfg = Debug|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|ARM64.ActiveCfg = Debug|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|ARM64.Build.0 = Debug|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|x64.ActiveCfg = Debug|x64 @@ -868,12 +800,10 @@ Global {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|x86.ActiveCfg = Debug|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Debug|x86.Build.0 = Debug|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|Any CPU.ActiveCfg = Release|Win32 - {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|ARM.ActiveCfg = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|ARM64.ActiveCfg = Release|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|ARM64.Build.0 = Release|ARM64 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|x64.ActiveCfg = Release|x64 @@ -881,12 +811,10 @@ Global {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|x86.ActiveCfg = Release|Win32 {6AF01638-84CF-4B65-9870-484DFFCAC772}.Release|x86.Build.0 = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x64.ActiveCfg = Release|x64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.AuditMode|x86.ActiveCfg = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|ARM.ActiveCfg = Debug|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|ARM64.Build.0 = Debug|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|x64.ActiveCfg = Debug|x64 @@ -894,12 +822,10 @@ Global {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|x86.ActiveCfg = Debug|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Debug|x86.Build.0 = Debug|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|Any CPU.ActiveCfg = Release|Win32 - {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|ARM.ActiveCfg = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|ARM64.ActiveCfg = Release|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|ARM64.Build.0 = Release|ARM64 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|x64.ActiveCfg = Release|x64 @@ -907,12 +833,10 @@ Global {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|x86.ActiveCfg = Release|Win32 {96927B31-D6E8-4ABD-B03E-A5088A30BEBE}.Release|x86.Build.0 = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x64.ActiveCfg = Release|x64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.AuditMode|x86.ActiveCfg = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|ARM.ActiveCfg = Debug|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|ARM64.Build.0 = Debug|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|x64.ActiveCfg = Debug|x64 @@ -920,12 +844,10 @@ Global {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|x86.ActiveCfg = Debug|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Debug|x86.Build.0 = Debug|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|Any CPU.ActiveCfg = Release|Win32 - {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|ARM.ActiveCfg = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|ARM64.ActiveCfg = Release|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|ARM64.Build.0 = Release|ARM64 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|x64.ActiveCfg = Release|x64 @@ -933,12 +855,10 @@ Global {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|x86.ActiveCfg = Release|Win32 {F210A4AE-E02A-4BFC-80BB-F50A672FE763}.Release|x86.Build.0 = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x64.ActiveCfg = Release|x64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.AuditMode|x86.ActiveCfg = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|ARM.ActiveCfg = Debug|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|ARM64.ActiveCfg = Debug|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|ARM64.Build.0 = Debug|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|x64.ActiveCfg = Debug|x64 @@ -946,13 +866,11 @@ Global {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|x86.ActiveCfg = Debug|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Debug|x86.Build.0 = Debug|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|x64.Build.0 = Fuzzing|x64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|Any CPU.ActiveCfg = Release|Win32 - {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|ARM.ActiveCfg = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|ARM64.ActiveCfg = Release|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|ARM64.Build.0 = Release|ARM64 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|x64.ActiveCfg = Release|x64 @@ -960,12 +878,10 @@ Global {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|x86.ActiveCfg = Release|Win32 {5D23E8E1-3C64-4CC1-A8F7-6861677F7239}.Release|x86.Build.0 = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x64.ActiveCfg = Release|x64 {18D09A24-8240-42D6-8CB6-236EEE820262}.AuditMode|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|ARM.ActiveCfg = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|ARM64.ActiveCfg = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|ARM64.Build.0 = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|x64.ActiveCfg = Debug|x64 @@ -973,13 +889,11 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|x86.ActiveCfg = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Debug|x86.Build.0 = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|x64.Build.0 = Fuzzing|x64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|Any CPU.ActiveCfg = Release|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|ARM.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|ARM64.ActiveCfg = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|ARM64.Build.0 = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|x64.ActiveCfg = Release|x64 @@ -987,62 +901,50 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820262}.Release|x86.Build.0 = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|Any CPU.ActiveCfg = Debug|ARM64 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|ARM.ActiveCfg = Release|ARM64 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|ARM.Build.0 = Release|ARM64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|ARM64.ActiveCfg = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x64.ActiveCfg = Release|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.AuditMode|x86.ActiveCfg = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|ARM.ActiveCfg = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|ARM64.ActiveCfg = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x64.ActiveCfg = Debug|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x64.Build.0 = Debug|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x86.ActiveCfg = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Debug|x86.Build.0 = Debug|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Fuzzing|Any CPU.ActiveCfg = Debug|x64 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Fuzzing|ARM.ActiveCfg = Debug|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Fuzzing|x64.ActiveCfg = Debug|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Fuzzing|x86.ActiveCfg = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|Any CPU.ActiveCfg = Release|Win32 - {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|ARM.ActiveCfg = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|ARM64.ActiveCfg = Release|ARM64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x64.ActiveCfg = Release|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x64.Build.0 = Release|x64 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x86.ActiveCfg = Release|Win32 {C17E1BF3-9D34-4779-9458-A8EF98CC5662}.Release|x86.Build.0 = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|Any CPU.ActiveCfg = Debug|ARM64 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|ARM.ActiveCfg = Release|ARM64 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|ARM.Build.0 = Release|ARM64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|ARM64.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x64.ActiveCfg = Release|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.AuditMode|x86.ActiveCfg = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|ARM.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|ARM64.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x64.ActiveCfg = Debug|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x64.Build.0 = Debug|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x86.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Debug|x86.Build.0 = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Fuzzing|Any CPU.ActiveCfg = Debug|Win32 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Fuzzing|ARM.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Fuzzing|x64.ActiveCfg = Debug|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Fuzzing|x86.ActiveCfg = Debug|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|Any CPU.ActiveCfg = Release|Win32 - {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|ARM.ActiveCfg = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|ARM64.ActiveCfg = Release|ARM64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x64.ActiveCfg = Release|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x64.Build.0 = Release|x64 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x86.ActiveCfg = Release|Win32 {099193A0-1E43-4BBC-BA7F-7B351E1342DF}.Release|x86.Build.0 = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x64.ActiveCfg = Release|x64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.AuditMode|x86.ActiveCfg = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|ARM.ActiveCfg = Debug|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|ARM64.ActiveCfg = Debug|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|ARM64.Build.0 = Debug|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|x64.ActiveCfg = Debug|x64 @@ -1050,12 +952,10 @@ Global {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|x86.ActiveCfg = Debug|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Debug|x86.Build.0 = Debug|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|Any CPU.ActiveCfg = Release|Win32 - {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|ARM.ActiveCfg = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|ARM64.ActiveCfg = Release|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|ARM64.Build.0 = Release|ARM64 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|x64.ActiveCfg = Release|x64 @@ -1063,12 +963,10 @@ Global {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|x86.ActiveCfg = Release|Win32 {FC802440-AD6A-4919-8F2C-7701F2B38D79}.Release|x86.Build.0 = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x64.ActiveCfg = Release|x64 {919544AC-D39B-463F-8414-3C3C67CF727C}.AuditMode|x86.ActiveCfg = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|ARM.ActiveCfg = Debug|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|ARM64.Build.0 = Debug|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|x64.ActiveCfg = Debug|x64 @@ -1076,12 +974,10 @@ Global {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|x86.ActiveCfg = Debug|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Debug|x86.Build.0 = Debug|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {919544AC-D39B-463F-8414-3C3C67CF727C}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|Any CPU.ActiveCfg = Release|Win32 - {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|ARM.ActiveCfg = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|ARM64.ActiveCfg = Release|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|ARM64.Build.0 = Release|ARM64 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|x64.ActiveCfg = Release|x64 @@ -1089,12 +985,10 @@ Global {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|x86.ActiveCfg = Release|Win32 {919544AC-D39B-463F-8414-3C3C67CF727C}.Release|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x64.ActiveCfg = Release|x64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.AuditMode|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|ARM.ActiveCfg = Debug|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|ARM64.ActiveCfg = Debug|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|ARM64.Build.0 = Debug|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|x64.ActiveCfg = Debug|x64 @@ -1102,12 +996,10 @@ Global {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|x86.ActiveCfg = Debug|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Debug|x86.Build.0 = Debug|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|Any CPU.ActiveCfg = Release|Win32 - {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|ARM.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|ARM64.ActiveCfg = Release|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|ARM64.Build.0 = Release|ARM64 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|x64.ActiveCfg = Release|x64 @@ -1115,12 +1007,10 @@ Global {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B36-F480018ED064}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x64.ActiveCfg = Release|x64 {06EC74CB-9A12-429C-B551-8532EC964726}.AuditMode|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|ARM.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|x64.ActiveCfg = Debug|x64 @@ -1128,13 +1018,11 @@ Global {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|x86.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Debug|x86.Build.0 = Debug|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|x64.Build.0 = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8532EC964726}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|Any CPU.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8532EC964726}.Release|ARM.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|x64.ActiveCfg = Release|x64 @@ -1142,12 +1030,10 @@ Global {06EC74CB-9A12-429C-B551-8532EC964726}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8532EC964726}.Release|x86.Build.0 = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x64.ActiveCfg = Release|x64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.AuditMode|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|ARM.ActiveCfg = Debug|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|ARM64.ActiveCfg = Debug|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|ARM64.Build.0 = Debug|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|x64.ActiveCfg = Debug|x64 @@ -1155,13 +1041,11 @@ Global {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|x86.ActiveCfg = Debug|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Debug|x86.Build.0 = Debug|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|x64.Build.0 = Fuzzing|x64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|Any CPU.ActiveCfg = Release|Win32 - {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|ARM.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|ARM64.ActiveCfg = Release|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|ARM64.Build.0 = Release|ARM64 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|x64.ActiveCfg = Release|x64 @@ -1169,12 +1053,10 @@ Global {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|x86.ActiveCfg = Release|Win32 {ED82003F-FC5D-4E94-8B47-F480018ED064}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x64.ActiveCfg = Release|x64 {06EC74CB-9A12-429C-B551-8562EC964846}.AuditMode|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|ARM.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|x64.ActiveCfg = Debug|x64 @@ -1182,13 +1064,11 @@ Global {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|x86.ActiveCfg = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Debug|x86.Build.0 = Debug|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|x64.Build.0 = Fuzzing|x64 {06EC74CB-9A12-429C-B551-8562EC964846}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|Any CPU.ActiveCfg = Release|Win32 - {06EC74CB-9A12-429C-B551-8562EC964846}.Release|ARM.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|x64.ActiveCfg = Release|x64 @@ -1196,12 +1076,10 @@ Global {06EC74CB-9A12-429C-B551-8562EC964846}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-429C-B551-8562EC964846}.Release|x86.Build.0 = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x64.ActiveCfg = Release|x64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.AuditMode|x86.ActiveCfg = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|ARM.ActiveCfg = Debug|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|ARM64.ActiveCfg = Debug|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|ARM64.Build.0 = Debug|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|x64.ActiveCfg = Debug|x64 @@ -1209,12 +1087,10 @@ Global {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|x86.ActiveCfg = Debug|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Debug|x86.Build.0 = Debug|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|Any CPU.ActiveCfg = Release|Win32 - {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|ARM.ActiveCfg = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|ARM64.ActiveCfg = Release|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|ARM64.Build.0 = Release|ARM64 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|x64.ActiveCfg = Release|x64 @@ -1222,12 +1098,10 @@ Global {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|x86.ActiveCfg = Release|Win32 {D3B92829-26CB-411A-BDA2-7F5DA3D25DD4}.Release|x86.Build.0 = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x64.ActiveCfg = Release|x64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.AuditMode|x86.ActiveCfg = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|ARM.ActiveCfg = Debug|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|ARM64.Build.0 = Debug|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|x64.ActiveCfg = Debug|x64 @@ -1235,12 +1109,10 @@ Global {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|x86.ActiveCfg = Debug|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Debug|x86.Build.0 = Debug|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|Any CPU.ActiveCfg = Release|Win32 - {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|ARM.ActiveCfg = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|ARM64.ActiveCfg = Release|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|ARM64.Build.0 = Release|ARM64 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x64.ActiveCfg = Release|x64 @@ -1248,12 +1120,10 @@ Global {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.ActiveCfg = Release|Win32 {C7A6A5D9-60BE-4AEB-A5F6-AFE352F86CBB}.Release|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x64.ActiveCfg = Release|x64 {990F2657-8580-4828-943F-5DD657D11842}.AuditMode|x86.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM.ActiveCfg = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.ActiveCfg = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Debug|ARM64.Build.0 = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Debug|x64.ActiveCfg = Debug|x64 @@ -1261,13 +1131,11 @@ Global {990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.ActiveCfg = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Debug|x86.Build.0 = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x64.Build.0 = Fuzzing|x64 {990F2657-8580-4828-943F-5DD657D11842}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Release|Any CPU.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11842}.Release|ARM.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.ActiveCfg = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Release|ARM64.Build.0 = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11842}.Release|x64.ActiveCfg = Release|x64 @@ -1275,12 +1143,10 @@ Global {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11842}.Release|x86.Build.0 = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64 {814DBDDE-894E-4327-A6E1-740504850098}.AuditMode|x86.ActiveCfg = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {814DBDDE-894E-4327-A6E1-740504850098}.Debug|ARM.ActiveCfg = Debug|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|ARM64.ActiveCfg = Debug|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|ARM64.Build.0 = Debug|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|x64.ActiveCfg = Debug|x64 @@ -1288,12 +1154,10 @@ Global {814DBDDE-894E-4327-A6E1-740504850098}.Debug|x86.ActiveCfg = Debug|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Debug|x86.Build.0 = Debug|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {814DBDDE-894E-4327-A6E1-740504850098}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {814DBDDE-894E-4327-A6E1-740504850098}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Release|Any CPU.ActiveCfg = Release|Win32 - {814DBDDE-894E-4327-A6E1-740504850098}.Release|ARM.ActiveCfg = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Release|ARM64.ActiveCfg = Release|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Release|ARM64.Build.0 = Release|ARM64 {814DBDDE-894E-4327-A6E1-740504850098}.Release|x64.ActiveCfg = Release|x64 @@ -1301,12 +1165,10 @@ Global {814DBDDE-894E-4327-A6E1-740504850098}.Release|x86.ActiveCfg = Release|Win32 {814DBDDE-894E-4327-A6E1-740504850098}.Release|x86.Build.0 = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x64.ActiveCfg = Release|x64 {814CBEEE-894E-4327-A6E1-740504850098}.AuditMode|x86.ActiveCfg = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {814CBEEE-894E-4327-A6E1-740504850098}.Debug|ARM.ActiveCfg = Debug|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|ARM64.ActiveCfg = Debug|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|ARM64.Build.0 = Debug|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|x64.ActiveCfg = Debug|x64 @@ -1314,12 +1176,10 @@ Global {814CBEEE-894E-4327-A6E1-740504850098}.Debug|x86.ActiveCfg = Debug|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Debug|x86.Build.0 = Debug|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {814CBEEE-894E-4327-A6E1-740504850098}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {814CBEEE-894E-4327-A6E1-740504850098}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Release|Any CPU.ActiveCfg = Release|Win32 - {814CBEEE-894E-4327-A6E1-740504850098}.Release|ARM.ActiveCfg = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Release|ARM64.ActiveCfg = Release|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Release|ARM64.Build.0 = Release|ARM64 {814CBEEE-894E-4327-A6E1-740504850098}.Release|x64.ActiveCfg = Release|x64 @@ -1327,7 +1187,6 @@ Global {814CBEEE-894E-4327-A6E1-740504850098}.Release|x86.ActiveCfg = Release|Win32 {814CBEEE-894E-4327-A6E1-740504850098}.Release|x86.Build.0 = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1335,7 +1194,6 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.AuditMode|x86.Build.0 = AuditMode|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|ARM.ActiveCfg = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|ARM64.ActiveCfg = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|ARM64.Build.0 = Debug|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x64.ActiveCfg = Debug|x64 @@ -1343,13 +1201,11 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x86.ActiveCfg = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Debug|x86.Build.0 = Debug|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x64.Build.0 = Fuzzing|x64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|Any CPU.ActiveCfg = Release|Win32 - {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|ARM.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|ARM64.ActiveCfg = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|ARM64.Build.0 = Release|ARM64 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x64.ActiveCfg = Release|x64 @@ -1357,12 +1213,10 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.ActiveCfg = Release|Win32 {18D09A24-8240-42D6-8CB6-236EEE820263}.Release|x86.Build.0 = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x64.ActiveCfg = Release|x64 {990F2657-8580-4828-943F-5DD657D11843}.AuditMode|x86.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM.ActiveCfg = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.ActiveCfg = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Debug|ARM64.Build.0 = Debug|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Debug|x64.ActiveCfg = Debug|x64 @@ -1370,12 +1224,10 @@ Global {990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.ActiveCfg = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Debug|x86.Build.0 = Debug|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {990F2657-8580-4828-943F-5DD657D11843}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Release|Any CPU.ActiveCfg = Release|Win32 - {990F2657-8580-4828-943F-5DD657D11843}.Release|ARM.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.ActiveCfg = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Release|ARM64.Build.0 = Release|ARM64 {990F2657-8580-4828-943F-5DD657D11843}.Release|x64.ActiveCfg = Release|x64 @@ -1383,7 +1235,6 @@ Global {990F2657-8580-4828-943F-5DD657D11843}.Release|x86.ActiveCfg = Release|Win32 {990F2657-8580-4828-943F-5DD657D11843}.Release|x86.Build.0 = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1391,7 +1242,6 @@ Global {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.AuditMode|x86.Build.0 = AuditMode|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|ARM.ActiveCfg = Debug|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|ARM64.ActiveCfg = Debug|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|ARM64.Build.0 = Debug|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|x64.ActiveCfg = Debug|x64 @@ -1399,13 +1249,11 @@ Global {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|x86.ActiveCfg = Debug|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Debug|x86.Build.0 = Debug|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|x64.Build.0 = Fuzzing|x64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|Any CPU.ActiveCfg = Release|Win32 - {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|ARM.ActiveCfg = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|ARM64.ActiveCfg = Release|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|ARM64.Build.0 = Release|ARM64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x64.ActiveCfg = Release|x64 @@ -1413,7 +1261,6 @@ Global {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.ActiveCfg = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.Build.0 = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1421,7 +1268,6 @@ Global {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.Build.0 = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM.ActiveCfg = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.ActiveCfg = Debug|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.Build.0 = Debug|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x64.ActiveCfg = Debug|x64 @@ -1429,13 +1275,11 @@ Global {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.ActiveCfg = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.Build.0 = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.Build.0 = Fuzzing|x64 {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|Any CPU.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM.ActiveCfg = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.ActiveCfg = Release|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.Build.0 = Release|ARM64 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x64.ActiveCfg = Release|x64 @@ -1443,13 +1287,11 @@ Global {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.ActiveCfg = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x64.Build.0 = AuditMode|x64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|x64.ActiveCfg = Debug|x64 @@ -1457,12 +1299,10 @@ Global {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|x64.ActiveCfg = Release|x64 @@ -1470,13 +1310,11 @@ Global {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x64.Build.0 = AuditMode|x64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -1484,12 +1322,10 @@ Global {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|x64.ActiveCfg = Release|x64 @@ -1497,12 +1333,10 @@ Global {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-ABCD-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|x64.ActiveCfg = Debug|x64 @@ -1510,12 +1344,10 @@ Global {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x64.ActiveCfg = Release|x64 @@ -1523,12 +1355,10 @@ Global {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x64.ActiveCfg = Debug|x64 @@ -1536,12 +1366,10 @@ Global {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x64.ActiveCfg = Release|x64 @@ -1549,12 +1377,10 @@ Global {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-F542-4635-A069-7CAEFB930070}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|Any CPU.ActiveCfg = Debug|x64 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|x64.ActiveCfg = Debug|x64 @@ -1562,12 +1388,10 @@ Global {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|x64.ActiveCfg = Release|x64 @@ -1575,12 +1399,10 @@ Global {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|x64.ActiveCfg = Debug|x64 @@ -1588,12 +1410,10 @@ Global {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x64.ActiveCfg = Release|x64 @@ -1601,13 +1421,11 @@ Global {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.Build.0 = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|Any CPU.ActiveCfg = Release|Win32 - {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x64.ActiveCfg = Release|x64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x64.Build.0 = Release|x64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x86.ActiveCfg = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|ARM.ActiveCfg = Debug|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|ARM64.Build.0 = Debug|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x64.ActiveCfg = Debug|x64 @@ -1615,12 +1433,10 @@ Global {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x86.ActiveCfg = Debug|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x86.Build.0 = Debug|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|Any CPU.ActiveCfg = Release|Win32 - {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|ARM.ActiveCfg = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|ARM64.ActiveCfg = Release|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|ARM64.Build.0 = Release|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x64.ActiveCfg = Release|x64 @@ -1628,12 +1444,10 @@ Global {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x86.ActiveCfg = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x86.Build.0 = Release|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.ActiveCfg = AuditMode|x64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|ARM.ActiveCfg = Debug|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.Build.0 = Debug|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|x64.ActiveCfg = Debug|x64 @@ -1641,12 +1455,10 @@ Global {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|x86.ActiveCfg = Debug|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Debug|x86.Build.0 = Debug|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|Any CPU.ActiveCfg = Release|Win32 - {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|ARM.ActiveCfg = Release|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|ARM64.ActiveCfg = Release|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|ARM64.Build.0 = Release|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|x64.ActiveCfg = Release|x64 @@ -1654,7 +1466,6 @@ Global {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|x86.ActiveCfg = Release|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.Release|x86.Build.0 = Release|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1662,7 +1473,6 @@ Global {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.AuditMode|x86.Build.0 = AuditMode|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|ARM.ActiveCfg = Debug|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|ARM64.ActiveCfg = Debug|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|ARM64.Build.0 = Debug|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|x64.ActiveCfg = Debug|x64 @@ -1670,13 +1480,11 @@ Global {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|x86.ActiveCfg = Debug|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Debug|x86.Build.0 = Debug|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|x64.Build.0 = Fuzzing|x64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|Any CPU.ActiveCfg = Release|Win32 - {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|ARM.ActiveCfg = Release|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|ARM64.ActiveCfg = Release|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|ARM64.Build.0 = Release|ARM64 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x64.ActiveCfg = Release|x64 @@ -1684,12 +1492,10 @@ Global {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.ActiveCfg = Release|Win32 {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00}.Release|x86.Build.0 = Release|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x64.ActiveCfg = AuditMode|x64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM.ActiveCfg = Debug|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.ActiveCfg = Debug|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|ARM64.Build.0 = Debug|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x64.ActiveCfg = Debug|x64 @@ -1697,12 +1503,10 @@ Global {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.ActiveCfg = Debug|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Debug|x86.Build.0 = Debug|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|Any CPU.ActiveCfg = Release|Win32 - {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM.ActiveCfg = Release|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.ActiveCfg = Release|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|ARM64.Build.0 = Release|ARM64 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x64.ActiveCfg = Release|x64 @@ -1710,8 +1514,6 @@ Global {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.ActiveCfg = Release|Win32 {34DE34D3-1CD6-4EE3-8BD9-A26B5B27EC73}.Release|x86.Build.0 = Release|Win32 {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|Any CPU.ActiveCfg = Release|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|ARM.ActiveCfg = Debug|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|ARM.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|ARM64.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|ARM64.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|x64.ActiveCfg = Debug|Any CPU @@ -1720,8 +1522,6 @@ Global {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.AuditMode|x86.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|ARM.ActiveCfg = Debug|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|ARM.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|ARM64.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|ARM64.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1729,14 +1529,11 @@ Global {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|x86.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Debug|x86.Build.0 = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Fuzzing|Any CPU.ActiveCfg = Debug|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Fuzzing|ARM.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Fuzzing|x64.ActiveCfg = Debug|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Fuzzing|x86.ActiveCfg = Fuzzing|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|Any CPU.ActiveCfg = Release|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|Any CPU.Build.0 = Release|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|ARM.ActiveCfg = Release|Any CPU - {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|ARM.Build.0 = Release|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|ARM64.ActiveCfg = Release|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|ARM64.Build.0 = Release|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|x64.ActiveCfg = Release|Any CPU @@ -1744,12 +1541,10 @@ Global {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|x86.ActiveCfg = Release|Any CPU {376FE273-6B84-4EB5-8B30-8DE9D21B022C}.Release|x86.Build.0 = Release|Any CPU {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|x64.ActiveCfg = Debug|x64 @@ -1757,12 +1552,10 @@ Global {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|x64.ActiveCfg = Release|x64 @@ -1770,12 +1563,10 @@ Global {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9333-4D05-B12A-1905CBF112F9}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|x64.ActiveCfg = Debug|x64 @@ -1783,12 +1574,10 @@ Global {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x64.ActiveCfg = Release|x64 @@ -1796,12 +1585,10 @@ Global {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9A12-429C-B551-8562EC954746}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x64.ActiveCfg = Debug|x64 @@ -1809,12 +1596,10 @@ Global {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x64.ActiveCfg = Release|x64 @@ -1822,7 +1607,6 @@ Global {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}.Release|x86.Build.0 = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1830,7 +1614,6 @@ Global {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.AuditMode|x86.Build.0 = AuditMode|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|ARM.ActiveCfg = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|ARM64.ActiveCfg = Debug|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|ARM64.Build.0 = Debug|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|x64.ActiveCfg = Debug|x64 @@ -1838,12 +1621,10 @@ Global {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|x86.ActiveCfg = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Debug|x86.Build.0 = Debug|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF63}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {48D21369-3D7B-4431-9967-24E81292CF63}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Release|Any CPU.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF63}.Release|ARM.ActiveCfg = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Release|ARM64.ActiveCfg = Release|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.Release|ARM64.Build.0 = Release|ARM64 {48D21369-3D7B-4431-9967-24E81292CF63}.Release|x64.ActiveCfg = Release|x64 @@ -1851,13 +1632,11 @@ Global {48D21369-3D7B-4431-9967-24E81292CF63}.Release|x86.ActiveCfg = Release|Win32 {48D21369-3D7B-4431-9967-24E81292CF63}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|Any CPU.ActiveCfg = Release|x64 - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|x64.Build.0 = AuditMode|x64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.AuditMode|x86.ActiveCfg = Release|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x64.ActiveCfg = Debug|x64 @@ -1865,12 +1644,10 @@ Global {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x64.ActiveCfg = Release|x64 @@ -1878,7 +1655,6 @@ Global {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}.Release|x86.Build.0 = Release|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1886,7 +1662,6 @@ Global {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.AuditMode|x86.Build.0 = AuditMode|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|ARM.ActiveCfg = Debug|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|ARM64.ActiveCfg = Debug|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|ARM64.Build.0 = Debug|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x64.ActiveCfg = Debug|x64 @@ -1894,12 +1669,10 @@ Global {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x86.ActiveCfg = Debug|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Debug|x86.Build.0 = Debug|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|Any CPU.ActiveCfg = Release|Win32 - {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|ARM.ActiveCfg = Release|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|ARM64.ActiveCfg = Release|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|ARM64.Build.0 = Release|ARM64 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x64.ActiveCfg = Release|x64 @@ -1907,14 +1680,12 @@ Global {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x86.ActiveCfg = Release|Win32 {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A}.Release|x86.Build.0 = Release|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|x64.ActiveCfg = Release|x64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.AuditMode|x86.Build.0 = AuditMode|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|ARM.ActiveCfg = Debug|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|ARM64.ActiveCfg = Debug|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|ARM64.Build.0 = Debug|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|x64.ActiveCfg = Debug|x64 @@ -1922,12 +1693,10 @@ Global {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|x86.ActiveCfg = Debug|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Debug|x86.Build.0 = Debug|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {A22EC5F6-7851-4B88-AC52-47249D437A52}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|Any CPU.ActiveCfg = Release|Win32 - {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|ARM.ActiveCfg = Release|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|ARM64.ActiveCfg = Release|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|ARM64.Build.0 = Release|ARM64 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x64.ActiveCfg = Release|x64 @@ -1935,12 +1704,10 @@ Global {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x86.ActiveCfg = Release|Win32 {A22EC5F6-7851-4B88-AC52-47249D437A52}.Release|x86.Build.0 = Release|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64 - {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|x64.ActiveCfg = AuditMode|x64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM.ActiveCfg = Debug|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.ActiveCfg = Debug|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.Build.0 = Debug|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|ARM64.Deploy.0 = Debug|ARM64 @@ -1951,12 +1718,10 @@ Global {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x86.Build.0 = Debug|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Debug|x86.Deploy.0 = Debug|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|Any CPU.ActiveCfg = Release|Win32 - {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM.ActiveCfg = Release|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.ActiveCfg = Release|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.Build.0 = Release|ARM64 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|ARM64.Deploy.0 = Release|ARM64 @@ -1967,14 +1732,12 @@ Global {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x86.Build.0 = Release|Win32 {A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}.Release|x86.Deploy.0 = Release|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|x64.ActiveCfg = Release|x64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.AuditMode|x86.Build.0 = AuditMode|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|ARM.ActiveCfg = Debug|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|ARM64.ActiveCfg = Debug|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|ARM64.Build.0 = Debug|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|x64.ActiveCfg = Debug|x64 @@ -1982,12 +1745,10 @@ Global {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|x86.ActiveCfg = Debug|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Debug|x86.Build.0 = Debug|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|Any CPU.ActiveCfg = Release|Win32 - {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|ARM.ActiveCfg = Release|Win32 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|ARM64.ActiveCfg = Release|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|ARM64.Build.0 = Release|ARM64 {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|x64.ActiveCfg = Release|x64 @@ -1996,7 +1757,6 @@ Global {767268EE-174A-46FE-96F0-EEE698A1BBC9}.Release|x86.Build.0 = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|Any CPU.ActiveCfg = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|Any CPU.Build.0 = Release|x64 - {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|ARM64.ActiveCfg = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|ARM64.Build.0 = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|x64.ActiveCfg = Release|x64 @@ -2004,33 +1764,28 @@ Global {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|x86.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.AuditMode|x86.Build.0 = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|ARM.ActiveCfg = Debug|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|ARM64.ActiveCfg = Debug|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|x64.ActiveCfg = Debug|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|x64.Build.0 = Debug|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|x86.ActiveCfg = Debug|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Debug|x86.Build.0 = Debug|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|Any CPU.ActiveCfg = Release|Win32 - {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|ARM.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|ARM64.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.ActiveCfg = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x64.ActiveCfg = Release|x64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.Build.0 = AuditMode|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM.ActiveCfg = Debug|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.ActiveCfg = Debug|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.Build.0 = Debug|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.ActiveCfg = Debug|x64 @@ -2038,12 +1793,10 @@ Global {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.ActiveCfg = Debug|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.Build.0 = Debug|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|Any CPU.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM.ActiveCfg = Release|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.ActiveCfg = Release|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.Build.0 = Release|ARM64 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.ActiveCfg = Release|x64 @@ -2051,14 +1804,12 @@ Global {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.ActiveCfg = Release|Win32 {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.Build.0 = Release|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x64.ActiveCfg = Release|x64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|x86.Build.0 = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM.ActiveCfg = Debug|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.ActiveCfg = Debug|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|ARM64.Build.0 = Debug|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x64.ActiveCfg = Debug|x64 @@ -2066,12 +1817,10 @@ Global {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.ActiveCfg = Debug|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Debug|x86.Build.0 = Debug|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {024052DE-83FB-4653-AEA4-90790D29D5BD}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|Any CPU.ActiveCfg = Release|Win32 - {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM.ActiveCfg = Release|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.ActiveCfg = Release|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|ARM64.Build.0 = Release|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x64.ActiveCfg = Release|x64 @@ -2079,14 +1828,12 @@ Global {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.ActiveCfg = Release|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.Release|x86.Build.0 = Release|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x64.ActiveCfg = Release|x64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.AuditMode|x86.Build.0 = AuditMode|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM.ActiveCfg = Debug|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.ActiveCfg = Debug|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|ARM64.Build.0 = Debug|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x64.ActiveCfg = Debug|x64 @@ -2094,12 +1841,10 @@ Global {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.ActiveCfg = Debug|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Debug|x86.Build.0 = Debug|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|Any CPU.ActiveCfg = Release|Win32 - {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM.ActiveCfg = Release|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.ActiveCfg = Release|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|ARM64.Build.0 = Release|ARM64 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x64.ActiveCfg = Release|x64 @@ -2107,7 +1852,6 @@ Global {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.ActiveCfg = Release|Win32 {067F0A06-FCB7-472C-96E9-B03B54E8E18D}.Release|x86.Build.0 = Release|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2115,7 +1859,6 @@ Global {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.AuditMode|x86.Build.0 = AuditMode|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|ARM.ActiveCfg = Debug|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|ARM64.ActiveCfg = Debug|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|ARM64.Build.0 = Debug|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x64.ActiveCfg = Debug|x64 @@ -2123,13 +1866,11 @@ Global {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x86.ActiveCfg = Debug|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Debug|x86.Build.0 = Debug|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x64.Build.0 = Fuzzing|x64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|Any CPU.ActiveCfg = Release|Win32 - {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|ARM.ActiveCfg = Release|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|ARM64.ActiveCfg = Release|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|ARM64.Build.0 = Release|ARM64 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x64.ActiveCfg = Release|x64 @@ -2137,14 +1878,10 @@ Global {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x86.ActiveCfg = Release|Win32 {6BAE5851-50D5-4934-8D5E-30361A8A40F3}.Release|x86.Build.0 = Release|Win32 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|Any CPU.ActiveCfg = Debug|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|ARM.ActiveCfg = Release|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|ARM.Build.0 = Release|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|ARM64.ActiveCfg = Debug|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|x64.ActiveCfg = Debug|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.AuditMode|x86.ActiveCfg = Debug|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|ARM.ActiveCfg = Debug|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|ARM.Build.0 = Debug|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|ARM64.ActiveCfg = Debug|ARM64 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|ARM64.Build.0 = Debug|ARM64 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|x64.ActiveCfg = Debug|x64 @@ -2152,13 +1889,10 @@ Global {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|x86.ActiveCfg = Debug|x86 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Debug|x86.Build.0 = Debug|x86 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Fuzzing|Any CPU.ActiveCfg = Debug|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Fuzzing|ARM.ActiveCfg = Debug|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Fuzzing|x64.ActiveCfg = Debug|x64 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Fuzzing|x86.ActiveCfg = Fuzzing|x86 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|ARM.ActiveCfg = Release|Any CPU - {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|ARM.Build.0 = Release|Any CPU {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|ARM64.ActiveCfg = Release|ARM64 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|ARM64.Build.0 = Release|ARM64 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x64.ActiveCfg = Release|x64 @@ -2166,7 +1900,6 @@ Global {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x86.ActiveCfg = Release|x86 {1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x86.Build.0 = Release|x86 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2174,7 +1907,6 @@ Global {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.Build.0 = AuditMode|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM.ActiveCfg = Debug|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.ActiveCfg = Debug|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.Build.0 = Debug|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.ActiveCfg = Debug|x64 @@ -2182,12 +1914,10 @@ Global {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.ActiveCfg = Debug|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.Build.0 = Debug|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|Any CPU.ActiveCfg = Release|Win32 - {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM.ActiveCfg = Release|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.ActiveCfg = Release|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.Build.0 = Release|ARM64 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.ActiveCfg = Release|x64 @@ -2195,7 +1925,6 @@ Global {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32 {506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2203,7 +1932,6 @@ Global {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.Build.0 = AuditMode|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM.ActiveCfg = Debug|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.ActiveCfg = Debug|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.Build.0 = Debug|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.ActiveCfg = Debug|x64 @@ -2211,12 +1939,10 @@ Global {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.ActiveCfg = Debug|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.Build.0 = Debug|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|Any CPU.ActiveCfg = Release|Win32 - {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM.ActiveCfg = Release|Win32 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.ActiveCfg = Release|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.Build.0 = Release|ARM64 {416FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.ActiveCfg = Release|x64 @@ -2226,7 +1952,6 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.ActiveCfg = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.Build.0 = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|Any CPU.Deploy.0 = Release|x64 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|ARM64.ActiveCfg = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|ARM64.Build.0 = Release|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|ARM64.Deploy.0 = Release|x64 @@ -2236,7 +1961,6 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|x86.Deploy.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x64.ActiveCfg = Debug|x64 @@ -2246,12 +1970,10 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x86.Deploy.0 = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|x64.ActiveCfg = Release|x64 @@ -2261,14 +1983,12 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|x86.Deploy.0 = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.Build.0 = AuditMode|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.ActiveCfg = Debug|x64 @@ -2276,12 +1996,10 @@ Global {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.ActiveCfg = Release|x64 @@ -2289,14 +2007,12 @@ Global {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x64.ActiveCfg = Release|x64 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.AuditMode|x86.Build.0 = AuditMode|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x64.ActiveCfg = Debug|x64 @@ -2304,12 +2020,10 @@ Global {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-082C-4476-9F33-94B339494076}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x64.ActiveCfg = Release|x64 @@ -2317,14 +2031,12 @@ Global {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-082C-4476-9F33-94B339494076}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x64.ActiveCfg = AuditMode|x64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.AuditMode|x86.Build.0 = AuditMode|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|ARM.ActiveCfg = Debug|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|ARM64.ActiveCfg = Debug|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x64.ActiveCfg = Debug|x64 @@ -2332,12 +2044,10 @@ Global {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x86.ActiveCfg = Debug|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|ARM.ActiveCfg = Release|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x64.ActiveCfg = Release|x64 @@ -2345,12 +2055,10 @@ Global {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-9B68-456A-B13E-C8218070DC42}.Release|x86.Build.0 = Release|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.AuditMode|x64.ActiveCfg = Release|x64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.AuditMode|x86.ActiveCfg = Release|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|ARM.ActiveCfg = Debug|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|ARM64.ActiveCfg = Debug|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|ARM64.Build.0 = Debug|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|x64.ActiveCfg = Debug|x64 @@ -2358,12 +2066,10 @@ Global {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|x86.ActiveCfg = Debug|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Debug|x86.Build.0 = Debug|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|Any CPU.ActiveCfg = Release|Win32 - {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|ARM.ActiveCfg = Release|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|ARM64.ActiveCfg = Release|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|ARM64.Build.0 = Release|ARM64 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|x64.ActiveCfg = Release|x64 @@ -2371,9 +2077,6 @@ Global {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|x86.ActiveCfg = Release|Win32 {21B7EA5E-1EF8-49B6-AC07-11714AF0E37D}.Release|x86.Build.0 = Release|Win32 {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|Any CPU.ActiveCfg = Release|Any CPU - {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM.ActiveCfg = Debug|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM.Build.0 = Debug|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM.Deploy.0 = Debug|ARM {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM64.Build.0 = Debug|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|ARM64.Deploy.0 = Debug|ARM64 @@ -2384,9 +2087,6 @@ Global {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|x86.Build.0 = Debug|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.AuditMode|x86.Deploy.0 = Debug|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM.ActiveCfg = Debug|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM.Build.0 = Debug|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM.Deploy.0 = Debug|ARM {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM64.Build.0 = Debug|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|ARM64.Deploy.0 = Debug|ARM64 @@ -2397,14 +2097,10 @@ Global {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|x86.Build.0 = Debug|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Debug|x86.Deploy.0 = Debug|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Fuzzing|Any CPU.ActiveCfg = Release|Any CPU - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Fuzzing|ARM.ActiveCfg = Fuzzing|ARM {F75E29D0-D288-478B-8D83-2C190F321A3F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Fuzzing|x64.ActiveCfg = Release|x64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Fuzzing|x86.ActiveCfg = Release|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM.ActiveCfg = Release|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM.Build.0 = Release|ARM - {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM.Deploy.0 = Release|ARM {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM64.ActiveCfg = Release|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM64.Build.0 = Release|ARM64 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|ARM64.Deploy.0 = Release|ARM64 @@ -2415,7 +2111,6 @@ Global {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|x86.Build.0 = Release|x86 {F75E29D0-D288-478B-8D83-2C190F321A3F}.Release|x86.Deploy.0 = Release|x86 {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x64.ActiveCfg = Release|x64 @@ -2423,7 +2118,6 @@ Global {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.AuditMode|x86.Build.0 = AuditMode|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|ARM.ActiveCfg = Debug|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|ARM64.ActiveCfg = Debug|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|ARM64.Build.0 = Debug|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|x64.ActiveCfg = Debug|x64 @@ -2431,12 +2125,10 @@ Global {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|x86.ActiveCfg = Debug|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Debug|x86.Build.0 = Debug|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {43CE4CE5-0010-4B99-9569-672670D26E26}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|Any CPU.ActiveCfg = Release|Win32 - {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|ARM.ActiveCfg = Release|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|ARM64.ActiveCfg = Release|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|ARM64.Build.0 = Release|ARM64 {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|x64.ActiveCfg = Release|x64 @@ -2444,14 +2136,12 @@ Global {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|x86.ActiveCfg = Release|Win32 {43CE4CE5-0010-4B99-9569-672670D26E26}.Release|x86.Build.0 = Release|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|x64.ActiveCfg = Release|x64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.AuditMode|x86.Build.0 = AuditMode|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|ARM.ActiveCfg = Debug|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|ARM64.Build.0 = Debug|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|x64.ActiveCfg = Debug|x64 @@ -2459,12 +2149,10 @@ Global {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|x86.ActiveCfg = Debug|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Debug|x86.Build.0 = Debug|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|Any CPU.ActiveCfg = Release|Win32 - {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|ARM.ActiveCfg = Release|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|ARM64.ActiveCfg = Release|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|ARM64.Build.0 = Release|ARM64 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|x64.ActiveCfg = Release|x64 @@ -2472,14 +2160,12 @@ Global {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|x86.ActiveCfg = Release|Win32 {27B5AAEB-A548-44CF-9777-F8BAA32AF7AE}.Release|x86.Build.0 = Release|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|x64.ActiveCfg = AuditMode|x64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.AuditMode|x86.Build.0 = AuditMode|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|ARM.ActiveCfg = Debug|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|ARM64.Build.0 = Debug|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|x64.ActiveCfg = Debug|x64 @@ -2487,12 +2173,10 @@ Global {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|x86.ActiveCfg = Debug|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Debug|x86.Build.0 = Debug|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|Any CPU.ActiveCfg = Release|Win32 - {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|ARM.ActiveCfg = Release|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|ARM64.ActiveCfg = Release|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|ARM64.Build.0 = Release|ARM64 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x64.ActiveCfg = Release|x64 @@ -2500,7 +2184,6 @@ Global {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x86.ActiveCfg = Release|Win32 {68A10CD3-AA64-465B-AF5F-ED4E9700543C}.Release|x86.Build.0 = Release|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2508,7 +2191,6 @@ Global {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.AuditMode|x86.Build.0 = AuditMode|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|ARM.ActiveCfg = Debug|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|ARM64.ActiveCfg = Debug|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|ARM64.Build.0 = Debug|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|x64.ActiveCfg = Debug|x64 @@ -2516,13 +2198,11 @@ Global {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|x86.ActiveCfg = Debug|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Debug|x86.Build.0 = Debug|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|x64.Build.0 = Fuzzing|x64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|Any CPU.ActiveCfg = Release|Win32 - {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|ARM.ActiveCfg = Release|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|ARM64.ActiveCfg = Release|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|ARM64.Build.0 = Release|ARM64 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|x64.ActiveCfg = Release|x64 @@ -2530,35 +2210,29 @@ Global {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|x86.ActiveCfg = Release|Win32 {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF}.Release|x86.Build.0 = Release|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {05D9052F-D78F-478F-968A-2DE38A6DB996}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.AuditMode|x64.ActiveCfg = AuditMode|x64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {05D9052F-D78F-478F-968A-2DE38A6DB996}.Debug|ARM.ActiveCfg = Debug|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Debug|ARM64.ActiveCfg = Debug|ARM64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Debug|x64.ActiveCfg = Debug|x64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Debug|x86.ActiveCfg = Debug|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|x64.Build.0 = Fuzzing|x64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Release|Any CPU.ActiveCfg = Release|Win32 - {05D9052F-D78F-478F-968A-2DE38A6DB996}.Release|ARM.ActiveCfg = Release|Win32 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Release|ARM64.ActiveCfg = Release|ARM64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Release|x64.ActiveCfg = Release|x64 {05D9052F-D78F-478F-968A-2DE38A6DB996}.Release|x86.ActiveCfg = Release|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|x64.ActiveCfg = Release|x64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.AuditMode|x86.Build.0 = AuditMode|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|ARM.ActiveCfg = Debug|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|ARM64.ActiveCfg = Debug|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|ARM64.Build.0 = Debug|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|x64.ActiveCfg = Debug|x64 @@ -2566,12 +2240,10 @@ Global {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|x86.ActiveCfg = Debug|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Debug|x86.Build.0 = Debug|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|Any CPU.ActiveCfg = Release|Win32 - {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|ARM.ActiveCfg = Release|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|ARM64.ActiveCfg = Release|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|ARM64.Build.0 = Release|ARM64 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x64.ActiveCfg = Release|x64 @@ -2579,12 +2251,10 @@ Global {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.ActiveCfg = Release|Win32 {C323DAEE-B307-4C7B-ACE5-7293CBEFCB5B}.Release|x86.Build.0 = Release|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|x64.ActiveCfg = Debug|x64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.AuditMode|x86.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|ARM64.Build.0 = Debug|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x64.ActiveCfg = Debug|x64 @@ -2592,12 +2262,10 @@ Global {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x86.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Debug|x86.Build.0 = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|Any CPU.ActiveCfg = Debug|Win32 - {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|ARM.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|x64.ActiveCfg = Debug|x64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Fuzzing|x86.ActiveCfg = Debug|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|Any CPU.ActiveCfg = Release|Win32 - {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM.ActiveCfg = Release|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM64.ActiveCfg = Release|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|ARM64.Build.0 = Release|ARM64 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x64.ActiveCfg = Release|x64 @@ -2605,7 +2273,6 @@ Global {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x86.ActiveCfg = Release|Win32 {F19DACD5-0C6E-40DC-B6E4-767A3200542C}.Release|x86.Build.0 = Release|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2613,7 +2280,6 @@ Global {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.AuditMode|x86.Build.0 = AuditMode|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM.ActiveCfg = Debug|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.ActiveCfg = Debug|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|ARM64.Build.0 = Debug|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x64.ActiveCfg = Debug|x64 @@ -2621,7 +2287,6 @@ Global {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.ActiveCfg = Debug|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Debug|x86.Build.0 = Debug|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 @@ -2629,7 +2294,6 @@ Global {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Fuzzing|x86.Build.0 = Fuzzing|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|Any CPU.ActiveCfg = Release|Win32 - {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM.ActiveCfg = Release|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.ActiveCfg = Release|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|ARM64.Build.0 = Release|ARM64 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x64.ActiveCfg = Release|x64 @@ -2637,13 +2301,11 @@ Global {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.ActiveCfg = Release|Win32 {8222900C-8B6C-452A-91AC-BE95DB04B95F}.Release|x86.Build.0 = Release|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64 - {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|ARM.ActiveCfg = AuditMode|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|x64.ActiveCfg = AuditMode|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|x64.Build.0 = AuditMode|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|Any CPU.ActiveCfg = Debug|x64 - {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|ARM.ActiveCfg = Debug|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|ARM64.ActiveCfg = Debug|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|ARM64.Build.0 = Debug|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|x64.ActiveCfg = Debug|x64 @@ -2651,12 +2313,10 @@ Global {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|x86.ActiveCfg = Debug|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.Debug|x86.Build.0 = Debug|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|x64 - {06EC74CB-9A12-428C-B551-8537EC964726}.Fuzzing|ARM.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.Release|Any CPU.ActiveCfg = Release|x64 - {06EC74CB-9A12-428C-B551-8537EC964726}.Release|ARM.ActiveCfg = Release|x64 {06EC74CB-9A12-428C-B551-8537EC964726}.Release|ARM64.ActiveCfg = Release|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.Release|ARM64.Build.0 = Release|ARM64 {06EC74CB-9A12-428C-B551-8537EC964726}.Release|x64.ActiveCfg = Release|x64 @@ -2664,12 +2324,10 @@ Global {06EC74CB-9A12-428C-B551-8537EC964726}.Release|x86.ActiveCfg = Release|Win32 {06EC74CB-9A12-428C-B551-8537EC964726}.Release|x86.Build.0 = Release|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64 - {75C6F576-18E9-4566-978A-F0A301CAC090}.AuditMode|ARM.ActiveCfg = AuditMode|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.AuditMode|x64.ActiveCfg = AuditMode|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|Any CPU.ActiveCfg = Debug|x64 - {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|ARM.ActiveCfg = Debug|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|ARM64.ActiveCfg = Debug|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|ARM64.Build.0 = Debug|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|x64.ActiveCfg = Debug|x64 @@ -2677,13 +2335,11 @@ Global {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|x86.ActiveCfg = Debug|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Debug|x86.Build.0 = Debug|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|x64 - {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|ARM.ActiveCfg = Fuzzing|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|x64.Build.0 = Fuzzing|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|Any CPU.ActiveCfg = Release|x64 - {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|ARM.ActiveCfg = Release|x64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|ARM64.ActiveCfg = Release|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|ARM64.Build.0 = Release|ARM64 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x64.ActiveCfg = Release|x64 @@ -2691,7 +2347,6 @@ Global {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.ActiveCfg = Release|Win32 {75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.Build.0 = Release|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -2699,7 +2354,6 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.ActiveCfg = AuditMode|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.Build.0 = AuditMode|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM.ActiveCfg = Debug|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.ActiveCfg = Debug|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.Build.0 = Debug|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x64.ActiveCfg = Debug|x64 @@ -2707,7 +2361,6 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.ActiveCfg = Debug|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.Build.0 = Debug|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 @@ -2715,7 +2368,6 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.Build.0 = Fuzzing|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|Any CPU.ActiveCfg = Release|Win32 - {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM.ActiveCfg = Release|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.ActiveCfg = Release|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.Build.0 = Release|ARM64 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x64.ActiveCfg = Release|x64 @@ -2723,86 +2375,70 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.ActiveCfg = Release|Win32 {3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.Build.0 = Release|Win32 {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.AuditMode|Any CPU.ActiveCfg = Debug|Any CPU - {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.AuditMode|ARM.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.AuditMode|ARM64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.AuditMode|x64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.AuditMode|x86.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|ARM.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|x64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|x64.Build.0 = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Debug|x86.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Fuzzing|Any CPU.ActiveCfg = Debug|Any CPU - {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Fuzzing|ARM.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Fuzzing|ARM64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Fuzzing|x64.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Fuzzing|x86.ActiveCfg = Debug|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|Any CPU.Build.0 = Release|Any CPU - {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|ARM.ActiveCfg = Release|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|ARM64.ActiveCfg = Release|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x64.ActiveCfg = Release|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x64.Build.0 = Release|Any CPU {613CCB57-5FA9-48EF-80D0-6B1E319E20C4}.Release|x86.ActiveCfg = Release|Any CPU {37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|x64.ActiveCfg = Release|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.AuditMode|x86.ActiveCfg = Release|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM.ActiveCfg = Debug|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM64.ActiveCfg = Debug|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x64.ActiveCfg = Debug|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x86.ActiveCfg = Debug|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|Any CPU.ActiveCfg = Release|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM.ActiveCfg = Release|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.ActiveCfg = Release|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.ActiveCfg = Release|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.ActiveCfg = Release|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|x64.ActiveCfg = Release|x64 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|x86.ActiveCfg = Release|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|ARM.ActiveCfg = Debug|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|x64.ActiveCfg = Debug|x64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|x86.ActiveCfg = Debug|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|Any CPU.ActiveCfg = Release|Win32 - {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|ARM.ActiveCfg = Release|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|ARM64.ActiveCfg = Release|ARM64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|x64.ActiveCfg = Release|x64 {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|x86.ActiveCfg = Release|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {328729E9-6723-416E-9C98-951F1473BBE1}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {328729E9-6723-416E-9C98-951F1473BBE1}.AuditMode|x64.ActiveCfg = Release|x64 {328729E9-6723-416E-9C98-951F1473BBE1}.AuditMode|x86.ActiveCfg = Release|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {328729E9-6723-416E-9C98-951F1473BBE1}.Debug|ARM.ActiveCfg = Debug|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Debug|ARM64.ActiveCfg = Debug|ARM64 {328729E9-6723-416E-9C98-951F1473BBE1}.Debug|x64.ActiveCfg = Debug|x64 {328729E9-6723-416E-9C98-951F1473BBE1}.Debug|x86.ActiveCfg = Debug|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {328729E9-6723-416E-9C98-951F1473BBE1}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 {328729E9-6723-416E-9C98-951F1473BBE1}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 {328729E9-6723-416E-9C98-951F1473BBE1}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|Any CPU.ActiveCfg = Release|Win32 - {328729E9-6723-416E-9C98-951F1473BBE1}.Release|ARM.ActiveCfg = Release|Win32 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|ARM64.ActiveCfg = Release|ARM64 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|x64.ActiveCfg = Release|x64 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|x86.ActiveCfg = Release|Win32 diff --git a/src/tools/MonarchPeasantPackage/MonarchPeasantPackage.wapproj b/src/tools/MonarchPeasantPackage/MonarchPeasantPackage.wapproj index ded757cf998..66580b81e64 100644 --- a/src/tools/MonarchPeasantPackage/MonarchPeasantPackage.wapproj +++ b/src/tools/MonarchPeasantPackage/MonarchPeasantPackage.wapproj @@ -20,14 +20,6 @@ Release x64 - - Debug - ARM - - - Release - ARM - Debug ARM64 From 9654fc6afee6169c48bc8485bdd54884d6801b0f Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 21 Feb 2024 21:15:06 +0000 Subject: [PATCH 19/50] Complete the 8-bit interface architecture (#16547) ## Summary of the Pull Request This PR adds support for the missing operations that are required in the 8-bit interface architecture extension. `DECAUPSS` - Assign User-Preference Supplemental Set `DECRQUPSS` - Request User-Preference Supplemental Set `ACS` - Announce Code Structure for ANSI levels 1, 2, and 3 ## Detailed Description of the Pull Request / Additional comments For the UPSS assignment there's a new `wstring_view` in `TerminalOutput` that tracks the current mapping table for the character set, and a new `VTID` field tracking the ID so it can be queried. The ANSI conformance just required a couple of calls to existing methods to designate the appropriate character sets and GL/GR maps. And now that we've implemented everything that's required, I've updated our device attributes report (`DA1`) to indicate support for the 8-bit interface architecture (feature 14). This PR also addresses some issues with the way the 8-bit GR capability is managed. Previously a soft reset (`DECSTR`) would return the code page to its startup state, and the restore cursor operation (`DECRC`) could have a similar effect. This was a problem for 8-bit apps that weren't expecting that behavior. I've now made it so those operations no longer have any effect on the code page, and the same applies to the C1 parsing option (`DECAC1`). The only way to restore the code page and C1 parsing to their startup state now is with a hard reset (`RIS`). ## Validation Steps Performed I've added unit tests covering the `DECAUPSS` and `DECRQUPSS` sequences, and I've also manually tested their behavior in Vttest. ## PR Checklist - [x] Closes #16546 - [x] Tests added/passed --- .github/actions/spelling/expect/expect.txt | 2 + src/terminal/adapter/DispatchTypes.hpp | 2 +- src/terminal/adapter/FontBuffer.cpp | 6 +- src/terminal/adapter/FontBuffer.hpp | 6 +- src/terminal/adapter/ITermDispatch.hpp | 6 +- src/terminal/adapter/adaptDispatch.cpp | 132 ++++++++++++---- src/terminal/adapter/adaptDispatch.hpp | 10 +- src/terminal/adapter/termDispatch.hpp | 6 +- src/terminal/adapter/terminalOutput.cpp | 81 ++++++++-- src/terminal/adapter/terminalOutput.hpp | 13 +- .../adapter/ut_adapter/adapterTest.cpp | 145 +++++++++++++++++- .../parser/OutputStateMachineEngine.cpp | 15 ++ .../parser/OutputStateMachineEngine.hpp | 5 + 13 files changed, 368 insertions(+), 61 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index b85247338c0..5a1ab96a5b3 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -429,6 +429,7 @@ DECRQM DECRQPSR DECRQSS DECRQTSR +DECRQUPSS DECRSPS decrst DECSACE @@ -1901,6 +1902,7 @@ UPDATEDISPLAY UPDOWN UPKEY UPSS +upss uregex URegular usebackq diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index a0cabb0fde8..52852bed6a2 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -638,7 +638,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes FullCell = 2 }; - enum class DrcsCharsetSize : VTInt + enum class CharsetSize : VTInt { Size94 = 0, Size96 = 1 diff --git a/src/terminal/adapter/FontBuffer.cpp b/src/terminal/adapter/FontBuffer.cpp index c7f205f5a10..c1962b8190d 100644 --- a/src/terminal/adapter/FontBuffer.cpp +++ b/src/terminal/adapter/FontBuffer.cpp @@ -153,14 +153,14 @@ bool FontBuffer::SetAttributes(const DispatchTypes::DrcsCellMatrix cellMatrix, } bool FontBuffer::SetStartChar(const VTParameter startChar, - const DispatchTypes::DrcsCharsetSize charsetSize) noexcept + const DispatchTypes::CharsetSize charsetSize) noexcept { switch (charsetSize) { - case DispatchTypes::DrcsCharsetSize::Size94: + case DispatchTypes::CharsetSize::Size94: _startChar = startChar.value_or(1); break; - case DispatchTypes::DrcsCharsetSize::Size96: + case DispatchTypes::CharsetSize::Size96: _startChar = startChar.value_or(0); break; default: diff --git a/src/terminal/adapter/FontBuffer.hpp b/src/terminal/adapter/FontBuffer.hpp index f520c7bc958..0141dbf1996 100644 --- a/src/terminal/adapter/FontBuffer.hpp +++ b/src/terminal/adapter/FontBuffer.hpp @@ -26,7 +26,7 @@ namespace Microsoft::Console::VirtualTerminal const DispatchTypes::DrcsFontSet fontSet, const DispatchTypes::DrcsFontUsage fontUsage) noexcept; bool SetStartChar(const VTParameter startChar, - const DispatchTypes::DrcsCharsetSize charsetSize) noexcept; + const DispatchTypes::CharsetSize charsetSize) noexcept; void AddSixelData(const wchar_t ch); bool FinalizeSixelData(); @@ -75,8 +75,8 @@ namespace Microsoft::Console::VirtualTerminal VTInt _columnsPerPage; bool _isTextFont; - DispatchTypes::DrcsCharsetSize _charsetSize; - DispatchTypes::DrcsCharsetSize _pendingCharsetSize; + DispatchTypes::CharsetSize _charsetSize; + DispatchTypes::CharsetSize _pendingCharsetSize; VTID _charsetId{ 0 }; VTID _pendingCharsetId{ 0 }; bool _charsetIdInitialized; diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 1b847d32ba0..0c46f975a8e 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -114,6 +114,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool LockingShiftRight(const VTInt gsetNumber) = 0; // LS1R, LS2R, LS3R virtual bool SingleShift(const VTInt gsetNumber) = 0; // SS2, SS3 virtual bool AcceptC1Controls(const bool enabled) = 0; // DECAC1 + virtual bool AnnounceCodeStructure(const VTInt ansiLevel) = 0; // ACS virtual bool SoftReset() = 0; // DECSTR virtual bool HardReset() = 0; // RIS @@ -147,7 +148,10 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch const DispatchTypes::DrcsFontSet fontSet, const DispatchTypes::DrcsFontUsage fontUsage, const VTParameter cellHeight, - const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD + const DispatchTypes::CharsetSize charsetSize) = 0; // DECDLD + + virtual bool RequestUserPreferenceCharset() = 0; // DECRQUPSS + virtual StringHandler AssignUserPreferenceCharset(const DispatchTypes::CharsetSize charsetSize) = 0; // DECAUPSS virtual StringHandler DefineMacro(const VTInt macroId, const DispatchTypes::MacroDeleteControl deleteControl, diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index fb504c20200..323707520bd 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -524,8 +524,6 @@ bool AdaptDispatch::CursorSaveState() savedCursorState.IsOriginModeRelative = _modes.test(Mode::Origin); savedCursorState.Attributes = attributes; savedCursorState.TermOutput = _termOutput; - savedCursorState.C1ControlsAccepted = _api.GetStateMachine().GetParserMode(StateMachine::Mode::AcceptC1); - savedCursorState.CodePage = _api.GetConsoleOutputCP(); return true; } @@ -557,17 +555,8 @@ bool AdaptDispatch::CursorRestoreState() // Restore text attributes. _api.SetTextAttributes(savedCursorState.Attributes); - // Restore designated character set. - _termOutput = savedCursorState.TermOutput; - - // Restore the parsing state of C1 control codes. - AcceptC1Controls(savedCursorState.C1ControlsAccepted); - - // Restore the code page if it was previously saved. - if (savedCursorState.CodePage != 0) - { - _api.SetConsoleOutputCP(savedCursorState.CodePage); - } + // Restore designated character sets. + _termOutput.RestoreFrom(savedCursorState.TermOutput); return true; } @@ -1541,21 +1530,22 @@ bool AdaptDispatch::DeviceAttributes() // 1 = 132 column mode (ConHost only) // 6 = Selective erase // 7 = Soft fonts + // 14 = 8-bit interface architecture // 21 = Horizontal scrolling // 22 = Color text // 23 = Greek character sets // 24 = Turkish character sets // 28 = Rectangular area operations // 32 = Text macros - // 42 = ISO Latin - 2 character set + // 42 = ISO Latin-2 character set if (_api.IsConsolePty()) { - _api.ReturnResponse(L"\x1b[?61;6;7;21;22;23;24;28;32;42c"); + _api.ReturnResponse(L"\x1b[?61;6;7;14;21;22;23;24;28;32;42c"); } else { - _api.ReturnResponse(L"\x1b[?61;1;6;7;21;22;23;24;28;32;42c"); + _api.ReturnResponse(L"\x1b[?61;1;6;7;14;21;22;23;24;28;32;42c"); } return true; } @@ -2233,7 +2223,7 @@ bool AdaptDispatch::SetAnsiMode(const bool ansiMode) { // When an attempt is made to update the mode, the designated character sets // need to be reset to defaults, even if the mode doesn't actually change. - _termOutput = {}; + _termOutput.SoftReset(); _api.GetStateMachine().SetParserMode(StateMachine::Mode::Ansi, ansiMode); _terminalInput.SetInputMode(TerminalInput::Mode::Ansi, ansiMode); @@ -2980,6 +2970,34 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled) return true; } +//Routine Description: +// ACS - Announces the ANSI conformance level for subsequent data exchange. +// This requires certain character sets to be mapped into the terminal's +// G-sets and in-use tables. +//Arguments: +// - ansiLevel - the expected conformance level +// Return value: +// - True if handled successfully. False otherwise. +bool AdaptDispatch::AnnounceCodeStructure(const VTInt ansiLevel) +{ + // Levels 1 and 2 require ASCII in G0/GL and Latin-1 in G1/GR. + // Level 3 only requires ASCII in G0/GL. + switch (ansiLevel) + { + case 1: + case 2: + Designate96Charset(1, VTID("A")); // Latin-1 designated as G1 + LockingShiftRight(1); // G1 mapped into GR + [[fallthrough]]; + case 3: + Designate94Charset(0, VTID("B")); // ASCII designated as G0 + LockingShift(0); // G0 mapped into GL + return true; + default: + return false; + } +} + //Routine Description: // Soft Reset - Perform a soft reset. See http://www.vt100.net/docs/vt510-rm/DECSTR.html // The following table lists everything that should be done, 'X's indicate the ones that @@ -3000,7 +3018,7 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled) // X Select graphic rendition SGR Normal rendition. // X Select character attribute DECSCA Normal (erasable by DECSEL and DECSED). // X Save cursor state DECSC Home position. -// Assign user preference DECAUPSS Set selected in Set-Up. +// X Assign user preference DECAUPSS Always Latin-1 (not configurable). // supplemental set // Select active DECSASD Main display. // status display @@ -3027,14 +3045,7 @@ bool AdaptDispatch::SoftReset() // Left margin = 1; right margin = page width. _DoSetLeftRightScrollingMargins(0, 0); - _termOutput = {}; // Reset all character set designations. - if (_initialCodePage.has_value()) - { - // Restore initial code page if previously changed by a DOCS sequence. - _api.SetConsoleOutputCP(_initialCodePage.value()); - } - // Disable parsing of C1 control codes. - AcceptC1Controls(false); + _termOutput.SoftReset(); // Reset all character set designations. SetGraphicsRendition({}); // Normal rendition. SetCharacterProtectionAttribute({}); // Default (unprotected) @@ -3045,6 +3056,12 @@ bool AdaptDispatch::SoftReset() _savedCursorState.at(0) = {}; // Main buffer _savedCursorState.at(1) = {}; // Alt buffer + // The TerminalOutput state in these buffers must be reset to + // the same state as the _termOutput instance, which is not + // necessarily equivalent to a full reset. + _savedCursorState.at(0).TermOutput = _termOutput; + _savedCursorState.at(1).TermOutput = _termOutput; + return !_api.IsConsolePty(); } @@ -3079,6 +3096,16 @@ bool AdaptDispatch::HardReset() _usingAltBuffer = false; } + // Completely reset the TerminalOutput state. + _termOutput = {}; + if (_initialCodePage.has_value()) + { + // Restore initial code page if previously changed by a DOCS sequence. + _api.SetConsoleOutputCP(_initialCodePage.value()); + } + // Disable parsing of C1 control codes. + AcceptC1Controls(false); + // Sets the SGR state to normal - this must be done before EraseInDisplay // to ensure that it clears with the default background color. SoftReset(); @@ -3885,7 +3912,7 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, const DispatchTypes::DrcsFontSet fontSet, const DispatchTypes::DrcsFontUsage fontUsage, const VTParameter cellHeight, - const DispatchTypes::DrcsCharsetSize charsetSize) + const DispatchTypes::CharsetSize charsetSize) { // The font buffer is created on demand. if (!_fontBuffer) @@ -3932,7 +3959,7 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, // We also need to inform the character set mapper of the ID that // will map to this font (we only support one font buffer so there // will only ever be one active dynamic character set). - if (charsetSize == DispatchTypes::DrcsCharsetSize::Size96) + if (charsetSize == DispatchTypes::CharsetSize::Size96) { _termOutput.SetDrcs96Designation(_fontBuffer->GetDesignation()); } @@ -3957,7 +3984,7 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber, // - // Return value: // - a function to receive the data or nullptr if the initial flush fails -ITermDispatch::StringHandler AdaptDispatch::_CreateDrcsPassthroughHandler(const DispatchTypes::DrcsCharsetSize charsetSize) +ITermDispatch::StringHandler AdaptDispatch::_CreateDrcsPassthroughHandler(const DispatchTypes::CharsetSize charsetSize) { const auto defaultPassthrough = _CreatePassthroughHandler(); if (defaultPassthrough) @@ -3980,7 +4007,7 @@ ITermDispatch::StringHandler AdaptDispatch::_CreateDrcsPassthroughHandler(const { // Once the DECDLD sequence is finished, we also output an SCS // sequence to map the character set into the G1 table. - const auto charset96 = charsetSize == DispatchTypes::DrcsCharsetSize::Size96; + const auto charset96 = charsetSize == DispatchTypes::CharsetSize::Size96; engine.ActionPassThroughString(charset96 ? L"\033-@" : L"\033)@"); } return true; @@ -3989,6 +4016,51 @@ ITermDispatch::StringHandler AdaptDispatch::_CreateDrcsPassthroughHandler(const return nullptr; } +// Method Description: +// - DECRQUPSS - Request the user-preference supplemental character set. +// Arguments: +// - None +// Return Value: +// - True +bool AdaptDispatch::RequestUserPreferenceCharset() +{ + const auto size = _termOutput.GetUserPreferenceCharsetSize(); + const auto id = _termOutput.GetUserPreferenceCharsetId(); + _api.ReturnResponse(fmt::format(FMT_COMPILE(L"\033P{}!u{}\033\\"), (size == 96 ? 1 : 0), id.ToString())); + return true; +} + +// Method Description: +// - DECAUPSS - Assigns the user-preference supplemental character set. +// Arguments: +// - charsetSize - Whether the character set is 94 or 96 characters. +// Return Value: +// - a function to parse the character set ID +ITermDispatch::StringHandler AdaptDispatch::AssignUserPreferenceCharset(const DispatchTypes::CharsetSize charsetSize) +{ + return [this, charsetSize, idBuilder = VTIDBuilder{}](const auto ch) mutable { + if (ch >= L'\x20' && ch <= L'\x2f') + { + idBuilder.AddIntermediate(ch); + } + else if (ch >= L'\x30' && ch <= L'\x7e') + { + const auto id = idBuilder.Finalize(ch); + switch (charsetSize) + { + case DispatchTypes::CharsetSize::Size94: + _termOutput.AssignUserPreferenceCharset(id, false); + break; + case DispatchTypes::CharsetSize::Size96: + _termOutput.AssignUserPreferenceCharset(id, true); + break; + } + return false; + } + return true; + }; +} + // Method Description: // - DECDMAC - Defines a string of characters as a macro that can later be // invoked with a DECINVM sequence. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 0a24d01577d..2a22f87e340 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -113,6 +113,7 @@ namespace Microsoft::Console::VirtualTerminal bool LockingShiftRight(const VTInt gsetNumber) override; // LS1R, LS2R, LS3R bool SingleShift(const VTInt gsetNumber) noexcept override; // SS2, SS3 bool AcceptC1Controls(const bool enabled) override; // DECAC1 + bool AnnounceCodeStructure(const VTInt ansiLevel) override; // ACS bool SoftReset() override; // DECSTR bool HardReset() override; // RIS bool ScreenAlignmentPattern() override; // DECALN @@ -149,7 +150,10 @@ namespace Microsoft::Console::VirtualTerminal const DispatchTypes::DrcsFontSet fontSet, const DispatchTypes::DrcsFontUsage fontUsage, const VTParameter cellHeight, - const DispatchTypes::DrcsCharsetSize charsetSize) override; // DECDLD + const DispatchTypes::CharsetSize charsetSize) override; // DECDLD + + bool RequestUserPreferenceCharset() override; // DECRQUPSS + StringHandler AssignUserPreferenceCharset(const DispatchTypes::CharsetSize charsetSize) override; // DECAUPSS StringHandler DefineMacro(const VTInt macroId, const DispatchTypes::MacroDeleteControl deleteControl, @@ -189,8 +193,6 @@ namespace Microsoft::Console::VirtualTerminal bool IsOriginModeRelative = false; TextAttribute Attributes = {}; TerminalOutput TermOutput = {}; - bool C1ControlsAccepted = false; - unsigned int CodePage = 0; }; struct Offset { @@ -268,7 +270,7 @@ namespace Microsoft::Console::VirtualTerminal void _ReportTabStops(); StringHandler _RestoreTabStops(); - StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::DrcsCharsetSize charsetSize); + StringHandler _CreateDrcsPassthroughHandler(const DispatchTypes::CharsetSize charsetSize); StringHandler _CreatePassthroughHandler(); std::vector _tabStopColumns; diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 122b48820bb..5c41999dc8a 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -107,6 +107,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool LockingShiftRight(const VTInt /*gsetNumber*/) override { return false; } // LS1R, LS2R, LS3R bool SingleShift(const VTInt /*gsetNumber*/) override { return false; } // SS2, SS3 bool AcceptC1Controls(const bool /*enabled*/) override { return false; } // DECAC1 + bool AnnounceCodeStructure(const VTInt /*ansiLevel*/) override { return false; } // ACS bool SoftReset() override { return false; } // DECSTR bool HardReset() override { return false; } // RIS @@ -140,7 +141,10 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons const DispatchTypes::DrcsFontSet /*fontSet*/, const DispatchTypes::DrcsFontUsage /*fontUsage*/, const VTParameter /*cellHeight*/, - const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; } // DECDLD + const DispatchTypes::CharsetSize /*charsetSize*/) override { return nullptr; } // DECDLD + + bool RequestUserPreferenceCharset() override { return false; } // DECRQUPSS + StringHandler AssignUserPreferenceCharset(const DispatchTypes::CharsetSize /*charsetSize*/) override { return nullptr; } // DECAUPSS StringHandler DefineMacro(const VTInt /*macroId*/, const DispatchTypes::MacroDeleteControl /*deleteControl*/, diff --git a/src/terminal/adapter/terminalOutput.cpp b/src/terminal/adapter/terminalOutput.cpp index f18f3512c64..4f39fc3558e 100644 --- a/src/terminal/adapter/terminalOutput.cpp +++ b/src/terminal/adapter/terminalOutput.cpp @@ -8,22 +8,73 @@ using namespace Microsoft::Console::VirtualTerminal; -TerminalOutput::TerminalOutput() noexcept +TerminalOutput::TerminalOutput(const bool grEnabled) noexcept : + _upssId{ VTID("A") }, + _upssTranslationTable{ Latin1 }, + _grTranslationEnabled{ grEnabled } { // By default we set all of the G-sets to ASCII, so if someone accidentally - // triggers a locking shift, they won't end up with Latin1 in the GL table, + // triggers a locking shift, they won't end up with UPSS in the GL table, // making their system unreadable. If ISO-2022 encoding is selected, though, - // we'll reset the G2 and G3 tables to Latin1, so that 8-bit apps will get a + // we'll reset the G2 and G3 tables to UPSS, so that 8-bit apps will get a // more meaningful character mapping by default. This is triggered by a DOCS // sequence, which will call the EnableGrTranslation method below. + const auto grTranslationTable = grEnabled ? _upssTranslationTable : Ascii; + const auto grId = grEnabled ? VTID("<") : VTID("B"); _gsetTranslationTables.at(0) = Ascii; _gsetTranslationTables.at(1) = Ascii; - _gsetTranslationTables.at(2) = Ascii; - _gsetTranslationTables.at(3) = Ascii; + _gsetTranslationTables.at(2) = grTranslationTable; + _gsetTranslationTables.at(3) = grTranslationTable; _gsetIds.at(0) = VTID("B"); _gsetIds.at(1) = VTID("B"); - _gsetIds.at(2) = VTID("B"); - _gsetIds.at(3) = VTID("B"); + _gsetIds.at(2) = grId; + _gsetIds.at(3) = grId; +} + +void TerminalOutput::SoftReset() noexcept +{ + // For a soft reset we want to reinitialize the character set designations, + // but retain the GR translation functionality if it's currently enabled. + *this = { _grTranslationEnabled }; +} + +void TerminalOutput::RestoreFrom(const TerminalOutput& savedState) noexcept +{ + // When restoring from a saved instance, we want to preserve the GR + // translation functionality if it's currently enabled. + const auto preserveGrTranslation = _grTranslationEnabled; + *this = savedState; + _grTranslationEnabled = preserveGrTranslation; +} + +bool TerminalOutput::AssignUserPreferenceCharset(const VTID charset, const bool size96) +{ + const auto translationTable = size96 ? _LookupTranslationTable96(charset) : _LookupTranslationTable94(charset); + RETURN_BOOL_IF_FALSE(!translationTable.empty()); + _upssId = charset; + _upssTranslationTable = translationTable; + // Any G-set mapped to UPSS will need its translation table updated. + for (auto gset = 0; gset < 4; gset++) + { + if (_gsetIds.at(gset) == VTID("<")) + { + _gsetTranslationTables.at(gset) = _upssTranslationTable; + } + } + // We also reapply the locking shifts in case they need to be updated. + LockingShift(_glSetNumber); + LockingShiftRight(_grSetNumber); + return true; +} + +VTID TerminalOutput::GetUserPreferenceCharsetId() const noexcept +{ + return _upssId; +} + +size_t TerminalOutput::GetUserPreferenceCharsetSize() const noexcept +{ + return _upssTranslationTable.size() == 96 ? 96 : 94; } bool TerminalOutput::Designate94Charset(size_t gsetNumber, const VTID charset) @@ -124,14 +175,17 @@ bool TerminalOutput::NeedToTranslate() const noexcept return !_glTranslationTable.empty() || !_grTranslationTable.empty() || _ssSetNumber != 0; } -void TerminalOutput::EnableGrTranslation(boolean enabled) +void TerminalOutput::EnableGrTranslation(const bool enabled) { _grTranslationEnabled = enabled; - // The default table for G2 and G3 is Latin1 when GR translation is enabled, + // The default table for G2 and G3 is UPSS when GR translation is enabled, // and ASCII when disabled. The reason for this is explained in the constructor. - const auto defaultTranslationTable = enabled ? std::wstring_view{ Latin1 } : std::wstring_view{ Ascii }; + const auto defaultTranslationTable = enabled ? _upssTranslationTable : Ascii; + const auto defaultId = enabled ? VTID("<") : VTID("B"); _gsetTranslationTables.at(2) = defaultTranslationTable; _gsetTranslationTables.at(3) = defaultTranslationTable; + _gsetIds.at(2) = defaultId; + _gsetIds.at(3) = defaultId; // We need to reapply the locking shifts in case the underlying G-sets have changed. LockingShift(_glSetNumber); LockingShiftRight(_grSetNumber); @@ -184,8 +238,8 @@ const std::wstring_view TerminalOutput::_LookupTranslationTable94(const VTID cha case VTID("0"): // DEC Special Graphics case VTID("2"): // Alternate Character ROM Special Graphics return DecSpecialGraphics; - case VTID("<"): // DEC Supplemental - return DecSupplemental; + case VTID("<"): // User-Preference Supplemental + return _upssTranslationTable; case VTID("A"): // British NRCS return BritishNrcs; case VTID("4"): // Dutch NRCS @@ -253,8 +307,9 @@ const std::wstring_view TerminalOutput::_LookupTranslationTable96(const VTID cha switch (charset) { case VTID("A"): // ISO Latin-1 Supplemental - case VTID("<"): // (UPSS when assigned to Latin-1) return Latin1; + case VTID("<"): // User-Preference Supplemental + return _upssTranslationTable; case VTID("B"): // ISO Latin-2 Supplemental return Latin2; case VTID("L"): // ISO Latin-Cyrillic Supplemental diff --git a/src/terminal/adapter/terminalOutput.hpp b/src/terminal/adapter/terminalOutput.hpp index 3b77a3da612..5e6fff29c71 100644 --- a/src/terminal/adapter/terminalOutput.hpp +++ b/src/terminal/adapter/terminalOutput.hpp @@ -23,8 +23,13 @@ namespace Microsoft::Console::VirtualTerminal class TerminalOutput sealed { public: - TerminalOutput() noexcept; + TerminalOutput(const bool grEnabled = false) noexcept; + void SoftReset() noexcept; + void RestoreFrom(const TerminalOutput& savedState) noexcept; + bool AssignUserPreferenceCharset(const VTID charset, const bool size96); + VTID GetUserPreferenceCharsetId() const noexcept; + size_t GetUserPreferenceCharsetSize() const noexcept; wchar_t TranslateKey(const wchar_t wch) const noexcept; bool Designate94Charset(const size_t gsetNumber, const VTID charset); bool Designate96Charset(const size_t gsetNumber, const VTID charset); @@ -39,7 +44,7 @@ namespace Microsoft::Console::VirtualTerminal size_t GetRightSetNumber() const noexcept; bool IsSingleShiftPending(const size_t gsetNumber) const noexcept; bool NeedToTranslate() const noexcept; - void EnableGrTranslation(boolean enabled); + void EnableGrTranslation(const bool enabled); private: const std::wstring_view _LookupTranslationTable94(const VTID charset) const; @@ -47,6 +52,8 @@ namespace Microsoft::Console::VirtualTerminal bool _SetTranslationTable(const size_t gsetNumber, const std::wstring_view translationTable); void _ReplaceDrcsTable(const std::wstring_view oldTable, const std::wstring_view newTable); + VTID _upssId; + std::wstring_view _upssTranslationTable; std::array _gsetTranslationTables; std::array _gsetIds; size_t _glSetNumber = 0; @@ -54,7 +61,7 @@ namespace Microsoft::Console::VirtualTerminal std::wstring_view _glTranslationTable; std::wstring_view _grTranslationTable; mutable size_t _ssSetNumber = 0; - boolean _grTranslationEnabled = false; + bool _grTranslationEnabled = false; VTID _drcsId = 0; std::wstring_view _drcsTranslationTable; }; diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index a1e775d3dfb..da8b7a35d3a 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -1716,7 +1716,7 @@ class AdapterTest _testGetSet->PrepData(); VERIFY_IS_TRUE(_pDispatch->DeviceAttributes()); - auto pwszExpectedResponse = L"\x1b[?61;1;6;7;21;22;23;24;28;32;42c"; + auto pwszExpectedResponse = L"\x1b[?61;1;6;7;14;21;22;23;24;28;32;42c"; _testGetSet->ValidateInputEvent(pwszExpectedResponse); Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work."); @@ -2275,6 +2275,7 @@ class AdapterTest _testGetSet->ValidateInputEvent(L"\033P1$u1;100;1;_;A;I;1;3;@;BBBB\033\\"); Log::Comment(L"94 charset designations"); + termOutput.AssignUserPreferenceCharset(VTID("%5"), false); _pDispatch->Designate94Charset(0, "%5"); _pDispatch->Designate94Charset(1, "<"); _pDispatch->Designate94Charset(2, "0"); @@ -2794,7 +2795,7 @@ class AdapterTest const auto cellMatrix = static_cast(cmw); RETURN_BOOL_IF_FALSE(fontBuffer.SetEraseControl(DispatchTypes::DrcsEraseControl::AllChars)); RETURN_BOOL_IF_FALSE(fontBuffer.SetAttributes(cellMatrix, cmh, ss, u)); - RETURN_BOOL_IF_FALSE(fontBuffer.SetStartChar(0, DispatchTypes::DrcsCharsetSize::Size94)); + RETURN_BOOL_IF_FALSE(fontBuffer.SetStartChar(0, DispatchTypes::CharsetSize::Size94)); fontBuffer.AddSixelData(L'B'); // Charset identifier for (auto ch : data) @@ -3037,6 +3038,146 @@ class AdapterTest VERIFY_IS_FALSE(_stateMachine->GetParserMode(StateMachine::Mode::AcceptC1)); } + TEST_METHOD(AssignUserPreferenceCharsets) + { + const auto assignCharset = [=](const auto charsetSize, const std::wstring_view charsetId = {}) { + const auto stringHandler = _pDispatch->AssignUserPreferenceCharset(charsetSize); + for (auto ch : charsetId) + { + stringHandler(ch); + } + stringHandler(L'\033'); // String terminator + }; + auto& termOutput = _pDispatch->_termOutput; + termOutput.SoftReset(); + + Log::Comment(L"DECAUPSS: DEC Supplemental"); + assignCharset(DispatchTypes::CharsetSize::Size94, L"%5"); + VERIFY_ARE_EQUAL(VTID("%5"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(94u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: DEC Greek"); + assignCharset(DispatchTypes::CharsetSize::Size94, L"\"?"); + VERIFY_ARE_EQUAL(VTID("\"?"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(94u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: DEC Hebrew"); + assignCharset(DispatchTypes::CharsetSize::Size94, L"\"4"); + VERIFY_ARE_EQUAL(VTID("\"4"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(94u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: DEC Turkish"); + assignCharset(DispatchTypes::CharsetSize::Size94, L"%0"); + VERIFY_ARE_EQUAL(VTID("%0"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(94u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: DEC Cyrillic"); + assignCharset(DispatchTypes::CharsetSize::Size94, L"&4"); + VERIFY_ARE_EQUAL(VTID("&4"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(94u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-1"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"A"); + VERIFY_ARE_EQUAL(VTID("A"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-2"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"B"); + VERIFY_ARE_EQUAL(VTID("B"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-Greek"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"F"); + VERIFY_ARE_EQUAL(VTID("F"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-Hebrew"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"H"); + VERIFY_ARE_EQUAL(VTID("H"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-Cyrillic"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"L"); + VERIFY_ARE_EQUAL(VTID("L"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + + Log::Comment(L"DECAUPSS: ISO Latin-5"); + assignCharset(DispatchTypes::CharsetSize::Size96, L"M"); + VERIFY_ARE_EQUAL(VTID("M"), termOutput.GetUserPreferenceCharsetId()); + VERIFY_ARE_EQUAL(96u, termOutput.GetUserPreferenceCharsetSize()); + } + + TEST_METHOD(RequestUserPreferenceCharsets) + { + auto& termOutput = _pDispatch->_termOutput; + + Log::Comment(L"DECRQUPSS: DEC Supplemental"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("%5"), false)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P0!u%5\033\\"); + + Log::Comment(L"DECRQUPSS: DEC Greek"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("\"?"), false)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P0!u\"?\033\\"); + + Log::Comment(L"DECRQUPSS: DEC Hebrew"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("\"4"), false)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P0!u\"4\033\\"); + + Log::Comment(L"DECRQUPSS: DEC Turkish"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("%0"), false)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P0!u%0\033\\"); + + Log::Comment(L"DECRQUPSS: DEC Cyrillic"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("&4"), false)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P0!u&4\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-1"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("A"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uA\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-2"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("B"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uB\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-Greek"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("F"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uF\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-Hebrew"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("H"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uH\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-Cyrillic"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("L"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uL\033\\"); + + Log::Comment(L"DECRQUPSS: ISO Latin-5"); + _testGetSet->PrepData(); + VERIFY_IS_TRUE(termOutput.AssignUserPreferenceCharset(VTID("M"), true)); + _pDispatch->RequestUserPreferenceCharset(); + _testGetSet->ValidateInputEvent(L"\033P1!uM\033\\"); + } + TEST_METHOD(MacroDefinitions) { const auto getMacroText = [&](const auto id) { diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 9d247bfc4fe..143734bd62b 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -274,6 +274,15 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id) case EscActionCodes::DECAC1_AcceptC1Controls: success = _dispatch->AcceptC1Controls(true); break; + case EscActionCodes::ACS_AnsiLevel1: + success = _dispatch->AnnounceCodeStructure(1); + break; + case EscActionCodes::ACS_AnsiLevel2: + success = _dispatch->AnnounceCodeStructure(2); + break; + case EscActionCodes::ACS_AnsiLevel3: + success = _dispatch->AnnounceCodeStructure(3); + break; case EscActionCodes::DECDHL_DoubleHeightLineTop: success = _dispatch->SetLineRendition(LineRendition::DoubleHeightTop); break; @@ -635,6 +644,9 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete case CsiActionCodes::DECSERA_SelectiveEraseRectangularArea: success = _dispatch->SelectiveEraseRectangularArea(parameters.at(0), parameters.at(1), parameters.at(2).value_or(0), parameters.at(3).value_or(0)); break; + case CsiActionCodes::DECRQUPSS_RequestUserPreferenceSupplementalSet: + success = _dispatch->RequestUserPreferenceCharset(); + break; case CsiActionCodes::DECIC_InsertColumn: success = _dispatch->InsertColumn(parameters.at(0)); break; @@ -699,6 +711,9 @@ IStateMachineEngine::StringHandler OutputStateMachineEngine::ActionDcsDispatch(c parameters.at(6), parameters.at(7)); break; + case DcsActionCodes::DECAUPSS_AssignUserPreferenceSupplementalSet: + handler = _dispatch->AssignUserPreferenceCharset(parameters.at(0)); + break; case DcsActionCodes::DECDMAC_DefineMacro: handler = _dispatch->DefineMacro(parameters.at(0).value_or(0), parameters.at(1), parameters.at(2)); break; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 32105133bf3..b405179890f 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -93,6 +93,9 @@ namespace Microsoft::Console::VirtualTerminal LS2R_LockingShift = VTID("}"), LS3R_LockingShift = VTID("|"), DECAC1_AcceptC1Controls = VTID(" 7"), + ACS_AnsiLevel1 = VTID(" L"), + ACS_AnsiLevel2 = VTID(" M"), + ACS_AnsiLevel3 = VTID(" N"), DECDHL_DoubleHeightLineTop = VTID("#3"), DECDHL_DoubleHeightLineBottom = VTID("#4"), DECSWL_SingleWidthLine = VTID("#5"), @@ -163,6 +166,7 @@ namespace Microsoft::Console::VirtualTerminal DECERA_EraseRectangularArea = VTID("$z"), DECSERA_SelectiveEraseRectangularArea = VTID("${"), DECSCPP_SetColumnsPerPage = VTID("$|"), + DECRQUPSS_RequestUserPreferenceSupplementalSet = VTID("&u"), DECIC_InsertColumn = VTID("'}"), DECDC_DeleteColumn = VTID("'~"), DECSACE_SelectAttributeChangeExtent = VTID("*x"), @@ -175,6 +179,7 @@ namespace Microsoft::Console::VirtualTerminal enum DcsActionCodes : uint64_t { DECDLD_DownloadDRCS = VTID("{"), + DECAUPSS_AssignUserPreferenceSupplementalSet = VTID("!u"), DECDMAC_DefineMacro = VTID("!z"), DECRSTS_RestoreTerminalState = VTID("$p"), DECRQSS_RequestSetting = VTID("$q"), From bf25595961190d97b6d92bcc23b383713acd1214 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Feb 2024 00:50:59 +0100 Subject: [PATCH 20/50] Remove DxEngine (#16278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With AtlasEngine being fairly stable at this point and being enabled by default in the 1.19 branch, this changeset removes DxEngine. ## Validation Steps Performed * WT builds and runs ✅ * WpfTestNetCore ✅ * Saving the config removes the `useAtlasEngine` key ✅ --- OpenConsole.sln | 57 - doc/cascadia/profiles.schema.json | 5 - src/cascadia/TerminalControl/ControlCore.cpp | 19 +- src/cascadia/TerminalControl/ControlCore.h | 2 +- src/cascadia/TerminalControl/HwndTerminal.cpp | 49 +- src/cascadia/TerminalControl/HwndTerminal.hpp | 31 +- .../TerminalControl/IControlSettings.idl | 2 - src/cascadia/TerminalControl/TermControl.cpp | 21 +- src/cascadia/TerminalControl/TermControl.h | 1 - .../TerminalControlLib.vcxproj | 3 +- .../TerminalCore/lib/terminalcore-lib.vcxproj | 3 - .../TerminalSettingsEditor/ProfileViewModel.h | 1 - .../ProfileViewModel.idl | 1 - .../Profiles_Advanced.xaml | 9 - .../TerminalSettingsEditor/Rendering.xaml | 6 - .../RenderingViewModel.h | 1 - .../RenderingViewModel.idl | 1 - .../Resources/en-US/Resources.resw | 4 - .../TerminalSettingsModel/MTSMSettings.h | 1 - .../TerminalSettingsModel/Profile.idl | 1 - .../TerminalSettings.cpp | 1 - .../TerminalSettingsModel/TerminalSettings.h | 1 - .../Control.UnitTests.vcxproj | 1 - .../UnitTests_TerminalCore/ScrollTest.cpp | 12 +- .../TilWinRtHelpersTests.cpp | 1 - src/cascadia/inc/ControlProperties.h | 1 - src/features.xml | 18 - src/host/exe/Host.EXE.vcxproj | 3 - src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj | 3 - src/host/settings.cpp | 4 +- src/host/settings.hpp | 11 +- src/host/ut_host/Host.UnitTests.vcxproj | 3 - src/host/ut_host/VtIoTests.cpp | 39 - src/interactivity/win32/lib/win32.LIB.vcxproj | 3 - .../Interactivity.Win32.UnitTests.vcxproj | 3 - src/interactivity/win32/window.cpp | 44 +- src/interactivity/win32/window.hpp | 4 - src/propslib/RegistrySerialization.cpp | 2 +- src/renderer/atlas/README.md | 2 - src/renderer/dx/BoxDrawingEffect.cpp | 30 - src/renderer/dx/BoxDrawingEffect.h | 30 - src/renderer/dx/CustomTextLayout.cpp | 1757 ------------ src/renderer/dx/CustomTextLayout.h | 216 -- src/renderer/dx/CustomTextRenderer.cpp | 979 ------- src/renderer/dx/CustomTextRenderer.h | 153 -- src/renderer/dx/DxFontInfo.cpp | 313 --- src/renderer/dx/DxFontInfo.h | 72 - src/renderer/dx/DxFontRenderData.cpp | 923 ------- src/renderer/dx/DxFontRenderData.h | 142 - src/renderer/dx/DxRenderer.cpp | 2396 ----------------- src/renderer/dx/DxRenderer.hpp | 327 --- src/renderer/dx/DxSoftFont.cpp | 245 -- src/renderer/dx/DxSoftFont.h | 56 - src/renderer/dx/IBoxDrawingEffect.idl | 20 - src/renderer/dx/ScreenPixelShader.h | 91 - src/renderer/dx/ScreenVertexShader.h | 20 - src/renderer/dx/dirs | 2 - src/renderer/dx/lib/dx.vcxproj | 48 - src/renderer/dx/lib/dx.vcxproj.filters | 31 - src/renderer/dx/lib/sources | 8 - src/renderer/dx/precomp.cpp | 4 - src/renderer/dx/precomp.h | 41 - src/renderer/dx/sources.inc | 42 - .../dx/ut_dx/CustomTextLayoutTests.cpp | 100 - src/renderer/dx/ut_dx/DefaultResource.rc | 12 - src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj | 42 - src/renderer/dx/ut_dx/product.pbxproj | 4 - src/renderer/dx/ut_dx/sources | 36 - 68 files changed, 76 insertions(+), 8438 deletions(-) delete mode 100644 src/renderer/dx/BoxDrawingEffect.cpp delete mode 100644 src/renderer/dx/BoxDrawingEffect.h delete mode 100644 src/renderer/dx/CustomTextLayout.cpp delete mode 100644 src/renderer/dx/CustomTextLayout.h delete mode 100644 src/renderer/dx/CustomTextRenderer.cpp delete mode 100644 src/renderer/dx/CustomTextRenderer.h delete mode 100644 src/renderer/dx/DxFontInfo.cpp delete mode 100644 src/renderer/dx/DxFontInfo.h delete mode 100644 src/renderer/dx/DxFontRenderData.cpp delete mode 100644 src/renderer/dx/DxFontRenderData.h delete mode 100644 src/renderer/dx/DxRenderer.cpp delete mode 100644 src/renderer/dx/DxRenderer.hpp delete mode 100644 src/renderer/dx/DxSoftFont.cpp delete mode 100644 src/renderer/dx/DxSoftFont.h delete mode 100644 src/renderer/dx/IBoxDrawingEffect.idl delete mode 100644 src/renderer/dx/ScreenPixelShader.h delete mode 100644 src/renderer/dx/ScreenVertexShader.h delete mode 100644 src/renderer/dx/dirs delete mode 100644 src/renderer/dx/lib/dx.vcxproj delete mode 100644 src/renderer/dx/lib/dx.vcxproj.filters delete mode 100644 src/renderer/dx/lib/sources delete mode 100644 src/renderer/dx/precomp.cpp delete mode 100644 src/renderer/dx/precomp.h delete mode 100644 src/renderer/dx/sources.inc delete mode 100644 src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp delete mode 100644 src/renderer/dx/ut_dx/DefaultResource.rc delete mode 100644 src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj delete mode 100644 src/renderer/dx/ut_dx/product.pbxproj delete mode 100644 src/renderer/dx/ut_dx/sources diff --git a/OpenConsole.sln b/OpenConsole.sln index 004f8dfe82a..1717f4cc8a5 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -161,8 +161,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererVt.unittest", "src\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BufferOut", "src\buffer\out\lib\bufferout.vcxproj", "{0CF235BD-2DA0-407E-90EE-C467E8BBC714}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererDx", "src\renderer\dx\lib\dx.vcxproj", "{48D21369-3D7B-4431-9967-24E81292CF62}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}" ProjectSection(ProjectDependencies) = postProject {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} = {71CC9D78-BA29-4D93-946F-BEF5D9A3A6EF} @@ -173,7 +171,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Control.Lib", "src\cascadia\TerminalControl\TerminalControlLib.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}" ProjectSection(ProjectDependencies) = postProject {1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB} - {48D21369-3D7B-4431-9967-24E81292CF62} = {48D21369-3D7B-4431-9967-24E81292CF62} {48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63} {8222900C-8B6C-452A-91AC-BE95DB04B95F} = {8222900C-8B6C-452A-91AC-BE95DB04B95F} {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} = {AF0A096A-8B3A-4949-81EF-7DF8F0FEE91F} @@ -319,8 +316,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{D3EF build\scripts\Test-WindowsTerminalPackage.ps1 = build\scripts\Test-WindowsTerminalPackage.ps1 EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dx.Unit.Tests", "src\renderer\dx\ut_dx\Dx.Unit.Tests.vcxproj", "{95B136F9-B238-490C-A7C5-5843C1FECAC4}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winconpty.Tests.Feature", "src\winconpty\ft_pty\winconpty.FeatureTests.vcxproj", "{024052DE-83FB-4653-AEA4-90790D29D5BD}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAzBridge", "src\cascadia\TerminalAzBridge\TerminalAzBridge.vcxproj", "{067F0A06-FCB7-472C-96E9-B03B54E8E18D}" @@ -1260,32 +1255,6 @@ Global {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x64.Build.0 = Release|x64 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.ActiveCfg = Release|Win32 {0CF235BD-2DA0-407E-90EE-C467E8BBC714}.Release|x86.Build.0 = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x64.Build.0 = AuditMode|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.AuditMode|x86.Build.0 = AuditMode|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|ARM64.Build.0 = Debug|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x64.ActiveCfg = Debug|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x64.Build.0 = Debug|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.ActiveCfg = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Debug|x86.Build.0 = Debug|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|Any CPU.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.ActiveCfg = Release|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|ARM64.Build.0 = Release|ARM64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x64.ActiveCfg = Release|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x64.Build.0 = Release|x64 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.ActiveCfg = Release|Win32 - {48D21369-3D7B-4431-9967-24E81292CF62}.Release|x86.Build.0 = Release|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1779,30 +1748,6 @@ Global {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x64.Build.0 = Release|x64 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.ActiveCfg = Release|Win32 {A602A555-BAAC-46E1-A91D-3DAB0475C5A1}.Release|x86.Build.0 = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x64.ActiveCfg = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.ActiveCfg = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.AuditMode|x86.Build.0 = AuditMode|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|ARM64.Build.0 = Debug|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.ActiveCfg = Debug|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x64.Build.0 = Debug|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.ActiveCfg = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Debug|x86.Build.0 = Debug|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|Any CPU.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.ActiveCfg = Release|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|ARM64.Build.0 = Release|ARM64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.ActiveCfg = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x64.Build.0 = Release|x64 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.ActiveCfg = Release|Win32 - {95B136F9-B238-490C-A7C5-5843C1FECAC4}.Release|x86.Build.0 = Release|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {024052DE-83FB-4653-AEA4-90790D29D5BD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64 @@ -2485,7 +2430,6 @@ Global {18D09A24-8240-42D6-8CB6-236EEE820263} = {89CDCC5C-9F53-4054-97A4-639D99F169CD} {990F2657-8580-4828-943F-5DD657D11843} = {05500DEF-2294-41E3-AF9A-24E580B82836} {0CF235BD-2DA0-407E-90EE-C467E8BBC714} = {1E4A062E-293B-4817-B20D-BF16B979E350} - {48D21369-3D7B-4431-9967-24E81292CF62} = {05500DEF-2294-41E3-AF9A-24E580B82836} {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-ABCD-429C-B551-8562EC954746} = {9921CA0A-320C-4460-8623-3A3196E7F4CB} {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {9921CA0A-320C-4460-8623-3A3196E7F4CB} @@ -2515,7 +2459,6 @@ Global {53DD5520-E64C-4C06-B472-7CE62CA539C9} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} {6B5A44ED-918D-4747-BFB1-2472A1FCA173} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} {D3EF7B96-CD5E-47C9-B9A9-136259563033} = {04170EEF-983A-4195-BFEF-2321E5E38A1E} - {95B136F9-B238-490C-A7C5-5843C1FECAC4} = {05500DEF-2294-41E3-AF9A-24E580B82836} {024052DE-83FB-4653-AEA4-90790D29D5BD} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87} diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index d82242b55b8..7225059372a 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2791,11 +2791,6 @@ "description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.", "type": "string" }, - "useAtlasEngine": { - "description": "Windows Terminal 1.16 and later ship with a new, performant text renderer. Set this to false to revert back to the old text renderer.", - "type": "boolean", - "default": true - }, "fontFace": { "default": "Cascadia Mono", "description": "[deprecated] Define 'face' within the 'font' object instead.", diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 68702b27500..0dd8df67a83 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -17,7 +17,6 @@ #include "EventArgs.h" #include "../../buffer/out/search.h" #include "../../renderer/atlas/AtlasEngine.h" -#include "../../renderer/dx/DxRenderer.hpp" #include "ControlCore.g.cpp" #include "SelectionColor.g.cpp" @@ -335,15 +334,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - if (_settings->UseAtlasEngine()) - { - _renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); - } - else - { - _renderEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - } - + _renderEngine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); _renderer->AddRenderEngine(_renderEngine.get()); // Initialize our font with the renderer @@ -359,7 +350,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); - // Update DxEngine's SelectionBackground + // Update AtlasEngine's SelectionBackground _renderEngine->SetSelectionBackground(til::color{ _settings->SelectionBackground() }); const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); @@ -915,10 +906,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Update the terminal core with its new Core settings _terminal->UpdateAppearance(*newAppearance); - // Update DxEngine settings under the lock + // Update AtlasEngine settings under the lock if (_renderEngine) { - // Update DxEngine settings under the lock + // Update AtlasEngine settings under the lock _renderEngine->SetSelectionBackground(til::color{ newAppearance->SelectionBackground() }); _renderEngine->SetRetroTerminalEffect(newAppearance->RetroTerminalEffect()); _renderEngine->SetPixelShaderPath(newAppearance->PixelShaderPath()); @@ -954,7 +945,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_updateAntiAliasingMode() { D2D1_TEXT_ANTIALIAS_MODE mode; - // Update DxEngine's AntialiasingMode + // Update AtlasEngine's AntialiasingMode switch (_settings->AntialiasingMode()) { case TextAntialiasingMode::Cleartype: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 6b513f2bf86..dff8ba52bf3 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -5,7 +5,7 @@ // - ControlCore.h // // Abstract: -// - This encapsulates a `Terminal` instance, a `DxEngine` and `Renderer`, and +// - This encapsulates a `Terminal` instance, a `AtlasEngine` and `Renderer`, and // an `ITerminalConnection`. This is intended to be everything that someone // might need to stand up a terminal instance in a control, but without any // regard for how the UX works. diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index b5a930f701a..8ac7708ce0f 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -3,11 +3,18 @@ #include "pch.h" #include "HwndTerminal.hpp" -#include + #include +#include + +#include "HwndTerminalAutomationPeer.hpp" +#include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../../renderer/atlas/AtlasEngine.h" +#include "../../renderer/base/renderer.hpp" +#include "../../renderer/uia/UiaRenderer.hpp" #include "../../types/viewport.cpp" -#include "../../types/inc/GlyphWidth.hpp" +using namespace ::Microsoft::Console::VirtualTerminal; using namespace ::Microsoft::Terminal::Core; static LPCWSTR term_window_class = L"HwndTerminalClass"; @@ -102,25 +109,23 @@ try } break; case WM_RBUTTONDOWN: - if (publicTerminal->_terminal && publicTerminal->_terminal->IsSelectionActive()) + try { - try + if (publicTerminal->_terminal) { - Terminal::TextCopyData bufferData; + const auto lock = publicTerminal->_terminal->LockForWriting(); + if (publicTerminal->_terminal->IsSelectionActive()) { - const auto lock = publicTerminal->_terminal->LockForWriting(); - bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true); + const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true); + LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf)); + publicTerminal->_ClearSelection(); + return 0; } - LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf)); - publicTerminal->_ClearSelection(); } - CATCH_LOG(); - } - else - { publicTerminal->_PasteTextFromClipboard(); + return 0; } - return 0; + CATCH_LOG(); case WM_DESTROY: // Release Terminal's hwnd so Teardown doesn't try to destroy it again publicTerminal->_hwnd.release(); @@ -210,10 +215,10 @@ HRESULT HwndTerminal::Initialize() RETURN_HR_IF_NULL(E_POINTER, localPointerToThread); RETURN_IF_FAILED(localPointerToThread->Initialize(_renderer.get())); - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - RETURN_IF_FAILED(dxEngine->SetHwnd(_hwnd.get())); - RETURN_IF_FAILED(dxEngine->Enable()); - _renderer->AddRenderEngine(dxEngine.get()); + auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); + RETURN_IF_FAILED(engine->SetHwnd(_hwnd.get())); + RETURN_IF_FAILED(engine->Enable()); + _renderer->AddRenderEngine(engine.get()); _UpdateFont(USER_DEFAULT_SCREEN_DPI); RECT windowRect; @@ -224,9 +229,9 @@ HRESULT HwndTerminal::Initialize() // Fist set up the dx engine with the window size in pixels. // Then, using the font, get the number of characters that can fit. const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); - RETURN_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); + RETURN_IF_FAILED(engine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); - _renderEngine = std::move(dxEngine); + _renderEngine = std::move(engine); _terminal->Create({ 80, 25 }, 9001, *_renderer); _terminal->SetWriteInputCallback([=](std::wstring_view input) noexcept { _WriteTextToConnection(input); }); @@ -749,7 +754,7 @@ try ScreenToClient(_hwnd.get(), cursorPosition.as_win32_point()); } - const TerminalInput::MouseButtonState state{ + const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state{ WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed), WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed), WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) @@ -894,7 +899,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font [[gsl::suppress(bounds .3)]] renderSettings.SetColorTableEntry(tableIndex, gsl::at(theme.ColorTable, tableIndex)); } - publicTerminal->_terminal->SetCursorStyle(static_cast(theme.CursorStyle)); + publicTerminal->_terminal->SetCursorStyle(static_cast(theme.CursorStyle)); publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, static_cast(fontSize), CP_UTF8 }; publicTerminal->_UpdateFont(newDpi); diff --git a/src/cascadia/TerminalControl/HwndTerminal.hpp b/src/cascadia/TerminalControl/HwndTerminal.hpp index 6646fd56506..45780a1d50e 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.hpp +++ b/src/cascadia/TerminalControl/HwndTerminal.hpp @@ -3,14 +3,31 @@ #pragma once -#include "../../renderer/base/Renderer.hpp" -#include "../../renderer/dx/DxRenderer.hpp" -#include "../../renderer/uia/UiaRenderer.hpp" -#include "../../cascadia/TerminalCore/Terminal.hpp" +#include "../../buffer/out/textBuffer.hpp" +#include "../../renderer/inc/FontInfoDesired.hpp" #include "../../types/IControlAccessibilityInfo.h" -#include "HwndTerminalAutomationPeer.hpp" -using namespace Microsoft::Console::VirtualTerminal; +namespace Microsoft::Console::Render::Atlas +{ + class AtlasEngine; +} + +namespace Microsoft::Console::Render +{ + using AtlasEngine = Atlas::AtlasEngine; + class IRenderData; + class Renderer; + class UiaEngine; +} + +namespace Microsoft::Terminal::Core +{ + class Terminal; +} + +class FontInfo; +class FontInfoDesired; +class HwndTerminalAutomationPeer; // Keep in sync with TerminalTheme.cs typedef struct _TerminalTheme @@ -79,7 +96,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal; std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer; - std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine; + std::unique_ptr<::Microsoft::Console::Render::AtlasEngine> _renderEngine; std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine; bool _focused{ false }; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2ccfbd5b90d..e89b58eeb8c 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -35,8 +35,6 @@ namespace Microsoft.Terminal.Control Boolean EnableUnfocusedAcrylic; ScrollbarState ScrollState { get; }; - Boolean UseAtlasEngine { get; }; - String FontFace { get; }; Single FontSize { get; }; Windows.UI.Text.FontWeight FontWeight { get; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 5978149cd36..b9c81abe67a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -2404,25 +2404,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation // then use it to measure how much space the requested rows and columns // will take up. // TODO: MSFT:21254947 - use a static function to do this instead of - // instantiating a DxEngine/AtlasEngine. + // instantiating a AtlasEngine. // GH#10211 - UNDER NO CIRCUMSTANCE should this fail. If it does, the // whole app will crash instantaneously on launch, which is no good. - float scale; - if (settings.UseAtlasEngine()) - { - auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); - LOG_IF_FAILED(engine->UpdateDpi(dpi)); - LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont)); - scale = engine->GetScaling(); - } - else - { - auto engine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - LOG_IF_FAILED(engine->UpdateDpi(dpi)); - LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont)); - scale = engine->GetScaling(); - } + const auto engine = std::make_unique<::Microsoft::Console::Render::AtlasEngine>(); + LOG_IF_FAILED(engine->UpdateDpi(dpi)); + LOG_IF_FAILED(engine->UpdateFont(desiredFont, actualFont)); + const auto scale = engine->GetScaling(); const auto actualFontSize = actualFont.GetSize(); // UWP XAML scrollbars aren't guaranteed to be the same size as the diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 7dc74d7136b..f2cc715b6eb 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -7,7 +7,6 @@ #include "XamlLights.h" #include "EventArgs.h" #include "../../renderer/base/Renderer.hpp" -#include "../../renderer/dx/DxRenderer.hpp" #include "../../renderer/uia/UiaRenderer.hpp" #include "../../cascadia/TerminalCore/Terminal.hpp" #include "../buffer/out/search.h" diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index 151fe35932d..d4bd7e4a004 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -31,7 +31,7 @@ - + @@ -166,7 +166,6 @@ - diff --git a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj index a6a9f5f1612..f1bc1acacdf 100644 --- a/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj +++ b/src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj @@ -43,9 +43,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {3c67784e-1453-49c2-9660-483e2cc7f7ad} diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 7a4bd26c13f..44df681665f 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -102,7 +102,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput); OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing); OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle); - OBSERVABLE_PROJECTED_SETTING(_profile, UseAtlasEngine); OBSERVABLE_PROJECTED_SETTING(_profile, Elevate); OBSERVABLE_PROJECTED_SETTING(_profile, VtPassthrough); OBSERVABLE_PROJECTED_SETTING(_profile, ReloadEnvironmentVariables); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index e8517be3995..5020ecb7945 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -103,7 +103,6 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAtlasEngine); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, VtPassthrough); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml index 815d6859d70..c9403bac0af 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.xaml @@ -117,15 +117,6 @@ - - - - - - - - - - Controls what happens when the application emits a BEL character. A description for what the "bell style" setting does. Presented near "Profile_BellStyle".{Locked="BEL"} - - Use the new text renderer ("AtlasEngine") - {Locked="AtlasEngine"} - Launch this application with a new environment block "environment variables" are user-definable values that can affect the way running processes will behave on a computer diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index d0dc6bfcca5..f9626e6b6c8 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -90,7 +90,6 @@ Author(s): X(hstring, TabTitle, "tabTitle") \ X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, UseAtlasEngine, "useAtlasEngine", true) \ X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 4aa9af3422a..16a17b15ee0 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -85,7 +85,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap, EnvironmentVariables); - INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine); INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector, BellSound); INHERITABLE_PROFILE_SETTING(Boolean, Elevate); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 580b06089fd..21d76fd8ed8 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -307,7 +307,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _SuppressApplicationTitle = profile.SuppressApplicationTitle(); } - _UseAtlasEngine = profile.UseAtlasEngine(); _ScrollState = profile.ScrollState(); _AntialiasingMode = profile.AntialiasingMode(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 009e6503da0..b065ee3c9f6 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -149,7 +149,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, IEnvironmentVariableMap, EnvironmentVariables); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::ScrollbarState, ScrollState, Microsoft::Terminal::Control::ScrollbarState::Visible); - INHERITABLE_SETTING(Model::TerminalSettings, bool, UseAtlasEngine, true); INHERITABLE_SETTING(Model::TerminalSettings, Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale); diff --git a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj index 76858394072..5406e1d7bb2 100644 --- a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj +++ b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj @@ -40,7 +40,6 @@ - diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index 21022f30333..92770d79727 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -2,18 +2,10 @@ // Licensed under the MIT license. #include "pch.h" -#include - -#include - -#include "../renderer/inc/DummyRenderer.hpp" -#include "../renderer/base/Renderer.hpp" -#include "../renderer/dx/DxRenderer.hpp" #include "../cascadia/TerminalCore/Terminal.hpp" -#include "MockTermSettings.h" -#include "consoletaeftemplates.hpp" -#include "../../inc/TestUtils.h" +#include "../renderer/inc/DummyRenderer.hpp" +#include "../renderer/inc/RenderEngineBase.hpp" using namespace winrt::Microsoft::Terminal::Core; using namespace Microsoft::Terminal::Core; diff --git a/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp b/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp index 89c2856b8e0..6e6eee5f355 100644 --- a/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TilWinRtHelpersTests.cpp @@ -8,7 +8,6 @@ #include "../renderer/inc/DummyRenderer.hpp" #include "../renderer/base/Renderer.hpp" -#include "../renderer/dx/DxRenderer.hpp" #include "../cascadia/TerminalCore/Terminal.hpp" #include "MockTermSettings.h" diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 1b61775becc..02c4ad2427a 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -72,7 +72,6 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ - X(bool, UseAtlasEngine, true) \ X(bool, UseBackgroundImageForWindow, false) \ X(bool, ShowMarks, false) \ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ diff --git a/src/features.xml b/src/features.xml index 4a555d7d4e6..e8fd44179d3 100644 --- a/src/features.xml +++ b/src/features.xml @@ -26,15 +26,6 @@ - - Feature_ConhostDxEngine - Controls whether conhost supports the DX engine and the UseDx registry key - AlwaysEnabled - - WindowsInbox - - - Feature_ConhostAtlasEngine Controls whether conhost supports the Atlas engine @@ -44,15 +35,6 @@ - - Feature_DxEngineShaderSupport - Controls whether the DX engine is built with shader support. - AlwaysEnabled - - WindowsInbox - - - Feature_UseNumpadEventsForClipboardInput Controls whether the clipboard converter (and ConPTY InputStateMachine) uses Numpad events instead of UChar diff --git a/src/host/exe/Host.EXE.vcxproj b/src/host/exe/Host.EXE.vcxproj index 13084434d7e..6de848979aa 100644 --- a/src/host/exe/Host.EXE.vcxproj +++ b/src/host/exe/Host.EXE.vcxproj @@ -47,9 +47,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {1c959542-bac2-4e55-9a6d-13251914cbb9} diff --git a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj index 7525726d8ff..6e5fb985efe 100644 --- a/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj +++ b/src/host/ft_fuzzer/Host.FuzzWrapper.vcxproj @@ -42,9 +42,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {1c959542-bac2-4e55-9a6d-13251914cbb9} diff --git a/src/host/settings.cpp b/src/host/settings.cpp index a9673658bb6..274fadec58f 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -53,7 +53,7 @@ Settings::Settings() : _fUseWindowSizePixels(false), // window size pixels initialized below _fInterceptCopyPaste(0), - _fUseDx(UseDx::Disabled), + _fUseDx(false), _fCopyColor(false) { _dwScreenBufferSize.X = 80; @@ -767,7 +767,7 @@ void Settings::SetTerminalScrolling(const bool terminalScrollingEnabled) noexcep // Determines whether our primary renderer should be DirectX or GDI. // This is based on user preference and velocity hold back state. -UseDx Settings::GetUseDx() const noexcept +bool Settings::GetUseDx() const noexcept { return _fUseDx; } diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 03eff335367..4edeb3ae927 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -24,13 +24,6 @@ constexpr unsigned short MIN_WINDOW_OPACITY = 0x4D; // 0x4D is approximately 30% #include "ConsoleArguments.hpp" #include "../renderer/inc/RenderSettings.hpp" -enum class UseDx : DWORD -{ - Disabled = 0, - DxEngine, - AtlasEngine, -}; - class Settings { using RenderSettings = Microsoft::Console::Render::RenderSettings; @@ -176,7 +169,7 @@ class Settings bool IsTerminalScrolling() const noexcept; void SetTerminalScrolling(const bool terminalScrollingEnabled) noexcept; - UseDx GetUseDx() const noexcept; + bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; private: @@ -219,7 +212,7 @@ class Settings std::wstring _LaunchFaceName; bool _fAllowAltF4Close; DWORD _dwVirtTermLevel; - UseDx _fUseDx; + bool _fUseDx; bool _fCopyColor; // this is used for the special STARTF_USESIZE mode. diff --git a/src/host/ut_host/Host.UnitTests.vcxproj b/src/host/ut_host/Host.UnitTests.vcxproj index 3592ec1489f..d464570c788 100644 --- a/src/host/ut_host/Host.UnitTests.vcxproj +++ b/src/host/ut_host/Host.UnitTests.vcxproj @@ -47,9 +47,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {990F2657-8580-4828-943F-5DD657D11843} diff --git a/src/host/ut_host/VtIoTests.cpp b/src/host/ut_host/VtIoTests.cpp index 7f4796df60d..23d2d3a9c7e 100644 --- a/src/host/ut_host/VtIoTests.cpp +++ b/src/host/ut_host/VtIoTests.cpp @@ -12,10 +12,6 @@ #include "../../renderer/vt/Xterm256Engine.hpp" #include "../../renderer/vt/XtermEngine.hpp" -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -#include "../../renderer/dx/DxRenderer.hpp" -#endif - using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; @@ -38,10 +34,6 @@ class Microsoft::Console::VirtualTerminal::VtIoTests TEST_METHOD(RendererDtorAndThread); -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - TEST_METHOD(RendererDtorAndThreadAndDx); -#endif - TEST_METHOD(BasicAnonymousPipeOpeningWithSignalChannelTest); }; @@ -428,37 +420,6 @@ void VtIoTests::RendererDtorAndThread() } } -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -void VtIoTests::RendererDtorAndThreadAndDx() -{ - Log::Comment(NoThrowString().Format( - L"Test deleting a Renderer a bunch of times")); - - for (auto i = 0; i < 16; ++i) - { - auto data = std::make_unique(); - auto thread = std::make_unique(); - auto* pThread = thread.get(); - auto pRenderer = std::make_unique(RenderSettings{}, data.get(), nullptr, 0, std::move(thread)); - VERIFY_SUCCEEDED(pThread->Initialize(pRenderer.get())); - - auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>(); - pRenderer->AddRenderEngine(dxEngine.get()); - // Sleep for a hot sec to make sure the thread starts before we enable painting - // If you don't, the thread might wait on the paint enabled event AFTER - // EnablePainting gets called, and if that happens, then the thread will - // never get destructed. This will only ever happen in the vstest test runner, - // which is what CI uses. - /*Sleep(500);*/ - - (void)dxEngine->Enable(); - pThread->EnablePainting(); - pRenderer->TriggerTeardown(); - pRenderer.reset(); - } -} -#endif - void VtIoTests::BasicAnonymousPipeOpeningWithSignalChannelTest() { Log::Comment(L"Test using anonymous pipes for the input and adding a signal channel."); diff --git a/src/interactivity/win32/lib/win32.LIB.vcxproj b/src/interactivity/win32/lib/win32.LIB.vcxproj index 8ffd169d7a7..1988841434b 100644 --- a/src/interactivity/win32/lib/win32.LIB.vcxproj +++ b/src/interactivity/win32/lib/win32.LIB.vcxproj @@ -63,9 +63,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - diff --git a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj index 919e3fb8365..9f354048194 100644 --- a/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj +++ b/src/interactivity/win32/ut_interactivity_win32/Interactivity.Win32.UnitTests.vcxproj @@ -26,9 +26,6 @@ {8222900C-8B6C-452A-91AC-BE95DB04B95F} - - {48d21369-3d7b-4431-9967-24e81292cf62} - {990f2657-8580-4828-943f-5dd657d11842} diff --git a/src/interactivity/win32/window.cpp b/src/interactivity/win32/window.cpp index 03eed072c13..3ef07add747 100644 --- a/src/interactivity/win32/window.cpp +++ b/src/interactivity/win32/window.cpp @@ -28,9 +28,6 @@ #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED #include "../../renderer/atlas/AtlasEngine.h" #endif -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED -#include "../../renderer/dx/DxRenderer.hpp" -#endif #include "../inc/ServiceLocator.hpp" #include "../../types/inc/Viewport.hpp" @@ -70,9 +67,6 @@ Window::~Window() // reducing the change for existing race conditions to turn into deadlocks. #ifndef NDEBUG delete pGdiEngine; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - delete pDxEngine; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED delete pAtlasEngine; #endif @@ -217,31 +211,17 @@ void Window::_UpdateSystemMetrics() const const auto useDx = pSettings->GetUseDx(); try { - switch (useDx) - { -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - case UseDx::DxEngine: - pDxEngine = new DxEngine(); - // TODO: MSFT:21255595 make this less gross - // Manually set the Dx Engine to Hwnd mode. When we're trying to - // determine the initial window size, which happens BEFORE the - // window is created, we'll want to make sure the DX engine does - // math in the hwnd mode, not the Composition mode. - THROW_IF_FAILED(pDxEngine->SetHwnd(nullptr)); - g.pRender->AddRenderEngine(pDxEngine); - break; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED - case UseDx::AtlasEngine: + if (useDx) + { pAtlasEngine = new AtlasEngine(); g.pRender->AddRenderEngine(pAtlasEngine); - break; + } + else #endif - default: + { pGdiEngine = new GdiEngine(); g.pRender->AddRenderEngine(pGdiEngine); - break; -#pragma warning(suppress : 4065) } } catch (...) @@ -332,20 +312,8 @@ void Window::_UpdateSystemMetrics() const { _hWnd = hWnd; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - if (pDxEngine) - { - HRESULT hr = S_OK; - if (SUCCEEDED(hr = pDxEngine->SetHwnd(hWnd))) - { - hr = pDxEngine->Enable(); - } - status = NTSTATUS_FROM_HRESULT(hr); - } - else -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED - if (pAtlasEngine) + if (pAtlasEngine) { const auto hr = pAtlasEngine->SetHwnd(hWnd); status = NTSTATUS_FROM_HRESULT(hr); diff --git a/src/interactivity/win32/window.hpp b/src/interactivity/win32/window.hpp index 7550b18c9d8..ea678628e1e 100644 --- a/src/interactivity/win32/window.hpp +++ b/src/interactivity/win32/window.hpp @@ -24,7 +24,6 @@ namespace Microsoft::Console::Render::Atlas namespace Microsoft::Console::Render { using AtlasEngine = Atlas::AtlasEngine; - class DxEngine; class GdiEngine; } @@ -113,9 +112,6 @@ namespace Microsoft::Console::Interactivity::Win32 HWND _hWnd; Render::GdiEngine* pGdiEngine = nullptr; -#if TIL_FEATURE_CONHOSTDXENGINE_ENABLED - Render::DxEngine* pDxEngine = nullptr; -#endif #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED Render::AtlasEngine* pAtlasEngine = nullptr; #endif diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index 2b9de45223e..a4441d25a5a 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -60,7 +60,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Dword, CONSOLE_REGISTRY_CURSORTYPE, SET_FIELD_AND_SIZE(_CursorType) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, - { _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, + { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) } // Special cases that are handled manually in Registry::LoadFromRegistry: diff --git a/src/renderer/atlas/README.md b/src/renderer/atlas/README.md index e5e87f2ca0f..c72e77f51e6 100644 --- a/src/renderer/atlas/README.md +++ b/src/renderer/atlas/README.md @@ -8,7 +8,6 @@ graph TD Renderer["Renderer (base/renderer.cpp)\nbreaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)"] RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\nabstracts 24 LOC 👻"\] GdiEngine["GdiEngine (gdi/...)"] - DxEngine["DxEngine (dx/...)"] subgraph AtlasEngine["AtlasEngine (atlas/...)"] AtlasEngine.cpp["AtlasEngine.cpp\nImplements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs"] @@ -24,7 +23,6 @@ graph TD Renderer -.-> RenderEngineBase %% Mermaid.js has no support for backwards arrow at the moment RenderEngineBase <-.->|extends| GdiEngine - RenderEngineBase <-.->|extends| DxEngine Renderer ----> AtlasEngine AtlasEngine.cpp <--> AtlasEngine.api.cpp AtlasEngine.cpp <--> AtlasEngine.r.cpp diff --git a/src/renderer/dx/BoxDrawingEffect.cpp b/src/renderer/dx/BoxDrawingEffect.cpp deleted file mode 100644 index d5af5a8451f..00000000000 --- a/src/renderer/dx/BoxDrawingEffect.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "BoxDrawingEffect.h" - -using namespace Microsoft::Console::Render; - -BoxDrawingEffect::BoxDrawingEffect() noexcept : - _scale{ 1.0f, 0.0f, 1.0f, 0.0f } -{ -} - -#pragma warning(suppress : 26434) // WRL RuntimeClassInitialize base is a no-op and we need this for MakeAndInitialize -HRESULT BoxDrawingEffect::RuntimeClassInitialize(float verticalScale, float verticalTranslate, float horizontalScale, float horizontalTranslate) noexcept -{ - _scale.VerticalScale = verticalScale; - _scale.VerticalTranslation = verticalTranslate; - _scale.HorizontalScale = horizontalScale; - _scale.HorizontalTranslation = horizontalTranslate; - return S_OK; -} - -[[nodiscard]] HRESULT STDMETHODCALLTYPE BoxDrawingEffect::GetScale(BoxScale* scale) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, scale); - *scale = _scale; - return S_OK; -} diff --git a/src/renderer/dx/BoxDrawingEffect.h b/src/renderer/dx/BoxDrawingEffect.h deleted file mode 100644 index 1bd0439cffb..00000000000 --- a/src/renderer/dx/BoxDrawingEffect.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include -#include - -#include "IBoxDrawingEffect_h.h" - -namespace Microsoft::Console::Render -{ - class BoxDrawingEffect : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IBoxDrawingEffect> - { - public: - BoxDrawingEffect() noexcept; - HRESULT RuntimeClassInitialize(float verticalScale, float verticalTranslate, float horizontalScale, float horizontalTranslate) noexcept; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetScale(BoxScale* scale) noexcept override; - - protected: - private: - BoxScale _scale; -#ifdef UNIT_TESTING - public: - friend class BoxDrawingEffectTests; -#endif - }; -} diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp deleted file mode 100644 index 41c64a5a5cd..00000000000 --- a/src/renderer/dx/CustomTextLayout.cpp +++ /dev/null @@ -1,1757 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CustomTextLayout.h" -#include "CustomTextRenderer.h" - -#include -#include -#include - -#include "BoxDrawingEffect.h" - -using namespace Microsoft::Console::Render; - -// Routine Description: -// - Creates a CustomTextLayout object for calculating which glyphs should be placed and where -// Arguments: -// - dxFontRenderData - The DirectWrite font render data for our layout -CustomTextLayout::CustomTextLayout(const gsl::not_null fontRenderData) : - _fontRenderData{ fontRenderData }, - _formatInUse{ fontRenderData->DefaultTextFormat().Get() }, - _fontInUse{ fontRenderData->DefaultFontFace().Get() }, - _numberSubstitution{}, - _readingDirection{ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT }, - _runs{}, - _breakpoints{}, - _runIndex{ 0 }, - _width{ gsl::narrow_cast(fontRenderData->GlyphCell().width) }, - _isEntireTextSimple{ false } -{ - _localeName.resize(gsl::narrow_cast(fontRenderData->DefaultTextFormat()->GetLocaleNameLength()) + 1); // +1 for null - THROW_IF_FAILED(fontRenderData->DefaultTextFormat()->GetLocaleName(_localeName.data(), gsl::narrow(_localeName.size()))); -} - -//Routine Description: -// - Resets this custom text layout to the freshly allocated state in terms of text analysis. -// Arguments: -// - , modifies internal state -// Return Value: -// - S_OK or suitable memory management issue -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Reset() noexcept -try -{ - _runs.clear(); - _breakpoints.clear(); - _runIndex = 0; - _isEntireTextSimple = false; - _textClusterColumns.clear(); - _text.clear(); - _glyphScaleCorrections.clear(); - _glyphClusters.clear(); - _glyphIndices.clear(); - _glyphDesignUnitAdvances.clear(); - _glyphAdvances.clear(); - _glyphOffsets.clear(); - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Appends text to this layout for analysis/processing. -// Arguments: -// - clusters - From the backing buffer, the text to be displayed clustered by the columns it should consume. -// Return Value: -// - S_OK or suitable memory management issue. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::AppendClusters(const std::span clusters) -try -{ - _textClusterColumns.reserve(_textClusterColumns.size() + clusters.size()); - - for (const auto& cluster : clusters) - { - const auto cols = gsl::narrow(cluster.GetColumns()); - const auto text = cluster.GetText(); - - // Push back the number of columns for this bit of text. - _textClusterColumns.push_back(cols); - - // If there is more than one text character here, push 0s for the rest of the columns - // of the text run. - _textClusterColumns.resize(_textClusterColumns.size() + base::ClampSub(text.size(), 1u), gsl::narrow_cast(0u)); - - _text += text; - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Figures out how many columns this layout should take. This will use the analyze step only. -// Arguments: -// - columns - The number of columns the layout should consume when done. -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetColumns(_Out_ UINT32* columns) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, columns); - *columns = 0; - - _formatInUse = _fontRenderData->DefaultTextFormat().Get(); - _fontInUse = _fontRenderData->DefaultFontFace().Get(); - - RETURN_IF_FAILED(_AnalyzeTextComplexity()); - RETURN_IF_FAILED(_AnalyzeRuns()); - RETURN_IF_FAILED(_ShapeGlyphRuns()); - - const auto totalAdvance = std::accumulate(_glyphAdvances.cbegin(), _glyphAdvances.cend(), 0.0f); - - *columns = static_cast(ceil(totalAdvance / _width)); - - return S_OK; -} - -// Routine Description: -// - Implements a drawing interface similarly to the default IDWriteTextLayout which will -// take the string from construction, analyze it for complexity, shape up the glyphs, -// and then draw the final product to the given renderer at the point and pass along -// the context information. -// - This specific class does the layout calculations and complexity analysis, not the -// final drawing. That's the renderer's job (passed in.) -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - originX - X pixel point of top left corner on final surface for drawing -// - originY - Y pixel point of top left corner on final surface for drawing -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::Draw(_In_opt_ void* clientDrawingContext, - _In_ IDWriteTextRenderer* renderer, - FLOAT originX, - FLOAT originY) noexcept -try -{ - const auto drawingContext = static_cast(clientDrawingContext); - - auto weight = _fontRenderData->DefaultFontWeight(); - auto style = _fontRenderData->DefaultFontStyle(); - const auto stretch = _fontRenderData->DefaultFontStretch(); - - if (drawingContext->useBoldFont) - { - // TODO: "relative" bold? - weight = DWRITE_FONT_WEIGHT_BOLD; - // Since we are setting the font weight according to the text attribute, - // make sure to tell the text format to ignore the user set font weight - _fontRenderData->InhibitUserWeight(true); - } - else - { - _fontRenderData->InhibitUserWeight(false); - } - - if (drawingContext->useItalicFont || _fontRenderData->DidUserSetItalic()) - { - style = DWRITE_FONT_STYLE_ITALIC; - } - - _formatInUse = _fontRenderData->TextFormatWithAttribute(weight, style, stretch).Get(); - _fontInUse = _fontRenderData->FontFaceWithAttribute(weight, style, stretch).Get(); - - RETURN_IF_FAILED(_AnalyzeTextComplexity()); - RETURN_IF_FAILED(_AnalyzeRuns()); - RETURN_IF_FAILED(_ShapeGlyphRuns()); - RETURN_IF_FAILED(_CorrectGlyphRuns()); - // Correcting box drawing has to come after both font fallback and - // the glyph run advance correction (which will apply a font size scaling factor). - // We need to know all the proposed X and Y dimension metrics to get this right. - RETURN_IF_FAILED(_CorrectBoxDrawing()); - - RETURN_IF_FAILED(_DrawGlyphRuns(clientDrawingContext, renderer, { originX, originY })); - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Uses the internal text information and the analyzers/font information from construction -// to determine the complexity of the text. If the text is determined to be entirely simple, -// we'll have more chances to optimize the layout process. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeTextComplexity() noexcept -{ - try - { - const auto textLength = gsl::narrow(_text.size()); - - auto isTextSimple = FALSE; - UINT32 uiLengthRead = 0; - - // Start from the beginning. - const UINT32 glyphStart = 0; - - _glyphIndices.resize(textLength); - - const auto hr = _fontRenderData->Analyzer()->GetTextComplexity( - _text.c_str(), - textLength, - _fontInUse, - &isTextSimple, - &uiLengthRead, - &_glyphIndices.at(glyphStart)); - - RETURN_IF_FAILED(hr); - - _isEntireTextSimple = isTextSimple && uiLengthRead == textLength; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Uses the internal text information and the analyzers/font information from construction -// to determine the complexity of the text inside this layout, compute the subsections (or runs) -// that contain similar property information, and stores that information internally. -// - We determine line breakpoints, bidirectional information, the script properties, -// number substitution, and font fallback properties in this function. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_AnalyzeRuns() noexcept -{ - try - { - // We're going to need the text length in UINT32 format for the DWrite calls. - // Convert it once up front. - const auto textLength = gsl::narrow(_text.size()); - - // Initially start out with one result that covers the entire range. - // This result will be subdivided by the analysis processes. - _runs.resize(1); - auto& initialRun = _runs.front(); - initialRun.textLength = textLength; - initialRun.bidiLevel = (_readingDirection == DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); - - // Allocate enough room to have one breakpoint per code unit. - _breakpoints.resize(_text.size()); - - if (!_isEntireTextSimple || _fontRenderData->DidUserSetAxes()) - { - // Call each of the analyzers in sequence, recording their results. - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeLineBreakpoints(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeBidi(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeScript(this, 0, textLength, this)); - RETURN_IF_FAILED(_fontRenderData->Analyzer()->AnalyzeNumberSubstitution(this, 0, textLength, this)); - // Perform our custom font fallback analyzer that mimics the pattern of the real analyzers. - RETURN_IF_FAILED(_AnalyzeFontFallback(this, 0, textLength)); - } - - // Ensure that a font face is attached to every run - for (auto& run : _runs) - { - if (!run.fontFace) - { - run.fontFace = _fontInUse; - } - } - - // Resequence the resulting runs in order before returning to caller. - _OrderRuns(); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Uses the internal run analysis information (from the analyze step) to map and shape out -// the glyphs from the fonts. This is effectively a loop of _ShapeGlyphRun. See it for details. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_ShapeGlyphRuns() noexcept -{ - try - { - // Shapes all the glyph runs in the layout. - const auto textLength = gsl::narrow(_text.size()); - - // Estimate the maximum number of glyph indices needed to hold a string. - const auto estimatedGlyphCount = _EstimateGlyphCount(textLength); - - _glyphIndices.resize(estimatedGlyphCount); - _glyphOffsets.resize(estimatedGlyphCount); - _glyphAdvances.resize(estimatedGlyphCount); - _glyphClusters.resize(textLength); - - UINT32 glyphStart = 0; - - // Shape each run separately. This is needed whenever script, locale, - // or reading direction changes. - for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) - { - LOG_IF_FAILED(_ShapeGlyphRun(runIndex, glyphStart)); - } - - _glyphIndices.resize(glyphStart); - _glyphOffsets.resize(glyphStart); - _glyphAdvances.resize(glyphStart); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Calculates the following information for any one particular run of text: -// 1. Indices (finding the ID number in each font for each glyph) -// 2. Offsets (the left/right or top/bottom spacing from the baseline origin for each glyph) -// 3. Advances (the width allowed for every glyph) -// 4. Clusters (the bunches of glyphs that represent a particular combined character) -// - A run is defined by the analysis step as a substring of the original text that has similar properties -// such that it can be processed together as a unit. -// Arguments: -// - runIndex - The ID number of the internal runs array to use while shaping -// - glyphStart - On input, which portion of the internal indices/offsets/etc. arrays to use -// to write the shaping information. -// - On output, the position that should be used by the next call as its start position -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept -{ - try - { - // Shapes a single run of text into glyphs. - // Alternately, you could iteratively interleave shaping and line - // breaking to reduce the number glyphs held onto at once. It's simpler - // for this demonstration to just do shaping and line breaking as two - // separate processes, but realize that this does have the consequence that - // certain advanced fonts containing line specific features (like Gabriola) - // will shape as if the line is not broken. - - Run& run = _runs.at(runIndex); - const auto textStart = run.textStart; - const auto textLength = run.textLength; - auto maxGlyphCount = gsl::narrow(_glyphIndices.size() - glyphStart); - UINT32 actualGlyphCount = 0; - - run.glyphStart = glyphStart; - run.glyphCount = 0; - - if (textLength == 0) - { - return S_FALSE; // Nothing to do.. - } - - // Allocate space for shaping to fill with glyphs and other information, - // with about as many glyphs as there are text characters. We'll actually - // need more glyphs than codepoints if they are decomposed into separate - // glyphs, or fewer glyphs than codepoints if multiple are substituted - // into a single glyph. In any case, the shaping process will need some - // room to apply those rules to even make that determination. - - if (textLength > maxGlyphCount) - { - maxGlyphCount = _EstimateGlyphCount(textLength); - const auto totalGlyphsArrayCount = glyphStart + maxGlyphCount; - _glyphIndices.resize(totalGlyphsArrayCount); - } - - if (_isEntireTextSimple && !_fontRenderData->DidUserSetFeatures()) - { - // When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and - // advances(in font design unit). With the help of font metrics, we can calculate the actual glyph - // advances without the need of GetGlyphPlacements. This shortcut will significantly reduce the time - // needed for text analysis. - DWRITE_FONT_METRICS1 metrics; - run.fontFace->GetMetrics(&metrics); - - // With simple text, there's only one run. The actual glyph count is the same as textLength. - _glyphDesignUnitAdvances.resize(textLength); - _glyphAdvances.resize(textLength); - - auto designUnitsPerEm = metrics.designUnitsPerEm; - - RETURN_IF_FAILED(_fontInUse->GetDesignGlyphAdvances( - textLength, - &_glyphIndices.at(glyphStart), - &_glyphDesignUnitAdvances.at(glyphStart), - run.isSideways)); - - for (size_t i = glyphStart; i < _glyphAdvances.size(); i++) - { - _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _formatInUse->GetFontSize() * run.fontScale; - } - - // Set all the clusters as sequential. In a simple run, we're going 1 to 1. - // Fill the clusters sequentially from 0 to N-1. - std::iota(_glyphClusters.begin(), _glyphClusters.end(), gsl::narrow_cast(0)); - - run.glyphCount = textLength; - glyphStart += textLength; - - return S_OK; - } - - std::vector textProps(textLength); - std::vector glyphProps(maxGlyphCount); - - // Get the features to apply to the font - const auto& features = _fontRenderData->DefaultFontFeatures(); -#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). - const DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { const_cast(features.data()), gsl::narrow(features.size()) }; - DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures; - const uint32_t fontFeatureLengths[] = { textLength }; - - // Get the glyphs from the text, retrying if needed. - - auto tries = 0; - -#pragma warning(suppress : 26485) // so we can pass in the fontFeatureLengths to GetGlyphs without the analyzer complaining - auto hr = S_OK; - do - { - hr = _fontRenderData->Analyzer()->GetGlyphs( - &_text.at(textStart), - textLength, - run.fontFace.Get(), - run.isSideways, // isSideways, - WI_IsFlagSet(run.bidiLevel, 1), // isRightToLeft - &run.script, - _localeName.data(), - (run.isNumberSubstituted) ? _numberSubstitution.Get() : nullptr, - &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount - maxGlyphCount, // maxGlyphCount - &_glyphClusters.at(textStart), - &textProps.at(0), - &_glyphIndices.at(glyphStart), - &glyphProps.at(0), - &actualGlyphCount); - tries++; - - if (hr == E_NOT_SUFFICIENT_BUFFER) - { - // Try again using a larger buffer. - maxGlyphCount = _EstimateGlyphCount(maxGlyphCount); - const auto totalGlyphsArrayCount = glyphStart + maxGlyphCount; - - glyphProps.resize(maxGlyphCount); - _glyphIndices.resize(totalGlyphsArrayCount); - } - else - { - break; - } - } while (tries < 2); // We'll give it two chances. - - RETURN_IF_FAILED(hr); - - // Get the placement of the all the glyphs. - - _glyphAdvances.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphAdvances.size())); - _glyphOffsets.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphOffsets.size())); - - const auto fontSizeFormat = _formatInUse->GetFontSize(); - const auto fontSize = fontSizeFormat * run.fontScale; - - hr = _fontRenderData->Analyzer()->GetGlyphPlacements( - &_text.at(textStart), - &_glyphClusters.at(textStart), - &textProps.at(0), - textLength, - &_glyphIndices.at(glyphStart), - &glyphProps.at(0), - actualGlyphCount, - run.fontFace.Get(), - fontSize, - run.isSideways, - (run.bidiLevel & 1), // isRightToLeft - &run.script, - _localeName.data(), - &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount - &_glyphAdvances.at(glyphStart), - &_glyphOffsets.at(glyphStart)); - - RETURN_IF_FAILED(hr); - - // Set the final glyph count of this run and advance the starting glyph. - run.glyphCount = actualGlyphCount; - glyphStart += actualGlyphCount; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Adjusts the glyph information from shaping to fit the layout pattern required -// for our renderer. -// This is effectively a loop of _CorrectGlyphRun. See it for details. -// Arguments: -// - - Uses internal state -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_CorrectGlyphRuns() noexcept -{ - try - { - // For simple text, there is no need to correct runs. - if (_isEntireTextSimple) - { - return S_OK; - } - - // Correct each run separately. This is needed whenever script, locale, - // or reading direction changes. - for (UINT32 runIndex = 0; runIndex < _runs.size(); ++runIndex) - { - LOG_IF_FAILED(_CorrectGlyphRun(runIndex)); - } - - // If scale corrections were needed, we need to split the run. - for (auto& c : _glyphScaleCorrections) - { - // Split after the adjustment first so it - // takes a copy of all the run properties before we modify them. - // GH 4665: This is the other half of the potential future perf item. - // If glyphs needing the same scale are coalesced, we could - // break fewer times and have fewer runs. - - // Example - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1---------^2------- - // Scale Factors: - // 1.0 1.0 1.0 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 11 - // 2: IDX = 17, LEN = 9 - - // From the scale correction... we get - // IDX = where the scale starts - // LEN = how long the scale adjustment runs - // SCALE = the scale factor. - - // We need to split the run so the SCALE factor - // only applies from IDX to LEN. - - // This is the index after the segment we're splitting. - const auto afterIndex = c.textIndex + c.textLength; - - // If the after index is still within the text, split the back - // half off first so we don't apply the scale factor to anything - // after this glyph/run segment. - // Example relative to above sample state: - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // We must split off first at 14 to leave the existing factor from 14-16. - // (because the act of splitting copies all properties, we don't want to - // adjust the scale factor BEFORE splitting off the existing one.) - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----xx^2-^3------- - // (xx is where we're going to put the correction when all is said and done. - // We're adjusting the scale of the "MN" text only.) - // Scale Factors: - // 1 1 1 1 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 8 - // 2: IDX = 14, LEN = 3 - // 3: IDX = 17, LEN = 9 - if (afterIndex < _text.size()) - { - _SetCurrentRun(afterIndex); - _SplitCurrentRun(afterIndex); - } - // If it's after the text, don't bother. The correction will just apply - // from the begin point to the end of the text. - // Example relative to above sample state: - // Correction says: IDX = 24, LEN = 2 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1---------^2-----xx - // xx is where we're going to put the correction when all is said and done. - // We don't need to split off the back portion because there's nothing after the xx. - - // Now split just this glyph off. - // Example versus the one above where we did already split the back half off.. - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----^2^3-^4------- - // (The MN text has been broken into its own run, 2.) - // Scale Factors: - // 1 1 1 1 1 - // (arrows are run begin) - // 0: IDX = 0, LEN = 6 - // 1: IDX = 6, LEN = 6 - // 2: IDX = 12, LEN = 2 - // 2: IDX = 14, LEN = 3 - // 3: IDX = 17, LEN = 9 - _SetCurrentRun(c.textIndex); - _SplitCurrentRun(c.textIndex); - - // Get the run with the one glyph and adjust the scale. - auto& run = _GetCurrentRun(); - run.fontScale = c.scale; - // Correction says: IDX = 12, LEN = 2, FACTOR = 0.8 - // Text: - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - // LEN = 26 - // Runs: - // ^0----^1----^2^3-^4------- - // (We've now only corrected run 2, selecting only the MN to 0.8) - // Scale Factors: - // 1 1 .8 1 1 - } - - // Dump the glyph scale corrections now that we're done with them. - _glyphScaleCorrections.clear(); - - // Order the runs. - _OrderRuns(); - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Adjusts the advances for each glyph in the run so it fits within a fixed-column count of cells. -// Arguments: -// - runIndex - The ID number of the internal runs array to use while shaping. -// Return Value: -// - S_OK or suitable DirectWrite or STL error code -[[nodiscard]] HRESULT CustomTextLayout::_CorrectGlyphRun(const UINT32 runIndex) noexcept -try -{ - const Run& run = _runs.at(runIndex); - - if (run.textLength == 0) - { - return S_FALSE; // Nothing to do.. - } - - // We're going to walk through and check for advances that don't match the space that we expect to give out. - - // Glyph Indices represents the number inside the selected font where the glyph image/paths are found. - // Text represents the original text we gave in. - // Glyph Clusters represents the map between Text and Glyph Indices. - // - There is one Glyph Clusters map column per character of text. - // - The value of the cluster at any given position is relative to the 0 index of this run. - // (a.k.a. it resets to 0 for every run) - // - If multiple Glyph Cluster map values point to the same index, then multiple text chars were used - // to create the same glyph cluster. - // - The delta between the value from one Glyph Cluster's value and the next one is how many - // Glyph Indices are consumed to make that cluster. - - // We're going to walk the map to find what spans of text and glyph indices make one cluster. - const auto clusterMapBegin = _glyphClusters.cbegin() + run.textStart; - const auto clusterMapEnd = clusterMapBegin + run.textLength; - - // Walk through every glyph in the run, collect them into clusters, then adjust them to fit in - // however many columns are expected for display by the text buffer. -#pragma warning(suppress : 26496) // clusterBegin is updated at the bottom of the loop but analysis isn't seeing it. - for (auto clusterBegin = clusterMapBegin; clusterBegin < clusterMapEnd; /* we will increment this inside the loop*/) - { - // One or more glyphs might belong to a single cluster. - // Consider the following examples: - - // 1. - // U+00C1 is Á. - // That is text of length one. - // A given font might have a complete single glyph for this - // which will be mapped into the _glyphIndices array. - // _text[0] = Á - // _glyphIndices[0] = 153 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 1 - // The delta between the value of Clusters 0 and 1 is 1. - // The number of times "0" is specified is once. - // This means that we've represented one text with one glyph. - - // 2. - // U+0041 is A and U+0301 is a combining acute accent ´. - // That is a text length of two. - // A given font might have two glyphs for this - // which will be mapped into the _glyphIndices array. - // _text[0] = A - // _text[1] = ´ (U+0301, combining acute) - // _glyphIndices[0] = 153 - // _glyphIndices[1] = 421 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 0 - // _glyphClusters[2] = 2 - // The delta between the value of Clusters 0/1 and 2 is 2. - // The number of times "0" is specified is twice. - // This means that we've represented two text with two glyphs. - - // There are two more scenarios that can occur that get us into - // NxM territory (N text by M glyphs) - - // 3. - // U+00C1 is Á. - // That is text of length one. - // A given font might represent this as two glyphs - // which will be mapped into the _glyphIndices array. - // _text[0] = Á - // _glyphIndices[0] = 153 - // _glyphIndices[1] = 421 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 2 - // The delta between the value of Clusters 0 and 1 is 2. - // The number of times "0" is specified is once. - // This means that we've represented one text with two glyphs. - - // 4. - // U+0041 is A and U+0301 is a combining acute accent ´. - // That is a text length of two. - // A given font might represent this as one glyph - // which will be mapped into the _glyphIndices array. - // _text[0] = A - // _text[1] = ´ (U+0301, combining acute) - // _glyphIndices[0] = 984 - // _glyphClusters[0] = 0 - // _glyphClusters[1] = 0 - // _glyphClusters[2] = 1 - // The delta between the value of Clusters 0/1 and 2 is 1. - // The number of times "0" is specified is twice. - // This means that we've represented two text with one glyph. - - // Finally, there's one more dimension. - // Due to supporting a specific coordinate system, the text buffer - // has told us how many columns it expects the text it gave us to take - // when displayed. - // That is stored in _textClusterColumns with one value for each - // character in the _text array. - // It isn't aware of how glyphs actually get mapped. - // So it's giving us a column count in terms of text characters - // but expecting it to be applied to all the glyphs in the cluster - // required to represent that text. - // We'll collect that up and use it at the end to adjust our drawing. - - // Our goal below is to walk through and figure out... - // A. How many glyphs belong to this cluster? - // B. Which text characters belong with those glyphs? - // C. How many columns, in total, were we told we could use - // to draw the glyphs? - - // This is the value under the beginning position in the map. - const auto clusterValue = *clusterBegin; - - // Find the cluster end point inside the map. - // We want to walk forward in the map until it changes (or we reach the end). - const auto clusterEnd = std::find_if(clusterBegin, clusterMapEnd, [clusterValue](auto compareVal) -> bool { return clusterValue != compareVal; }); - - // The beginning of the text span is just how far the beginning of the cluster is into the map. - const auto clusterTextBegin = std::distance(_glyphClusters.cbegin(), clusterBegin); - - // The distance from beginning to end is the cluster text length. - const auto clusterTextLength = std::distance(clusterBegin, clusterEnd); - - // The beginning of the glyph span is just the original cluster value. - const auto clusterGlyphBegin = clusterValue + run.glyphStart; - - // The difference between the value inside the end iterator and the original value is the glyph length. - // If the end iterator was off the end of the map, then it's the total run glyph count minus wherever we started. - const auto clusterGlyphLength = (clusterEnd != clusterMapEnd ? *clusterEnd : run.glyphCount) - clusterValue; - - // Now we can specify the spans within the text-index and glyph-index based vectors - // that store our drawing metadata. - // All the text ones run [clusterTextBegin, clusterTextBegin + clusterTextLength) - // All the cluster ones run [clusterGlyphBegin, clusterGlyphBegin + clusterGlyphLength) - - // Get how many columns we expected the glyph to have. - const auto columns = base::saturated_cast(std::accumulate(_textClusterColumns.cbegin() + clusterTextBegin, - _textClusterColumns.cbegin() + clusterTextBegin + clusterTextLength, - 0u)); - - // Multiply into pixels to get the "advance" we expect this text/glyphs to take when drawn. - const auto advanceExpected = static_cast(columns * _width); - - // Sum up the advances across the entire cluster to find what the actual value is that we've been told. - const auto advanceActual = std::accumulate(_glyphAdvances.cbegin() + clusterGlyphBegin, - _glyphAdvances.cbegin() + clusterGlyphBegin + clusterGlyphLength, - 0.0f); - - // With certain font faces at certain sizes, the advances seem to be slightly more than - // the pixel grid; Cascadia Code at 13pt (though, 200% scale) had an advance of 10.000001. - // We don't want anything sub one hundredth of a cell to make us break up runs, because - // doing so results in suboptimal rendering. - // If what we expect is bigger than what we have... pad it out. - if ((advanceExpected - advanceActual) > 0.001f) - { - // Get the amount of space we have leftover. - const auto diff = advanceExpected - advanceActual; - - // Move the X offset (pixels to the right from the left edge) by half the excess space - // so half of it will be left of the glyph and the other half on the right. - // Here we need to move every glyph in the cluster. - std::for_each(_glyphOffsets.begin() + clusterGlyphBegin, - _glyphOffsets.begin() + clusterGlyphBegin + clusterGlyphLength, - [halfDiff = diff / 2](DWRITE_GLYPH_OFFSET& offset) -> void { offset.advanceOffset += halfDiff; }); - - // Set the advance of the final glyph in the set to all excess space not consumed by the first few so - // we get the perfect width we want. - _glyphAdvances.at(static_cast(clusterGlyphBegin) + clusterGlyphLength - 1) += diff; - } - // If what we expect is smaller than what we have... rescale the font size to get a smaller glyph to fit. - else if ((advanceExpected - advanceActual) < -0.001f) - { - const auto scaleProposed = advanceExpected / advanceActual; - - // Store the glyph scale correction for future run breaking - // GH 4665: In theory, we could also store the length of the new run and coalesce - // in case two adjacent glyphs need the same scale factor. - _glyphScaleCorrections.push_back(ScaleCorrection{ - gsl::narrow(clusterTextBegin), - gsl::narrow(clusterTextLength), - scaleProposed }); - - // Adjust all relevant advances by the scale factor. - std::for_each(_glyphAdvances.begin() + clusterGlyphBegin, - _glyphAdvances.begin() + clusterGlyphBegin + clusterGlyphLength, - [scaleProposed](float& advance) -> void { advance *= scaleProposed; }); - } - - clusterBegin = clusterEnd; - } - - // Certain fonts, like Batang, contain glyphs for hidden control - // and formatting characters. So we'll want to explicitly force their - // advance to zero. - // I'm leaving this here for future reference, but I don't think we want invisible glyphs for this renderer. - //if (run.script.shapes & DWRITE_SCRIPT_SHAPES_NO_VISUAL) - //{ - // std::fill(_glyphAdvances.begin() + glyphStart, - // _glyphAdvances.begin() + glyphStart + actualGlyphCount, - // 0.0f - // ); - //} - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Takes the analyzed and shaped textual information from the layout process and -// forwards it into the given renderer in a run-by-run fashion. -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - origin - pixel point of top left corner on final surface for drawing -// Return Value: -// - S_OK or suitable DirectX/DirectWrite/Direct2D result code. -[[nodiscard]] HRESULT CustomTextLayout::_DrawGlyphRuns(_In_opt_ void* clientDrawingContext, - IDWriteTextRenderer* renderer, - const D2D_POINT_2F origin) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, renderer); - - try - { - // We're going to start from the origin given and walk to the right for each - // sub-run that was calculated by the layout analysis. - auto mutableOrigin = origin; - - // Draw each run separately. - for (auto runIndex = 0; runIndex < gsl::narrow(_runs.size()); ++runIndex) - { - // Get the run - const Run& run = _runs.at(runIndex); - - if (!WI_IsFlagSet(run.bidiLevel, 1)) - { - RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, run)); - } - // This is the RTL behavior. We will advance to the last contiguous RTL run, draw that, - // and then keep on going backwards from there, and then move runIndex beyond. - // Let's say we have runs abcdEFGh, where runs EFG are RTL. - // Then we will draw them in the order abcdGFEh - else - { - const auto originalRunIndex = runIndex; - auto lastIndexRTL = runIndex; - - // Step 1: Get to the last contiguous RTL run from here - while (lastIndexRTL < gsl::narrow(_runs.size()) - 1) // only could ever advance if there's something left - { - const Run& nextRun = _runs.at(gsl::narrow_cast(lastIndexRTL + 1)); - if (WI_IsFlagSet(nextRun.bidiLevel, 1)) - { - lastIndexRTL++; - } - else - { - break; - } - } - - // Go from the last to the first and draw - for (runIndex = lastIndexRTL; runIndex >= originalRunIndex; runIndex--) - { - const Run& currentRun = _runs.at(runIndex); - RETURN_IF_FAILED(_DrawGlyphRun(clientDrawingContext, renderer, mutableOrigin, currentRun)); - } - runIndex = lastIndexRTL; // and the for loop will take the increment to the last one - } - } - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Draw the given run -// - The origin is updated to be after the run. -// Arguments: -// - clientDrawingContext - Optional pointer to information that the renderer might need -// while attempting to graphically place the text onto the screen -// - renderer - The interface to be used for actually putting text onto the screen -// - origin - pixel point of top left corner on final surface for drawing -// - run - the run to be drawn -[[nodiscard]] HRESULT CustomTextLayout::_DrawGlyphRun(_In_opt_ void* clientDrawingContext, - gsl::not_null renderer, - D2D_POINT_2F& mutableOrigin, - const Run& run) noexcept -{ - try - { - // Prepare the glyph run and description objects by converting our - // internal storage representation into something that matches DWrite's structures. - DWRITE_GLYPH_RUN glyphRun; - glyphRun.bidiLevel = run.bidiLevel; - glyphRun.fontEmSize = _formatInUse->GetFontSize() * run.fontScale; - glyphRun.fontFace = run.fontFace.Get(); - glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart); - glyphRun.glyphCount = run.glyphCount; - glyphRun.glyphIndices = &_glyphIndices.at(run.glyphStart); - glyphRun.glyphOffsets = &_glyphOffsets.at(run.glyphStart); - glyphRun.isSideways = false; - - DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDescription; - glyphRunDescription.clusterMap = _glyphClusters.data(); - glyphRunDescription.localeName = _localeName.data(); - glyphRunDescription.string = _text.data(); - glyphRunDescription.stringLength = run.textLength; - glyphRunDescription.textPosition = run.textStart; - - // Calculate the origin for the next run based on the amount of space - // that would be consumed. We are doing this calculation now, not after, - // because if the text is RTL then we need to advance immediately, before the - // write call since DirectX expects the origin to the RIGHT of the text for RTL. - const auto postOriginX = std::accumulate(_glyphAdvances.begin() + run.glyphStart, - _glyphAdvances.begin() + run.glyphStart + run.glyphCount, - mutableOrigin.x); - - // Check for RTL, if it is, apply space adjustment. - if (WI_IsFlagSet(glyphRun.bidiLevel, 1)) - { - mutableOrigin.x = postOriginX; - } - - // Try to draw it - RETURN_IF_FAILED(renderer->DrawGlyphRun(clientDrawingContext, - mutableOrigin.x, - mutableOrigin.y, - DWRITE_MEASURING_MODE_NATURAL, - &glyphRun, - &glyphRunDescription, - run.drawingEffect.Get())); - - // Either way, we should be at this point by the end of writing this sequence, - // whether it was LTR or RTL. - mutableOrigin.x = postOriginX; - } - CATCH_RETURN(); - return S_OK; -} - -// Routine Description: -// - Estimates the maximum number of glyph indices needed to hold a string of -// a given length. This is the formula given in the Uniscribe SDK and should -// cover most cases. Degenerate cases will require a reallocation. -// Arguments: -// - textLength - the number of wchar_ts in the original string -// Return Value: -// - An estimate of how many glyph spaces may be required in the shaping arrays -// to hold the data from a string of the given length. -[[nodiscard]] constexpr UINT32 CustomTextLayout::_EstimateGlyphCount(const UINT32 textLength) noexcept -{ - // This formula is from https://docs.microsoft.com/en-us/windows/desktop/api/dwrite/nf-dwrite-idwritetextanalyzer-getglyphs - // and is the recommended formula for estimating buffer size for glyph count. - return 3 * textLength / 2 + 16; -} - -#pragma region IDWriteTextAnalysisSource methods -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetTextAtPosition -// - This method will retrieve a substring of the text in this layout -// to be used in an analysis step. -// Arguments: -// - textPosition - The index of the first character of the text to retrieve. -// - textString - The pointer to the first character of text at the index requested. -// - textLength - The characters available at/after the textString pointer (string length). -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetTextAtPosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textString); - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - - *textString = nullptr; - *textLength = 0; - - if (textPosition < _text.size()) - { - *textString = &_text.at(textPosition); - *textLength = gsl::narrow(_text.size()) - textPosition; - } - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetTextBeforePosition -// - This method will retrieve a substring of the text in this layout -// to be used in an analysis step. -// Arguments: -// - textPosition - The index one after the last character of the text to retrieve. -// - textString - The pointer to the first character of text at the index requested. -// - textLength - The characters available at/after the textString pointer (string length). -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetTextBeforePosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textString); - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - - *textString = nullptr; - *textLength = 0; - - if (textPosition > 0 && textPosition <= _text.size()) - { - *textString = _text.data(); - *textLength = textPosition; - } - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetParagraphReadingDirection -// - This returns the implied reading direction for this block of text (LTR/RTL/etc.) -// Arguments: -// - -// Return Value: -// - The reading direction held for this layout from construction -[[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE CustomTextLayout::GetParagraphReadingDirection() noexcept -{ - return _readingDirection; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetLocaleName -// - Retrieves the locale name to apply to this text. Sometimes analysis and chosen glyphs vary on locale. -// Arguments: -// - textPosition - The index of the first character in the held string for which layout information is needed -// - textLength - How many characters of the string from the index that the returned locale applies to -// - localeName - Zero terminated string of the locale name. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetLocaleName(UINT32 textPosition, - _Out_ UINT32* textLength, - _Outptr_result_z_ const WCHAR** localeName) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - RETURN_HR_IF_NULL(E_INVALIDARG, localeName); - - *localeName = _localeName.data(); - *textLength = gsl::narrow(_text.size()) - textPosition; - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSource::GetNumberSubstitution -// - Retrieves the number substitution object name to apply to this text. -// Arguments: -// - textPosition - The index of the first character in the held string for which layout information is needed -// - textLength - How many characters of the string from the index that the returned locale applies to -// - numberSubstitution - Object to use for substituting numbers inside the determined range -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::GetNumberSubstitution(UINT32 textPosition, - _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, textLength); - RETURN_HR_IF_NULL(E_INVALIDARG, numberSubstitution); - - *numberSubstitution = nullptr; - *textLength = gsl::narrow(_text.size()) - textPosition; - - return S_OK; -} -#pragma endregion - -#pragma region IDWriteTextAnalysisSink methods -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetScriptAnalysis -// - Accepts the result of the script analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - scriptAnalysis - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetScriptAnalysis(UINT32 textPosition, - UINT32 textLength, - _In_ const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.script = *scriptAnalysis; - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetLineBreakpoints -// - Accepts the result of the line breakpoint computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - scriptAnalysis - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetLineBreakpoints(UINT32 textPosition, - UINT32 textLength, - _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) -{ - try - { - if (textLength > 0) - { - RETURN_HR_IF_NULL(E_INVALIDARG, lineBreakpoints); - std::copy_n(lineBreakpoints, textLength, _breakpoints.begin() + textPosition); - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetBidiLevel -// - Accepts the result of the bidirectional analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - explicitLevel - The analysis information for all glyphs starting at position for length. -// - resolvedLevel - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetBidiLevel(UINT32 textPosition, - UINT32 textLength, - UINT8 /*explicitLevel*/, - UINT8 resolvedLevel) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.bidiLevel = resolvedLevel; - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextAnalysisSink::SetNumberSubstitution -// - Accepts the result of the number substitution analysis computation performed by an IDWriteTextAnalyzer and -// stores it internally for later shaping and drawing purposes. -// Arguments: -// - textPosition - The index of the first character in the string that the result applies to -// - textLength - How many characters of the string from the index that the result applies to -// - numberSubstitution - The analysis information for all glyphs starting at position for length. -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::SetNumberSubstitution(UINT32 textPosition, - UINT32 textLength, - _In_ IDWriteNumberSubstitution* numberSubstitution) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - run.isNumberSubstituted = (numberSubstitution != nullptr); - } - } - CATCH_RETURN(); - - return S_OK; -} -#pragma endregion - -#pragma region internal methods for mimicking text analyzer pattern but for font fallback -// Routine Description: -// - Mimics an IDWriteTextAnalyser but for font fallback calculations. -// Arguments: -// - source - a text analysis source to retrieve substrings of the text to be analyzed -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Result: -// - S_OK, STL/GSL errors, or a suitable DirectWrite failure code on font fallback analysis. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, - UINT32 textPosition, - UINT32 textLength) -{ - try - { - // Get the font fallback first - ::Microsoft::WRL::ComPtr format1; - if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1)))) - { - // If IDWriteTextFormat1 does not exist, return directly as this OS version doesn't have font fallback. - return S_FALSE; - } - RETURN_HR_IF_NULL(E_NOINTERFACE, format1); - - ::Microsoft::WRL::ComPtr fallback; - RETURN_IF_FAILED(format1->GetFontFallback(&fallback)); - - ::Microsoft::WRL::ComPtr collection; - RETURN_IF_FAILED(format1->GetFontCollection(&collection)); - - std::wstring familyName; - familyName.resize(gsl::narrow_cast(format1->GetFontFamilyNameLength()) + 1); - RETURN_IF_FAILED(format1->GetFontFamilyName(familyName.data(), gsl::narrow(familyName.size()))); - - const auto weight = format1->GetFontWeight(); - const auto style = format1->GetFontStyle(); - const auto stretch = format1->GetFontStretch(); - - if (!fallback) - { - fallback = _fontRenderData->SystemFontFallback(); - } - - ::Microsoft::WRL::ComPtr fallback1; - ::Microsoft::WRL::ComPtr format3; - - // If the OS supports IDWriteFontFallback1 and IDWriteTextFormat3, we can use the - // newer MapCharacters to apply axes of variation to the font - if (!FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format3))) && !FAILED(fallback->QueryInterface(IID_PPV_ARGS(&fallback1)))) - { - const auto axesVector = _fontRenderData->GetAxisVector(weight, stretch, style, format3.Get()); - // Walk through and analyze the entire string - while (textLength > 0) - { - UINT32 mappedLength = 0; - ::Microsoft::WRL::ComPtr mappedFont; - auto scale = 0.0f; - - fallback1->MapCharacters(source, - textPosition, - textLength, - collection.Get(), - familyName.data(), - axesVector.data(), - gsl::narrow(axesVector.size()), - &mappedLength, - &scale, - &mappedFont); - - RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, mappedFont, scale)); - - textPosition += mappedLength; - textLength -= mappedLength; - } - } - else - { - // The chunk of code below is very similar to the one above, unfortunately this needs - // to stay for Win7 compatibility reasons. It is also not possible to combine the two - // because they call different versions of MapCharacters - - // Walk through and analyze the entire string - while (textLength > 0) - { - UINT32 mappedLength = 0; - ::Microsoft::WRL::ComPtr mappedFont; - auto scale = 0.0f; - - fallback->MapCharacters(source, - textPosition, - textLength, - collection.Get(), - familyName.data(), - weight, - style, - stretch, - &mappedLength, - &mappedFont, - &scale); - - RETURN_LAST_ERROR_IF(!mappedFont); - ::Microsoft::WRL::ComPtr face; - RETURN_IF_FAILED(mappedFont->CreateFontFace(&face)); - RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, face, scale)); - - textPosition += mappedLength; - textLength -= mappedLength; - } - } - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Mimics an IDWriteTextAnalysisSink but for font fallback calculations with our -// Analyzer mimic method above. -// Arguments: -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// - fontFace - the fontFace that applies to the substring range -// - scale - the scale of the font to apply -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetMappedFontFace(UINT32 textPosition, - UINT32 textLength, - const ::Microsoft::WRL::ComPtr& fontFace, - FLOAT const scale) -{ - try - { - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - - if (fontFace != nullptr) - { - RETURN_IF_FAILED(fontFace.As(&run.fontFace)); - } - else - { - run.fontFace = _fontInUse; - } - - // Store the font scale as well. - run.fontScale = scale; - } - } - CATCH_RETURN(); - - return S_OK; -} -#pragma endregion - -#pragma region internal methods for mimicking text analyzer to identify and split box drawing regions - -// Routine Description: -// - Helper method to detect if something is a box drawing character. -// Arguments: -// - wch - Specific character. -// Return Value: -// - True if box drawing. False otherwise. -static constexpr bool _IsBoxDrawingCharacter(const wchar_t wch) -{ - if (wch >= 0x2500 && wch <= 0x259F) - { - return true; - } - - return false; -} - -// Routine Description: -// - Corrects all runs for box drawing characteristics. Splits as it walks, if it must. -// If there are fallback fonts, this must happen after that's analyzed and after the -// advances are corrected so we can use the font size scaling factors to determine -// the appropriate layout heights for the correction scale/translate matrix. -// Arguments: -// - - Operates on all runs then orders them back up. -// Return Value: -// - S_OK, STL/GSL errors, or an E_ABORT from mathematical failures. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_CorrectBoxDrawing() noexcept -try -{ - RETURN_IF_FAILED(_AnalyzeBoxDrawing(this, 0, gsl::narrow(_text.size()))); - _OrderRuns(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - An analyzer to walk through the source text and search for runs of box drawing characters. -// It will segment the text into runs of those characters and mark them for special drawing, if necessary. -// Arguments: -// - source - a text analysis source to retrieve substrings of the text to be analyzed -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Result: -// - S_OK, STL/GSL errors, or an E_ABORT from mathematical failures. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_AnalyzeBoxDrawing(const gsl::not_null source, - UINT32 textPosition, - UINT32 textLength) -try -{ - // Walk through and analyze the entire string - while (textLength > 0) - { - // Get the substring of text remaining to analyze. - const WCHAR* text; - UINT32 length; - RETURN_IF_FAILED(source->GetTextAtPosition(textPosition, &text, &length)); - - // Put it into a view for iterator convenience. - const std::wstring_view str(text, length); - - // Find the first box drawing character in the string from the front. - const auto firstBox = std::find_if(str.cbegin(), str.cend(), _IsBoxDrawingCharacter); - - // If we found no box drawing characters, move on with life. - if (firstBox == str.cend()) - { - return S_OK; - } - // If we found one, keep looking forward until we find NOT a box drawing character. - else - { - // Find the last box drawing character. - const auto lastBox = std::find_if(firstBox, str.cend(), [](wchar_t wch) { return !_IsBoxDrawingCharacter(wch); }); - - // Skip distance is how far we had to move forward to find a box. - const auto firstBoxDistance = std::distance(str.cbegin(), firstBox); - UINT32 skipDistance; - RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(firstBoxDistance).AssignIfValid(&skipDistance)); - - // Move the position/length of the outside counters up to the part where boxes start. - textPosition += skipDistance; - textLength -= skipDistance; - - // Run distance is how many box characters in a row there are. - const auto runDistance = std::distance(firstBox, lastBox); - UINT32 mappedLength; - RETURN_HR_IF(E_ABORT, !base::MakeCheckedNum(runDistance).AssignIfValid(&mappedLength)); - - // Split the run and set the box effect on this segment of the run - RETURN_IF_FAILED(_SetBoxEffect(textPosition, mappedLength)); - - // Move us forward for the outer loop to continue scanning after this point. - textPosition += mappedLength; - textLength -= mappedLength; - } - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - A callback to split a run and apply box drawing characteristics to just that sub-run. -// Arguments: -// - textPosition - the index to start the substring operation -// - textLength - the length of the substring operation -// Return Value: -// - S_OK or appropriate STL/GSL failure code. -[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetBoxEffect(UINT32 textPosition, - UINT32 textLength) -try -{ - _SetCurrentRun(textPosition); - _SplitCurrentRun(textPosition); - - while (textLength > 0) - { - auto& run = _FetchNextRun(textLength); - - if (run.fontFace == _fontRenderData->DefaultFontFace()) - { - run.drawingEffect = _fontRenderData->DefaultBoxDrawingEffect(); - } - else - { - ::Microsoft::WRL::ComPtr eff; - RETURN_IF_FAILED(DxFontRenderData::s_CalculateBoxEffect(_formatInUse, _width, run.fontFace.Get(), run.fontScale, &eff)); - - // store data in the run - run.drawingEffect = std::move(eff); - } - } - - return S_OK; -} -CATCH_RETURN(); - -#pragma endregion - -#pragma region internal Run manipulation functions for storing information from sink callbacks -// Routine Description: -// - Used by the sink setters, this returns a reference to the next run. -// Position and length are adjusted to now point after the current run -// being returned. -// Arguments: -// - textLength - The amount of characters for which the next analysis result will apply. -// - The starting index is implicit based on the currently chosen run. -// Return Value: -// - reference to the run needed to store analysis data -[[nodiscard]] CustomTextLayout::LinkedRun& CustomTextLayout::_FetchNextRun(UINT32& textLength) -{ - const auto originalRunIndex = _runIndex; - - auto& run = _runs.at(originalRunIndex); - auto runTextLength = run.textLength; - - // Split the tail if needed (the length remaining is less than the - // current run's size). - if (textLength < runTextLength) - { - runTextLength = textLength; // Limit to what's actually left. - const auto runTextStart = run.textStart; - - _SplitCurrentRun(runTextStart + runTextLength); - } - else - { - // Just advance the current run. - _runIndex = run.nextRunIndex; - } - - textLength -= runTextLength; - - // Return a reference to the run that was just current. - // Careful, we have to look it up again as _SplitCurrentRun can resize the array and reshuffle all the reference locations - return _runs.at(originalRunIndex); -} - -// Routine Description: -// - Retrieves the current run according to the internal -// positioning set by Set/Split Current Run methods. -// Arguments: -// - -// Return Value: -// - Mutable reference of the current run. -[[nodiscard]] CustomTextLayout::LinkedRun& CustomTextLayout::_GetCurrentRun() -{ - return _runs.at(_runIndex); -} - -// Routine Description: -// - Move the current run to the given position. -// Since the analyzers generally return results in a forward manner, -// this will usually just return early. If not, find the -// corresponding run for the text position. -// Arguments: -// - textPosition - The index into the original string for which we want to select the corresponding run -// Return Value: -// - - Updates internal state -void CustomTextLayout::_SetCurrentRun(const UINT32 textPosition) -{ - if (_runIndex < _runs.size() && _runs.at(_runIndex).ContainsTextPosition(textPosition)) - { - return; - } - - _runIndex = gsl::narrow( - std::find(_runs.begin(), _runs.end(), textPosition) - _runs.begin()); -} - -// Routine Description: -// - Splits the current run and adjusts the run values accordingly. -// Arguments: -// - splitPosition - The index into the run where we want to split it into two -// Return Value: -// - - Updates internal state, the back half will be selected after running -void CustomTextLayout::_SplitCurrentRun(const UINT32 splitPosition) -{ - const auto runTextStart = _runs.at(_runIndex).textStart; - - if (splitPosition <= runTextStart) - return; // no change - - // Grow runs by one. - const auto totalRuns = _runs.size(); - try - { - _runs.resize(totalRuns + 1); - } - catch (...) - { - return; // Can't increase size. Return same run. - } - - // Copy the old run to the end. - auto& frontHalf = _runs.at(_runIndex); - auto& backHalf = _runs.back(); - backHalf = frontHalf; - - // Adjust runs' text positions and lengths. - const auto splitPoint = splitPosition - runTextStart; - backHalf.textStart += splitPoint; - backHalf.textLength -= splitPoint; - frontHalf.textLength = splitPoint; - frontHalf.nextRunIndex = gsl::narrow(totalRuns); - _runIndex = gsl::narrow(totalRuns); - - // If there is already a glyph mapping in these runs, - // we need to correct it for the split as well. - // See also (for NxM): - // https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained - - if (frontHalf.glyphCount > 0) - { - // Starting from this: - // TEXT (_text) - // f i ñ e - // CLUSTERMAP (_glyphClusters) - // 0 0 1 3 - // GLYPH INDICES (_glyphIndices) - // 19 81 23 72 - // With _runs length = 1 - // _runs(0): - // - Text Index: 0 - // - Text Length: 4 - // - Glyph Index: 0 - // - Glyph Length: 4 - // - // If we split at text index = 2 (between i and ñ)... - // ... then this will be the state after the text splitting above: - // - // TEXT (_text) - // f i ñ e - // CLUSTERMAP (_glyphClusters) - // 0 0 1 3 - // GLYPH INDICES (_glyphIndices) - // 19 81 23 72 - // With _runs length = 2 - // _runs(0): - // - Text Index: 0 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 4 - // _runs(1): - // - Text Index: 2 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 4 - // - // Notice that the text index/length values are correct, - // but we haven't fixed up the glyph index/lengths to match. - // We need it to say: - // With _runs length = 2 - // _runs(0): - // - Text Index: 0 - // - Text Length: 2 - // - Glyph Index: 0 - // - Glyph Length: 1 - // _runs(1): - // - Text Index: 2 - // - Text Length: 2 - // - Glyph Index: 1 - // - Glyph Length: 3 - // - // Which means that the cluster map value under the beginning - // of the right-hand text range is our offset to fix all the values. - // In this case, that's 1 corresponding with the ñ. - const auto mapOffset = _glyphClusters.at(backHalf.textStart); - - // The front half's glyph start index (position in _glyphIndices) - // stays the same. - - // The front half's glyph count (items in _glyphIndices to consume) - // is the offset value as that's now one past the end of the front half. - // (and count is end index + 1) - frontHalf.glyphCount = mapOffset; - - // The back half starts at the index that's one past the end of the front - backHalf.glyphStart += mapOffset; - - // And the back half count (since it was copied from the front half above) - // now just needs to be subtracted by how many we gave the front half. - backHalf.glyphCount -= mapOffset; - - // The CLUSTERMAP is also wrong given that it is relative - // to each run. And now there are two runs so the map - // value under the ñ and e need to updated to be relative - // to the text index "2" now instead of the original. - // - // For the entire range of the back half, we need to walk through and - // slide all the glyph mapping values to be relative to the new - // backHalf.glyphStart, or adjust it by the offset we just set it to. - const auto updateBegin = _glyphClusters.begin() + backHalf.textStart; - std::for_each(updateBegin, updateBegin + backHalf.textLength, [mapOffset](UINT16& n) { - n -= mapOffset; - }); - } -} - -// Routine Description: -// - Takes the linked runs stored in the state variable _runs -// and ensures that their vector/array indexes are in order in which they're drawn. -// - This is to be used after splitting and reordering them with the split/select functions -// as those manipulate the runs like a linked list (instead of an ordered array) -// while splitting to reduce copy overhead and just reorder them when complete with this func. -// Arguments: -// - - Manipulates _runs variable. -// Return Value: -// - -void CustomTextLayout::_OrderRuns() -{ - std::sort(_runs.begin(), _runs.end(), [](auto& a, auto& b) { return a.textStart < b.textStart; }); - for (UINT32 i = 0; i < _runs.size() - 1; ++i) - { - til::at(_runs, i).nextRunIndex = i + 1; - } - - _runs.back().nextRunIndex = 0; -} - -#pragma endregion diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h deleted file mode 100644 index 4ea4da0f4d9..00000000000 --- a/src/renderer/dx/CustomTextLayout.h +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include - -#include -#include -#include - -#include "BoxDrawingEffect.h" -#include "DxFontRenderData.h" -#include "../inc/Cluster.hpp" - -namespace Microsoft::Console::Render -{ - class CustomTextLayout : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextAnalysisSource, IDWriteTextAnalysisSink> - { - public: - // Based on the Windows 7 SDK sample at https://github.com/pauldotknopf/WindowsSDK7-Samples/tree/master/multimedia/DirectWrite/CustomLayout - - CustomTextLayout(const gsl::not_null fontRenderData); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const std::span clusters); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE Reset() noexcept; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetColumns(_Out_ UINT32* columns); - - // IDWriteTextLayout methods (but we don't actually want to implement them all, so just this one matching the existing interface) - [[nodiscard]] HRESULT STDMETHODCALLTYPE Draw(_In_opt_ void* clientDrawingContext, - _In_ IDWriteTextRenderer* renderer, - FLOAT originX, - FLOAT originY) noexcept; - - // IDWriteTextAnalysisSource methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 textPosition, - _Outptr_result_buffer_(*textLength) WCHAR const** textString, - _Out_ UINT32* textLength) noexcept override; - [[nodiscard]] DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() noexcept override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 textPosition, - _Out_ UINT32* textLength, - _Outptr_result_z_ const WCHAR** localeName) noexcept override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 textPosition, - _Out_ UINT32* textLength, - _COM_Outptr_ IDWriteNumberSubstitution** numberSubstitution) noexcept override; - - // IDWriteTextAnalysisSink methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetScriptAnalysis(UINT32 textPosition, - UINT32 textLength, - _In_ const DWRITE_SCRIPT_ANALYSIS* scriptAnalysis) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetLineBreakpoints(UINT32 textPosition, - UINT32 textLength, - _In_reads_(textLength) DWRITE_LINE_BREAKPOINT const* lineBreakpoints) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetBidiLevel(UINT32 textPosition, - UINT32 textLength, - UINT8 explicitLevel, - UINT8 resolvedLevel) override; - [[nodiscard]] HRESULT STDMETHODCALLTYPE SetNumberSubstitution(UINT32 textPosition, - UINT32 textLength, - _In_ IDWriteNumberSubstitution* numberSubstitution) override; - - protected: - // A single contiguous run of characters containing the same analysis results. - struct Run - { - Run() noexcept : - textStart(), - textLength(), - glyphStart(), - glyphCount(), - bidiLevel(), - script(), - isNumberSubstituted(), - isSideways(), - fontFace{ nullptr }, - fontScale{ 1.0 }, - drawingEffect{ nullptr } - { - } - - UINT32 textStart; // starting text position of this run - UINT32 textLength; // number of contiguous code units covered - UINT32 glyphStart; // starting glyph in the glyphs array - UINT32 glyphCount; // number of glyphs associated with this run of text - DWRITE_SCRIPT_ANALYSIS script; - UINT8 bidiLevel; - bool isNumberSubstituted; - bool isSideways; - ::Microsoft::WRL::ComPtr fontFace; - FLOAT fontScale; - ::Microsoft::WRL::ComPtr drawingEffect; - - inline bool ContainsTextPosition(UINT32 desiredTextPosition) const noexcept - { - return desiredTextPosition >= textStart && desiredTextPosition < textStart + textLength; - } - - inline bool operator==(UINT32 desiredTextPosition) const noexcept - { - // Search by text position using std::find - return ContainsTextPosition(desiredTextPosition); - } - }; - - // Single text analysis run, which points to the next run. - struct LinkedRun : Run - { - LinkedRun() noexcept : - nextRunIndex(0) - { - } - - UINT32 nextRunIndex; // index of next run - }; - - [[nodiscard]] LinkedRun& _FetchNextRun(UINT32& textLength); - [[nodiscard]] LinkedRun& _GetCurrentRun(); - void _SetCurrentRun(const UINT32 textPosition); - void _SplitCurrentRun(const UINT32 splitPosition); - void _OrderRuns(); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength); - [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFontFace(UINT32 textPosition, UINT32 textLength, const ::Microsoft::WRL::ComPtr& fontFace, FLOAT const scale); - - [[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(const gsl::not_null source, UINT32 textPosition, UINT32 textLength); - [[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength); - - [[nodiscard]] HRESULT _AnalyzeTextComplexity() noexcept; - [[nodiscard]] HRESULT _AnalyzeRuns() noexcept; - [[nodiscard]] HRESULT _ShapeGlyphRuns() noexcept; - [[nodiscard]] HRESULT _ShapeGlyphRun(const UINT32 runIndex, UINT32& glyphStart) noexcept; - [[nodiscard]] HRESULT _CorrectGlyphRuns() noexcept; - [[nodiscard]] HRESULT _CorrectGlyphRun(const UINT32 runIndex) noexcept; - [[nodiscard]] HRESULT STDMETHODCALLTYPE _CorrectBoxDrawing() noexcept; - [[nodiscard]] HRESULT _DrawGlyphRuns(_In_opt_ void* clientDrawingContext, - IDWriteTextRenderer* renderer, - const D2D_POINT_2F origin) noexcept; - [[nodiscard]] HRESULT _DrawGlyphRun(_In_opt_ void* clientDrawingContext, - gsl::not_null renderer, - D2D_POINT_2F& mutableOrigin, - const Run& run) noexcept; - - [[nodiscard]] static constexpr UINT32 _EstimateGlyphCount(const UINT32 textLength) noexcept; - - private: - // DirectWrite font render data - DxFontRenderData* _fontRenderData; - - // DirectWrite text formats - IDWriteTextFormat* _formatInUse; - - // DirectWrite font faces - IDWriteFontFace1* _fontInUse; - - // The text we're analyzing and processing into a layout - std::wstring _text; - std::vector _textClusterColumns; - size_t _width; - - // Properties of the text that might be relevant. - std::wstring _localeName; - ::Microsoft::WRL::ComPtr _numberSubstitution; - DWRITE_READING_DIRECTION _readingDirection; - - // Text analysis results - std::vector _runs; - std::vector _breakpoints; - - // Text analysis interim status variable (to assist the Analyzer Sink in operations involving _runs) - UINT32 _runIndex; - - // Glyph shaping results - - // Whether the entire text is determined to be simple and does not require full script shaping. - bool _isEntireTextSimple; - - std::vector _glyphOffsets; - - // Clusters are complicated. They're in respect to each individual run. - // The offsets listed here are in respect to the _text string, but from the beginning index of - // each run. - // That means if we have two runs, we will see 0 1 2 3 4 0 1 2 3 4 5 6 7... in this clusters count. - std::vector _glyphClusters; - - // This appears to be the index of the glyph inside each font. - std::vector _glyphIndices; - - // This is for calculating glyph advances when the entire text is simple. - std::vector _glyphDesignUnitAdvances; - - std::vector _glyphAdvances; - - struct ScaleCorrection - { - UINT32 textIndex; - UINT32 textLength; - float scale; - }; - - // These are used to further break the runs apart and adjust the font size so glyphs fit inside the cells. - std::vector _glyphScaleCorrections; - -#ifdef UNIT_TESTING - public: - CustomTextLayout() = default; - - friend class CustomTextLayoutTests; -#endif - }; -} diff --git a/src/renderer/dx/CustomTextRenderer.cpp b/src/renderer/dx/CustomTextRenderer.cpp deleted file mode 100644 index c217cbfc353..00000000000 --- a/src/renderer/dx/CustomTextRenderer.cpp +++ /dev/null @@ -1,979 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "CustomTextRenderer.h" - -#include "../../inc/DefaultSettings.h" - -#include -#include -#include - -using namespace Microsoft::Console::Render; - -#pragma region IDWritePixelSnapping methods -// Routine Description: -// - Implementation of IDWritePixelSnapping::IsPixelSnappingDisabled -// - Determines if we're allowed to snap text to pixels for this particular drawing context -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - isDisabled - TRUE if we do not snap to nearest pixels. FALSE otherwise. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::IsPixelSnappingDisabled(void* /*clientDrawingContext*/, - _Out_ BOOL* isDisabled) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, isDisabled); - - *isDisabled = false; - return S_OK; -} - -// Routine Description: -// - Implementation of IDWritePixelSnapping::GetPixelsPerDip -// - Retrieves the number of real monitor pixels to use per device-independent-pixel (DIP) -// - DIPs are used by DirectX all the way until the final drawing surface so things are only -// scaled at the very end and the complexity can be abstracted. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - pixelsPerDip - The number of pixels per DIP. 96 is standard DPI. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pixelsPerDip); - - const DrawingContext* drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - float dpiX, dpiY; - drawingContext->renderTarget->GetDpi(&dpiX, &dpiY); - *pixelsPerDip = dpiX / USER_DEFAULT_SCREEN_DPI; - return S_OK; -} - -// Routine Description: -// - Implementation of IDWritePixelSnapping::GetCurrentTransform -// - Retrieves the matrix transform to be used while laying pixels onto the -// drawing context -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - transform - The matrix transform to use to adapt DIP representations into real monitor coordinates. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::GetCurrentTransform(void* clientDrawingContext, - DWRITE_MATRIX* transform) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, transform); - - const DrawingContext* drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - // Retrieve as D2D1 matrix then copy into DWRITE matrix. - D2D1_MATRIX_3X2_F d2d1Matrix{ 0 }; - drawingContext->renderTarget->GetTransform(&d2d1Matrix); - - transform->dx = d2d1Matrix.dx; - transform->dy = d2d1Matrix.dy; - transform->m11 = d2d1Matrix.m11; - transform->m12 = d2d1Matrix.m12; - transform->m21 = d2d1Matrix.m21; - transform->m22 = d2d1Matrix.m22; - - return S_OK; -} -#pragma endregion - -#pragma region IDWriteTextRenderer methods -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawUnderline -// - Directs us to draw an underline on the given context at the given position. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - underline - The properties of the underline that we should use for drawing -// - clientDrawingEffect - any special effect to pass along for rendering -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::DrawUnderline(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) noexcept -{ - return _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + underline->offset, - underline->width, - underline->thickness, - underline->readingDirection, - underline->flowDirection); -} - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawStrikethrough -// - Directs us to draw a strikethrough on the given context at the given position. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - strikethrough - The properties of the strikethrough that we should use for drawing -// - clientDrawingEffect - any special effect to pass along for rendering -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::DrawStrikethrough(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) noexcept -{ - return _FillRectangle(clientDrawingContext, - clientDrawingEffect, - baselineOriginX, - baselineOriginY + strikethrough->offset, - strikethrough->width, - strikethrough->thickness, - strikethrough->readingDirection, - strikethrough->flowDirection); -} - -// Routine Description: -// - Helper method to draw a line through our text. -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - clientDrawingEffect - any special effect passed along for rendering -// - x - The left coordinate of the rectangle -// - y - The top coordinate of the rectangle -// - width - The width of the rectangle (from X to the right) -// - height - The height of the rectangle (from Y down) -// - readingDirection - textual reading information that could affect the rectangle -// - flowDirection - textual flow information that could affect the rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT CustomTextRenderer::_FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION /*readingDirection*/, - DWRITE_FLOW_DIRECTION /*flowDirection*/) noexcept -{ - auto drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext); - - // Get brush - ID2D1Brush* brush = drawingContext->foregroundBrush; - - if (clientDrawingEffect != nullptr) - { - brush = static_cast(clientDrawingEffect); - } - - const auto rect = D2D1::RectF(x, y, x + width, y + thickness); - drawingContext->renderTarget->FillRectangle(&rect, brush); - - return S_OK; -} - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawInlineObject -// - Passes drawing control from the outer layout down into the context of an embedded object -// which can have its own drawing layout and renderer properties at a given position -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - originX - The left coordinate of the draw position -// - originY - The top coordinate of the draw position -// - inlineObject - The object to draw at the position -// - isSideways - Should be drawn vertically instead of horizontally -// - isRightToLeft - Should be drawn RTL (or bottom to top) instead of the default way -// - clientDrawingEffect - any special effect passed along for rendering -// Return Value: -// - S_OK or appropriate error from the delegated inline object's draw call -[[nodiscard]] HRESULT CustomTextRenderer::DrawInlineObject(void* clientDrawingContext, - FLOAT originX, - FLOAT originY, - IDWriteInlineObject* inlineObject, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown* clientDrawingEffect) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, inlineObject); - - return inlineObject->Draw(clientDrawingContext, - this, - originX, - originY, - isSideways, - isRightToLeft, - clientDrawingEffect); -} - -// Function Description: -// - Attempt to draw the cursor. If the cursor isn't visible or on, this -// function will do nothing. If the cursor isn't within the bounds of the -// current run of text, then this function will do nothing. -// - This function will get called twice during a run, once before the text is -// drawn (underneath the text), and again after the text is drawn (above the -// text). Depending on if the cursor wants to be drawn above or below the -// text, this function will do nothing for the first/second pass -// (respectively). -// Arguments: -// - d2dContext - Pointer to the current D2D drawing context -// - textRunBounds - The bounds of the current run of text. -// - drawingContext - Pointer to structure of information required to draw -// - firstPass - true if we're being called before the text is drawn, false afterwards. -// Return Value: -// - S_FALSE if we did nothing, S_OK if we successfully painted, otherwise an appropriate HRESULT -[[nodiscard]] HRESULT CustomTextRenderer::DrawCursor(gsl::not_null d2dContext, - D2D1_RECT_F textRunBounds, - const DrawingContext& drawingContext, - const bool firstPass) -try -{ - if (!drawingContext.cursorInfo.has_value()) - { - return S_FALSE; - } - - const auto& options = drawingContext.cursorInfo.value(); - - // if the cursor is off, do nothing - it should not be visible. - if (!options.isOn) - { - return S_FALSE; - } - - const auto fInvert = !options.fUseColor; - // The normal, colored FullBox and legacy cursors are drawn in the first pass - // so they go behind the text. - // Inverted cursors are drawn in two passes. - // All other cursors are drawn in the second pass only. - if (!fInvert) - { - if (firstPass != (options.cursorType == CursorType::FullBox)) - { - return S_FALSE; - } - } - - // TODO GH#6338: Add support for `"cursorTextColor": null` for letting the - // cursor draw on top again. - - // Create rectangular block representing where the cursor can fill. - D2D1_RECT_F rect; - rect.left = options.coordCursor.x * drawingContext.cellSize.width; - rect.top = options.coordCursor.y * drawingContext.cellSize.height; - rect.right = rect.left + drawingContext.cellSize.width; - rect.bottom = rect.top + drawingContext.cellSize.height; - - // If we're double-width, make it one extra glyph wider - if (options.fIsDoubleWidth) - { - rect.right += drawingContext.cellSize.width; - } - - // If the cursor isn't within the bounds of this current run of text, do nothing. - if (rect.top > textRunBounds.bottom || - rect.bottom <= textRunBounds.top || - rect.left > textRunBounds.right || - rect.right <= textRunBounds.left) - { - return S_FALSE; - } - - auto paintType = CursorPaintType::Fill; - switch (options.cursorType) - { - case CursorType::Legacy: - { - // Enforce min/max cursor height - auto ulHeight = std::clamp(options.ulCursorHeightPercent, MinCursorHeightPercent, MaxCursorHeightPercent); - ulHeight = gsl::narrow_cast(drawingContext.cellSize.height * ulHeight) / 100; - ulHeight = std::max(ulHeight, MinCursorHeightPixels); // No smaller than 1px - - rect.top = rect.bottom - ulHeight; - break; - } - case CursorType::VerticalBar: - { - // It can't be wider than one cell or we'll have problems in invalidation, so restrict here. - // It's either the left + the proposed width from the ease of access setting, or - // it's the right edge of the block cursor as a maximum. - rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth); - break; - } - case CursorType::Underscore: - { - rect.top = rect.bottom - 1; - break; - } - case CursorType::DoubleUnderscore: - { - // Use rect for lower line. - rect.top = rect.bottom - 1; - break; - } - case CursorType::EmptyBox: - { - paintType = CursorPaintType::Outline; - break; - } - case CursorType::FullBox: - { - break; - } - default: - return E_NOTIMPL; - } - - // **DRAW** PHASE - Microsoft::WRL::ComPtr brush; - Microsoft::WRL::ComPtr originalTarget; - Microsoft::WRL::ComPtr commandList; - D2D1::Matrix3x2F originalTransform; - if (!fInvert) - { - // Make sure to make the cursor opaque - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(til::color{ options.cursorColor }, &brush)); - } - else - { - // CURSOR INVERSION - // We're trying to invert the cursor and the character underneath it without redrawing the text (as - // doing so would break up the run if it were part of a ligature). To do that, we're going to try - // to invert the content of the screen where the cursor would have been. - // - // This renderer, however, supports transparency. In fact, in its default configuration it will not - // have a background at all (it delegates background handling to somebody else.) You can't invert what - // isn't there. - // - // To properly invert the cursor in such a configuration, then, we have to play some tricks. Examples - // are given below for two cursor types, but this applies to all of them. - // - // First, we'll draw a "backplate" in the user's requested background color (with the alpha channel - // set to 0xFF). (firstPass == true) - // - // EMPTY BOX FILLED BOX - // ===== ===== - // = = ===== - // = = ===== - // = = ===== - // ===== ===== - // - // Then, outside of DrawCursor, the glyph is drawn: - // - // EMPTY BOX FILLED BOX - // ==A== ==A== - // =A A= =A=A= - // AAAAA AAAAA - // A A A===A - // A===A A===A - // - // Last, we'll draw the cursor again in all white and use that as the *mask* for inverting the already- - // drawn pixels. (firstPass == false) (# = mask, a = inverted A) - // - // EMPTY BOX FILLED BOX - // ##a## ##a## - // #A A# #a#a# - // aAAAa aaaaa - // a a a###a - // a###a a###a - if (firstPass) - { - // Draw a backplate behind the cursor in the *background* color so that we can invert it later. - // Make sure the cursor is always readable (see gh-3647) - const til::color color{ til::color{ drawingContext.backgroundBrush->GetColor() } ^ RGB(63, 63, 63) }; - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(color.with_alpha(255), - &brush)); - } - else - { - // When we're drawing an inverted cursor on the second pass (foreground), we want to draw it into a - // command list, which we will then draw down with MASK_INVERT. We'll draw it in white, - // which will ensure that every component is masked. - RETURN_IF_FAILED(d2dContext->CreateCommandList(&commandList)); - d2dContext->GetTarget(&originalTarget); - d2dContext->SetTarget(commandList.Get()); - // We use an identity transform here to avoid the active transform being applied twice. - d2dContext->GetTransform(&originalTransform); - d2dContext->SetTransform(D2D1::Matrix3x2F::Identity()); - RETURN_IF_FAILED(d2dContext->CreateSolidColorBrush(COLOR_WHITE, &brush)); - } - } - - switch (paintType) - { - case CursorPaintType::Fill: - { - d2dContext->FillRectangle(rect, brush.Get()); - break; - } - case CursorPaintType::Outline: - { - // DrawRectangle in straddles physical pixels in an attempt to draw a line - // between them. To avoid this, bump the rectangle around by half the stroke width. - rect.top += 0.5f; - rect.left += 0.5f; - rect.bottom -= 0.5f; - rect.right -= 0.5f; - - d2dContext->DrawRectangle(rect, brush.Get()); - break; - } - default: - return E_NOTIMPL; - } - - if (options.cursorType == CursorType::DoubleUnderscore) - { - // Draw upper line directly. - auto upperLine = rect; - upperLine.top -= 2; - upperLine.bottom -= 2; - d2dContext->FillRectangle(upperLine, brush.Get()); - } - - if (commandList) - { - // We drew the entire cursor in a command list - // so now we draw that command list using MASK_INVERT over the existing image - RETURN_IF_FAILED(commandList->Close()); - d2dContext->SetTarget(originalTarget.Get()); - d2dContext->SetTransform(originalTransform); - d2dContext->DrawImage(commandList.Get(), D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_MASK_INVERT); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Implementation of IDWriteTextRenderer::DrawInlineObject -// - Passes drawing control from the outer layout down into the context of an embedded object -// which can have its own drawing layout and renderer properties at a given position -// Arguments: -// - clientDrawingContext - Pointer to structure of information required to draw -// - baselineOriginX - The text baseline position's X coordinate -// - baselineOriginY - The text baseline position's Y coordinate -// - The baseline is generally not the top nor the bottom of the "cell" that -// text is drawn into. It's usually somewhere "in the middle" and depends on the -// font and the glyphs. It can be calculated during layout and analysis in respect -// to the given font and glyphs. -// - measuringMode - The mode to measure glyphs in the DirectWrite context -// - glyphRun - Information on the glyphs -// - glyphRunDescription - Further metadata about the glyphs used while drawing -// - clientDrawingEffect - any special effect passed along for rendering -// Return Value: -// - S_OK, GSL/WIL/STL error, or appropriate DirectX/Direct2D/DirectWrite based error while drawing. -[[nodiscard]] HRESULT CustomTextRenderer::DrawGlyphRun( - void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - DWRITE_MEASURING_MODE measuringMode, - const DWRITE_GLYPH_RUN* glyphRun, - const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - IUnknown* clientDrawingEffect) -{ - // Color glyph rendering sourced from https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/DWriteColorGlyph - -#pragma warning(suppress : 26429) // Symbol 'drawingContext' is never tested for nullness, it can be marked as not_null (f.23). - auto drawingContext = static_cast(clientDrawingContext); - - // Since we've delegated the drawing of the background of the text into this function, the origin passed in isn't actually the baseline. - // It's the top left corner. Save that off first. - const auto origin = D2D1::Point2F(baselineOriginX, baselineOriginY); - - // Then make a copy for the baseline origin (which is part way down the left side of the text, not the top or bottom). - // We'll use this baseline Origin for drawing the actual text. - const D2D1_POINT_2F baselineOrigin{ origin.x, origin.y + drawingContext->spacing.baseline }; - - ::Microsoft::WRL::ComPtr d2dContext; - RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // Determine clip rectangle - D2D1_RECT_F clipRect; - clipRect.top = origin.y + drawingContext->topClipOffset; - clipRect.bottom = origin.y + drawingContext->cellSize.height - drawingContext->bottomClipOffset; - clipRect.left = 0; - clipRect.right = FLT_MAX; - - // If we already have a clip rectangle, check if it different than the previous one. - if (_clipRect.has_value()) - { - const auto storedVal = _clipRect.value(); - // If it is different, pop off the old one and push the new one on. - if (storedVal.top != clipRect.top || storedVal.bottom != clipRect.bottom || - storedVal.left != clipRect.left || storedVal.right != clipRect.right) - { - d2dContext->PopAxisAlignedClip(); - - // Clip all drawing in this glyph run to where we expect. - // We need the AntialiasMode here to be Aliased to ensure - // that background boxes line up with each other and don't leave behind - // stray colors. - // See GH#3626 for more details. - d2dContext->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - _clipRect = clipRect; - } - } - // If we have no clip rectangle, it's easy. Push it on and go. - else - { - // See above for aliased flag explanation. - d2dContext->PushAxisAlignedClip(clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - _clipRect = clipRect; - } - - // Draw the background - // The rectangle needs to be deduced based on the origin and the BidiDirection - const auto advancesSpan = std::span{ glyphRun->glyphAdvances, glyphRun->glyphCount }; - const auto totalSpan = std::accumulate(advancesSpan.begin(), advancesSpan.end(), 0.0f); - - D2D1_RECT_F rect; - rect.top = origin.y; - rect.bottom = rect.top + drawingContext->cellSize.height; - rect.left = origin.x; - // Check for RTL, if it is, move rect.left to the left from the baseline - if (WI_IsFlagSet(glyphRun->bidiLevel, 1)) - { - rect.left -= totalSpan; - } - rect.right = rect.left + totalSpan; - - d2dContext->FillRectangle(rect, drawingContext->backgroundBrush); - - RETURN_IF_FAILED(DrawCursor(d2dContext.Get(), rect, *drawingContext, true)); - - // GH#5098: If we're rendering with cleartype text, we need to always render - // onto an opaque background. If our background _isn't_ opaque, then we need - // to use grayscale AA for this run of text. - // - // We can force grayscale AA for just this run of text by pushing a new - // layer onto the d2d context. We'll only need to do this for cleartype - // text, when our eventual background isn't actually opaque. See - // DxEngine::PaintBufferLine and DxEngine::UpdateDrawingBrushes for more - // details. - // - // DANGER: Layers slow us down. Only do this in the specific case where - // someone has chosen the slower ClearType antialiasing (versus the faster - // grayscale antialiasing). - - // First, create the scope_exit to pop the layer. If we don't need the - // layer, we'll just gracefully release it. - auto popLayer = wil::scope_exit([&d2dContext]() noexcept { - d2dContext->PopLayer(); - }); - - if (drawingContext->forceGrayscaleAA) - { - // Mysteriously, D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE actually - // gets us the behavior we want, which is grayscale. - d2dContext->PushLayer(D2D1::LayerParameters(rect, - nullptr, - D2D1_ANTIALIAS_MODE_ALIASED, - D2D1::IdentityMatrix(), - 1.0, - nullptr, - D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE), - nullptr); - } - else - { - popLayer.release(); - } - // Now go onto drawing the text. - - // First check if we want a color font and try to extract color emoji first. - // Color emoji are only available on Windows 10+ - static const auto s_isWindows10OrGreater = IsWindows10OrGreater(); - if (WI_IsFlagSet(drawingContext->options, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT) && s_isWindows10OrGreater) - { - ::Microsoft::WRL::ComPtr d2dContext4; - RETURN_IF_FAILED(d2dContext.As(&d2dContext4)); - - ::Microsoft::WRL::ComPtr dwriteFactory4; - RETURN_IF_FAILED(drawingContext->dwriteFactory->QueryInterface(dwriteFactory4.GetAddressOf())); - - // The list of glyph image formats this renderer is prepared to support. - const auto supportedFormats = - DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE | - DWRITE_GLYPH_IMAGE_FORMATS_CFF | - DWRITE_GLYPH_IMAGE_FORMATS_COLR | - DWRITE_GLYPH_IMAGE_FORMATS_SVG | - DWRITE_GLYPH_IMAGE_FORMATS_PNG | - DWRITE_GLYPH_IMAGE_FORMATS_JPEG | - DWRITE_GLYPH_IMAGE_FORMATS_TIFF | - DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; - - // Determine whether there are any color glyph runs within glyphRun. If - // there are, glyphRunEnumerator can be used to iterate through them. - ::Microsoft::WRL::ComPtr glyphRunEnumerator; - const auto hr = dwriteFactory4->TranslateColorGlyphRun(baselineOrigin, - glyphRun, - glyphRunDescription, - supportedFormats, - measuringMode, - nullptr, - 0, - &glyphRunEnumerator); - - // If the analysis found no color glyphs in the run, just draw normally. - if (hr == DWRITE_E_NOCOLOR) - { - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - baselineOrigin, - measuringMode, - glyphRun, - glyphRunDescription, - drawingContext->foregroundBrush, - clientDrawingEffect)); - } - else - { - RETURN_IF_FAILED(hr); - - ::Microsoft::WRL::ComPtr tempBrush; - - // Complex case: the run has one or more color runs within it. Iterate - // over the sub-runs and draw them, depending on their format. - for (;;) - { - BOOL haveRun; - RETURN_IF_FAILED(glyphRunEnumerator->MoveNext(&haveRun)); - if (!haveRun) - break; - - DWRITE_COLOR_GLYPH_RUN1 const* colorRun; - RETURN_IF_FAILED(glyphRunEnumerator->GetCurrentRun(&colorRun)); - - const auto currentBaselineOrigin = D2D1::Point2F(colorRun->baselineOriginX, colorRun->baselineOriginY); - - switch (colorRun->glyphImageFormat) - { - case DWRITE_GLYPH_IMAGE_FORMATS_PNG: - case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: - case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: - case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8: - { - // This run is bitmap glyphs. Use Direct2D to draw them. - d2dContext4->DrawColorBitmapGlyphRun(colorRun->glyphImageFormat, - currentBaselineOrigin, - &colorRun->glyphRun, - measuringMode); - } - break; - - case DWRITE_GLYPH_IMAGE_FORMATS_SVG: - { - // This run is SVG glyphs. Use Direct2D to draw them. - d2dContext4->DrawSvgGlyphRun(currentBaselineOrigin, - &colorRun->glyphRun, - drawingContext->foregroundBrush, - nullptr, // svgGlyphStyle - 0, // colorPaletteIndex - measuringMode); - } - break; - - case DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE: - case DWRITE_GLYPH_IMAGE_FORMATS_CFF: - case DWRITE_GLYPH_IMAGE_FORMATS_COLR: - default: - { - // This run is solid-color outlines, either from non-color - // glyphs or from COLR glyph layers. Use Direct2D to draw them. - - ID2D1Brush* layerBrush{ nullptr }; - // The rule is "if 0xffff, use current brush." See: - // https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_2/ns-dwrite_2-dwrite_color_glyph_run - if (colorRun->paletteIndex == 0xFFFF) - { - // This run uses the current text color. - layerBrush = drawingContext->foregroundBrush; - } - else - { - if (!tempBrush) - { - RETURN_IF_FAILED(d2dContext4->CreateSolidColorBrush(colorRun->runColor, &tempBrush)); - } - else - { - // This run specifies its own color. - tempBrush->SetColor(colorRun->runColor); - } - layerBrush = tempBrush.Get(); - } - - // Draw the run with the selected color. - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - currentBaselineOrigin, - measuringMode, - &colorRun->glyphRun, - colorRun->glyphRunDescription, - layerBrush, - clientDrawingEffect)); - } - break; - } - } - } - } - else - { - // Simple case: the run has no color glyphs. Draw the main glyph run - // using the current text color. - RETURN_IF_FAILED(_DrawBasicGlyphRun(drawingContext, - baselineOrigin, - measuringMode, - glyphRun, - glyphRunDescription, - drawingContext->foregroundBrush, - clientDrawingEffect)); - } - - RETURN_IF_FAILED(DrawCursor(d2dContext.Get(), rect, *drawingContext, false)); - - return S_OK; -} -#pragma endregion - -[[nodiscard]] HRESULT CustomTextRenderer::EndClip(void* clientDrawingContext) noexcept -try -{ - auto drawingContext = static_cast(clientDrawingContext); - RETURN_HR_IF(E_INVALIDARG, !drawingContext); - - if (_clipRect.has_value()) - { - drawingContext->renderTarget->PopAxisAlignedClip(); - _clipRect = std::nullopt; - } - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawBasicGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - ID2D1Brush* brush, - _In_opt_ IUnknown* clientDrawingEffect) -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - RETURN_HR_IF_NULL(E_INVALIDARG, brush); - - ::Microsoft::WRL::ComPtr d2dContext; - RETURN_IF_FAILED(clientDrawingContext->renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // If a special drawing effect was specified, see if we know how to deal with it. - if (clientDrawingEffect) - { - ::Microsoft::WRL::ComPtr boxEffect; - if (SUCCEEDED(clientDrawingEffect->QueryInterface(&boxEffect))) - { - return _DrawBoxRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription, boxEffect.Get()); - } - - //_DrawBasicGlyphRunManually(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription); - //_DrawGlowGlyphRun(clientDrawingContext, baselineOrigin, measuringMode, glyphRun, glyphRunDescription); - } - - // If we get down here, there either was no special effect or we don't know what to do with it. Use the standard GlyphRun drawing. - - // Using the context is the easiest/default way of drawing. - d2dContext->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, brush, measuringMode); - - return S_OK; -} - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawBoxRunManually(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE /*measuringMode*/, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/, - _In_ IBoxDrawingEffect* clientDrawingEffect) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingEffect); - - ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); - - ::Microsoft::WRL::ComPtr pathGeometry; - d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr geometrySink; - pathGeometry->Open(geometrySink.GetAddressOf()); - - glyphRun->fontFace->GetGlyphRunOutline( - glyphRun->fontEmSize, - glyphRun->glyphIndices, - glyphRun->glyphAdvances, - glyphRun->glyphOffsets, - glyphRun->glyphCount, - glyphRun->isSideways, - glyphRun->bidiLevel % 2, - geometrySink.Get()); - - geometrySink->Close(); - - // Can be used to see the dimensions of what is written. - /*D2D1_RECT_F bounds; - pathGeometry->GetBounds(D2D1::IdentityMatrix(), &bounds);*/ - // The bounds here are going to be centered around the baseline of the font. - // That is, the DWRITE_GLYPH_METRICS property for this glyph's baseline is going - // to be at the 0 point in the Y direction when we receive the geometry. - // The ascent will go up negative from Y=0 and the descent will go down positive from Y=0. - // As for the horizontal direction, I didn't study this in depth, but it appears to always be - // positive X with both the left and right edges being positive and away from X=0. - // For one particular instance, we might ask for the geometry for a U+2588 box and see the bounds as: - // - // Top= - // -20.315 - // ----------- - // | | - // | | - // Left= | | Right= - // 13.859 | | 26.135 - // | | - // Origin --> X | | - // (0,0) | | - // ----------- - // Bottom= - // 5.955 - - // Dig out the box drawing effect parameters. - BoxScale scale; - RETURN_IF_FAILED(clientDrawingEffect->GetScale(&scale)); - - // The scale transform will inflate the entire geometry first. - // We want to do this before it moves out of its original location as generally our - // algorithms for fitting cells will blow up the glyph to the size it needs to be first and then - // nudge it into place with the translations. - const auto scaleTransform = D2D1::Matrix3x2F::Scale(scale.HorizontalScale, scale.VerticalScale); - - // Now shift it all the way to where the baseline says it should be. - const auto baselineTransform = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y); - - // Finally apply the little "nudge" that we may have been directed to align it better with the cell. - const auto offsetTransform = D2D1::Matrix3x2F::Translation(scale.HorizontalTranslation, scale.VerticalTranslation); - - // The order is important here. Scale it first, then slide it into place. - const auto matrixTransformation = scaleTransform * baselineTransform * offsetTransform; - - ::Microsoft::WRL::ComPtr transformedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixTransformation, - transformedGeometry.GetAddressOf()); - - // Can be used to see the dimensions after translation. - /*D2D1_RECT_F boundsAfter; - transformedGeometry->GetBounds(D2D1::IdentityMatrix(), &boundsAfter);*/ - // Compare this to the original bounds above to see what the matrix did. - // To make it useful, first visualize for yourself the pixel dimensions of the cell - // based on the baselineOrigin and the exact integer cell width and heights that we're storing. - // You'll also probably need the full-pixel ascent and descent because the point we're given - // is the baseline, not the top left corner of the cell as we're used to. - // Most of these metrics can be found in the initial font creation routines or in - // the line spacing applied to the text format (member variables on the renderer). - // baselineOrigin = (0, 567) - // fullPixelAscent = 39 - // fullPixelDescent = 9 - // cell dimensions = 26 x 48 (notice 48 height is 39 + 9 or ascent + descent) - // This means that our cell should be the rectangle - // - // T=528 - // |-------| - // L=0 | | - // | | - // Baseline->x | - // Origin | | R=26 - // |-------| - // B=576 - // - // And we'll want to check that the bounds after transform will fit the glyph nicely inside - // this box. - // If not? We didn't do the scaling or translation correctly. Oops. - - // Fill in the geometry. Don't outline, it can leave stuff outside the area we expect. - clientDrawingContext->renderTarget->FillGeometry(transformedGeometry.Get(), clientDrawingContext->foregroundBrush); - - return S_OK; -} -CATCH_RETURN(); - -[[nodiscard]] HRESULT CustomTextRenderer::_DrawGlowGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE /*measuringMode*/, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, clientDrawingContext); - RETURN_HR_IF_NULL(E_INVALIDARG, glyphRun); - - // This is glow text manually - ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); - - ::Microsoft::WRL::ComPtr pathGeometry; - d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr geometrySink; - pathGeometry->Open(geometrySink.GetAddressOf()); - - glyphRun->fontFace->GetGlyphRunOutline( - glyphRun->fontEmSize, - glyphRun->glyphIndices, - glyphRun->glyphAdvances, - glyphRun->glyphOffsets, - glyphRun->glyphCount, - glyphRun->isSideways, - glyphRun->bidiLevel % 2, - geometrySink.Get()); - - geometrySink->Close(); - - const auto matrixAlign = D2D1::Matrix3x2F::Translation(baselineOrigin.x, baselineOrigin.y); - - ::Microsoft::WRL::ComPtr transformedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixAlign, - transformedGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr alignedGeometry; - d2dFactory->CreateTransformedGeometry(pathGeometry.Get(), - &matrixAlign, - alignedGeometry.GetAddressOf()); - - ::Microsoft::WRL::ComPtr brush; - ::Microsoft::WRL::ComPtr outlineBrush; - - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), brush.GetAddressOf()); - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), outlineBrush.GetAddressOf()); - - clientDrawingContext->renderTarget->DrawGeometry(transformedGeometry.Get(), outlineBrush.Get(), 2.0f); - - clientDrawingContext->renderTarget->FillGeometry(alignedGeometry.Get(), brush.Get()); - - return S_OK; -} diff --git a/src/renderer/dx/CustomTextRenderer.h b/src/renderer/dx/CustomTextRenderer.h deleted file mode 100644 index 188b843d950..00000000000 --- a/src/renderer/dx/CustomTextRenderer.h +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include "BoxDrawingEffect.h" -#include "../../renderer/inc/CursorOptions.h" - -namespace Microsoft::Console::Render -{ - struct DrawingContext - { - DrawingContext(ID2D1RenderTarget* renderTarget, - ID2D1SolidColorBrush* foregroundBrush, - ID2D1SolidColorBrush* backgroundBrush, - bool forceGrayscaleAA, - IDWriteFactory* dwriteFactory, - const DWRITE_LINE_SPACING spacing, - const D2D_SIZE_F cellSize, - const D2D_SIZE_F targetSize, - const std::optional& cursorInfo, - const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) noexcept : - renderTarget(renderTarget), - foregroundBrush(foregroundBrush), - backgroundBrush(backgroundBrush), - useBoldFont(false), - useItalicFont(false), - forceGrayscaleAA(forceGrayscaleAA), - dwriteFactory(dwriteFactory), - spacing(spacing), - cellSize(cellSize), - targetSize(targetSize), - cursorInfo(cursorInfo), - options(options), - topClipOffset(0), - bottomClipOffset(0) - { - } - - ID2D1RenderTarget* renderTarget; - ID2D1SolidColorBrush* foregroundBrush; - ID2D1SolidColorBrush* backgroundBrush; - bool useBoldFont; - bool useItalicFont; - bool forceGrayscaleAA; - IDWriteFactory* dwriteFactory; - DWRITE_LINE_SPACING spacing; - D2D_SIZE_F cellSize; - D2D_SIZE_F targetSize; - std::optional cursorInfo; - D2D1_DRAW_TEXT_OPTIONS options; - FLOAT topClipOffset; - FLOAT bottomClipOffset; - }; - - // Helper to choose which Direct2D method to use when drawing the cursor rectangle - enum class CursorPaintType - { - Fill, - Outline - }; - - constexpr const ULONG MinCursorHeightPixels = 1; - constexpr const ULONG MinCursorHeightPercent = 1; - constexpr const ULONG MaxCursorHeightPercent = 100; - - class CustomTextRenderer : public ::Microsoft::WRL::RuntimeClass<::Microsoft::WRL::RuntimeClassFlags<::Microsoft::WRL::ClassicCom | ::Microsoft::WRL::InhibitFtmBase>, IDWriteTextRenderer> - { - public: - // http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html - // https://docs.microsoft.com/en-us/windows/desktop/DirectWrite/how-to-implement-a-custom-text-renderer - - // IDWritePixelSnapping methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void* clientDrawingContext, - _Out_ BOOL* isDisabled) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void* clientDrawingContext, - _Out_ FLOAT* pixelsPerDip) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE GetCurrentTransform(void* clientDrawingContext, - _Out_ DWRITE_MATRIX* transform) noexcept override; - - // IDWriteTextRenderer methods - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawGlyphRun(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - IUnknown* clientDrawingEffect) override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawUnderline(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_UNDERLINE* underline, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawStrikethrough(void* clientDrawingContext, - FLOAT baselineOriginX, - FLOAT baselineOriginY, - _In_ const DWRITE_STRIKETHROUGH* strikethrough, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE DrawInlineObject(void* clientDrawingContext, - FLOAT originX, - FLOAT originY, - IDWriteInlineObject* inlineObject, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown* clientDrawingEffect) noexcept override; - - [[nodiscard]] HRESULT STDMETHODCALLTYPE EndClip(void* clientDrawingContext) noexcept; - - [[nodiscard]] static HRESULT DrawCursor(gsl::not_null d2dContext, - D2D1_RECT_F textRunBounds, - const DrawingContext& drawingContext, - const bool firstPass); - - private: - [[nodiscard]] HRESULT _FillRectangle(void* clientDrawingContext, - IUnknown* clientDrawingEffect, - float x, - float y, - float width, - float thickness, - DWRITE_READING_DIRECTION readingDirection, - DWRITE_FLOW_DIRECTION flowDirection) noexcept; - - [[nodiscard]] HRESULT _DrawBasicGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - ID2D1Brush* brush, - _In_opt_ IUnknown* clientDrawingEffect); - - [[nodiscard]] HRESULT _DrawBoxRunManually(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, - _In_ IBoxDrawingEffect* clientDrawingEffect) noexcept; - - [[nodiscard]] HRESULT _DrawGlowGlyphRun(DrawingContext* clientDrawingContext, - D2D1_POINT_2F baselineOrigin, - DWRITE_MEASURING_MODE measuringMode, - _In_ const DWRITE_GLYPH_RUN* glyphRun, - _In_opt_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription) noexcept; - - std::optional _clipRect; - }; -} diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp deleted file mode 100644 index b0f79cde39a..00000000000 --- a/src/renderer/dx/DxFontInfo.cpp +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "DxFontInfo.h" - -#include -#include - -#include "../base/FontCache.h" - -static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" }; - -using namespace Microsoft::Console::Render; - -DxFontInfo::DxFontInfo(IDWriteFactory1* dwriteFactory) : - DxFontInfo{ dwriteFactory, {}, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL } -{ -} - -DxFontInfo::DxFontInfo( - IDWriteFactory1* dwriteFactory, - std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) : - _familyName(familyName), - _weight(weight), - _style(style), - _stretch(stretch), - _didFallback(false) -{ - __assume(dwriteFactory != nullptr); - THROW_IF_FAILED(dwriteFactory->GetSystemFontCollection(_fontCollection.addressof(), FALSE)); -} - -bool DxFontInfo::operator==(const DxFontInfo& other) const noexcept -{ - return (_familyName == other._familyName && - _weight == other._weight && - _style == other._style && - _stretch == other._stretch && - _didFallback == other._didFallback); -} - -std::wstring_view DxFontInfo::GetFamilyName() const noexcept -{ - return _familyName; -} - -void DxFontInfo::SetFamilyName(const std::wstring_view familyName) -{ - _familyName = familyName; -} - -DWRITE_FONT_WEIGHT DxFontInfo::GetWeight() const noexcept -{ - return _weight; -} - -void DxFontInfo::SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept -{ - _weight = weight; -} - -DWRITE_FONT_STYLE DxFontInfo::GetStyle() const noexcept -{ - return _style; -} - -void DxFontInfo::SetStyle(const DWRITE_FONT_STYLE style) noexcept -{ - _style = style; -} - -DWRITE_FONT_STRETCH DxFontInfo::GetStretch() const noexcept -{ - return _stretch; -} - -void DxFontInfo::SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept -{ - _stretch = stretch; -} - -bool DxFontInfo::GetFallback() const noexcept -{ - return _didFallback; -} - -IDWriteFontCollection* DxFontInfo::GetFontCollection() const noexcept -{ - return _fontCollection.get(); -} - -void DxFontInfo::SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch) -{ - _familyName = familyName; - _weight = weight; - _style = style; - _stretch = stretch; -} - -// Routine Description: -// - Attempts to locate the font given, but then begins falling back if we cannot find it. -// - We'll try to fall back to Consolas with the given weight/stretch/style first, -// then try Consolas again with normal weight/stretch/style, -// and if nothing works, then we'll throw an error. -// Arguments: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::ResolveFontFaceWithFallback(std::wstring& localeName) -{ - // First attempt to find exactly what the user asked for. - _didFallback = false; - Microsoft::WRL::ComPtr face{ nullptr }; - - // GH#10211 - wrap this all up in a try/catch. If the nearby fonts are - // corrupted, then we don't want to throw out of this top half of this - // method. We still want to fall back to a font that's reasonable, below. - try - { - face = _FindFontFace(localeName); - } - CATCH_LOG(); - - if constexpr (Feature_NearbyFontLoading::IsEnabled()) - { - try - { - if (!face) - { - _fontCollection = FontCache::GetCached(); - face = _FindFontFace(localeName); - } - } - CATCH_LOG(); - } - - if (!face) - { - // If we missed, try looking a little more by trimming the last word off the requested family name a few times. - // Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and - // an unexpected error dialog. We theoretically could detect the weight words and convert them, but this - // is the quick fix for the majority scenario. - // The long/full fix is backlogged to GH#9744 - // Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over - // this resolution. - while (!face && !_familyName.empty()) - { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } - - // trim string down to just before the found space - // (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string) - _familyName = _familyName.substr(0, lastSpace); - - try - { - // Try to find it with the shortened family name - face = _FindFontFace(localeName); - } - CATCH_LOG(); - } - } - - // Alright, if our quick shot at trimming didn't work either... - // move onto looking up a font from our hard-coded list of fonts - // that should really always be available. - if (!face) - { - for (const auto fallbackFace : FALLBACK_FONT_FACES) - { - _familyName = fallbackFace; - - try - { - face = _FindFontFace(localeName); - } - CATCH_LOG(); - - if (face) - { - _didFallback = true; - break; - } - } - } - - THROW_HR_IF_NULL(E_FAIL, face); - - return face; -} - -// Routine Description: -// - Locates a suitable font face from the given information -// Arguments: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -#pragma warning(suppress : 26429) // C26429: Symbol 'fontCollection' is never tested for nullness, it can be marked as not_null (f.23). -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::_FindFontFace(std::wstring& localeName) -{ - Microsoft::WRL::ComPtr fontFace; - - UINT32 familyIndex; - BOOL familyExists; - - THROW_IF_FAILED(_fontCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists)); - - if (familyExists) - { - Microsoft::WRL::ComPtr fontFamily; - THROW_IF_FAILED(_fontCollection->GetFontFamily(familyIndex, &fontFamily)); - - Microsoft::WRL::ComPtr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(GetWeight(), GetStretch(), GetStyle(), &font)); - - Microsoft::WRL::ComPtr fontFace0; - THROW_IF_FAILED(font->CreateFontFace(&fontFace0)); - - THROW_IF_FAILED(fontFace0.As(&fontFace)); - - // Retrieve metrics in case the font we created was different than what was requested. - _weight = font->GetWeight(); - _stretch = font->GetStretch(); - _style = font->GetStyle(); - - // Dig the family name out at the end to return it. - _familyName = _GetFontFamilyName(fontFamily.Get(), localeName); - } - - return fontFace; -} - -// Routine Description: -// - Retrieves the font family name out of the given object in the given locale. -// - If we can't find a valid name for the given locale, we'll fallback and report it back. -// Arguments: -// - fontFamily - DirectWrite font family object -// - localeName - The locale in which the name should be retrieved. -// - If fallback occurred, this is updated to what we retrieved instead. -// Return Value: -// - Localized string name of the font family -[[nodiscard]] std::wstring DxFontInfo::_GetFontFamilyName(const gsl::not_null fontFamily, - std::wstring& localeName) -{ - // See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection - Microsoft::WRL::ComPtr familyNames; - THROW_IF_FAILED(fontFamily->GetFamilyNames(&familyNames)); - - // First we have to find the right family name for the locale. We're going to bias toward what the caller - // requested, but fallback if we need to and reply with the locale we ended up choosing. - UINT32 index = 0; - BOOL exists = false; - - // This returns S_OK whether or not it finds a locale name. Check exists field instead. - // If it returns an error, it's a real problem, not an absence of this locale name. - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename - THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); - - // If we tried and it still doesn't exist, try with the fallback locale. - if (!exists) - { - localeName = L"en-us"; - THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists)); - } - - // If it still doesn't exist, we're going to try index 0. - if (!exists) - { - index = 0; - - // Get the locale name out so at least the caller knows what locale this name goes with. - UINT32 length = 0; - THROW_IF_FAILED(familyNames->GetLocaleNameLength(index, &length)); - localeName.resize(length); - - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalenamelength - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalename - // GetLocaleNameLength does not include space for null terminator, but GetLocaleName needs it so add one. - THROW_IF_FAILED(familyNames->GetLocaleName(index, localeName.data(), length + 1)); - } - - // OK, now that we've decided which family name and the locale that it's in... let's go get it. - UINT32 length = 0; - THROW_IF_FAILED(familyNames->GetStringLength(index, &length)); - - // Make our output buffer and resize it so it is allocated. - std::wstring retVal; - retVal.resize(length); - - // FINALLY, go fetch the string name. - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstringlength - // https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstring - // Once again, GetStringLength is without the null, but GetString needs the null. So add one. - THROW_IF_FAILED(familyNames->GetString(index, retVal.data(), length + 1)); - - // and return it. - return retVal; -} diff --git a/src/renderer/dx/DxFontInfo.h b/src/renderer/dx/DxFontInfo.h deleted file mode 100644 index c9ed0a41e2c..00000000000 --- a/src/renderer/dx/DxFontInfo.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include -#include -#include - -namespace Microsoft::Console::Render -{ - class DxFontInfo - { - public: - DxFontInfo(IDWriteFactory1* dwriteFactory); - - DxFontInfo( - IDWriteFactory1* dwriteFactory, - std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - bool operator==(const DxFontInfo& other) const noexcept; - - std::wstring_view GetFamilyName() const noexcept; - void SetFamilyName(const std::wstring_view familyName); - - DWRITE_FONT_WEIGHT GetWeight() const noexcept; - void SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept; - - DWRITE_FONT_STYLE GetStyle() const noexcept; - void SetStyle(const DWRITE_FONT_STYLE style) noexcept; - - DWRITE_FONT_STRETCH GetStretch() const noexcept; - void SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept; - - bool GetFallback() const noexcept; - IDWriteFontCollection* GetFontCollection() const noexcept; - - void SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch); - - [[nodiscard]] ::Microsoft::WRL::ComPtr ResolveFontFaceWithFallback(std::wstring& localeName); - - private: - [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(std::wstring& localeName); - - [[nodiscard]] std::wstring _GetFontFamilyName(const gsl::not_null fontFamily, - std::wstring& localeName); - - // The font name we should be looking for - std::wstring _familyName; - - // The weight (bold, light, etc.) - DWRITE_FONT_WEIGHT _weight; - - // Normal, italic, etc. - DWRITE_FONT_STYLE _style; - - // The stretch of the font is the spacing between each letter - DWRITE_FONT_STRETCH _stretch; - - wil::com_ptr _fontCollection; - - // Indicates whether we couldn't match the user request and had to choose from a hard-coded default list. - bool _didFallback; - }; -} diff --git a/src/renderer/dx/DxFontRenderData.cpp b/src/renderer/dx/DxFontRenderData.cpp deleted file mode 100644 index 7765067d0b7..00000000000 --- a/src/renderer/dx/DxFontRenderData.cpp +++ /dev/null @@ -1,923 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "DxFontRenderData.h" - -#include - -static constexpr float POINTS_PER_INCH = 72.0f; -static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us"; -static constexpr size_t TAG_LENGTH = 4; - -using namespace Microsoft::Console::Render; - -DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory) : - _dwriteFactory(std::move(dwriteFactory)), - _defaultFontInfo{ _dwriteFactory.Get() }, - _lineSpacing{}, - _lineMetrics{}, - _fontSize{} -{ -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::Analyzer() -{ - if (!_dwriteTextAnalyzer) - { - Microsoft::WRL::ComPtr analyzer; - THROW_IF_FAILED(_dwriteFactory->CreateTextAnalyzer(&analyzer)); - THROW_IF_FAILED(analyzer.As(&_dwriteTextAnalyzer)); - } - - return _dwriteTextAnalyzer; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::SystemFontFallback() -{ - if (!_systemFontFallback) - { - ::Microsoft::WRL::ComPtr factory2; - THROW_IF_FAILED(_dwriteFactory.As(&factory2)); - factory2->GetSystemFontFallback(&_systemFontFallback); - } - - return _systemFontFallback; -} - -[[nodiscard]] std::wstring DxFontRenderData::UserLocaleName() -{ - if (_userLocaleName.empty()) - { - std::array localeName; - - const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow(localeName.size())); - if (returnCode) - { - _userLocaleName = { localeName.data() }; - } - else - { - _userLocaleName = { FALLBACK_LOCALE.data(), FALLBACK_LOCALE.size() }; - } - } - - return _userLocaleName; -} - -[[nodiscard]] til::size DxFontRenderData::GlyphCell() noexcept -{ - return _glyphCell; -} - -[[nodiscard]] DxFontRenderData::LineMetrics DxFontRenderData::GetLineMetrics() noexcept -{ - return _lineMetrics; -} - -[[nodiscard]] DWRITE_FONT_WEIGHT DxFontRenderData::DefaultFontWeight() noexcept -{ - return _defaultFontInfo.GetWeight(); -} - -[[nodiscard]] DWRITE_FONT_STYLE DxFontRenderData::DefaultFontStyle() noexcept -{ - return _defaultFontInfo.GetStyle(); -} - -[[nodiscard]] DWRITE_FONT_STRETCH DxFontRenderData::DefaultFontStretch() noexcept -{ - return _defaultFontInfo.GetStretch(); -} - -[[nodiscard]] const std::vector& DxFontRenderData::DefaultFontFeatures() const noexcept -{ - return _featureVector; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultTextFormat() -{ - return TextFormatWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultFontFace() -{ - return FontFaceWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultBoxDrawingEffect() -{ - if (!_boxDrawingEffect) - { - // Calculate and cache the box effect for the base font. Scale is 1.0f because the base font is exactly the scale we want already. - THROW_IF_FAILED(s_CalculateBoxEffect(DefaultTextFormat().Get(), _glyphCell.width, DefaultFontFace().Get(), 1.0f, &_boxDrawingEffect)); - } - - return _boxDrawingEffect; -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) -{ - const auto textFormatIt = _textFormatMap.find(_ToMapKey(weight, style, stretch)); - if (textFormatIt == _textFormatMap.end()) - { - auto fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); - - // Create the font with the fractional pixel height size. - // It should have an integer pixel width by our math. - // Then below, apply the line spacing to the format to position the floating point pixel height characters - // into a cell that has an integer pixel height leaving some padding above/below as necessary to round them out. - auto localeName = UserLocaleName(); - Microsoft::WRL::ComPtr textFormat; - THROW_IF_FAILED(_BuildTextFormat(fontInfo, localeName).As(&textFormat)); - THROW_IF_FAILED(textFormat->SetLineSpacing(_lineSpacing.method, _lineSpacing.height, _lineSpacing.baseline)); - THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); - THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)); - - _textFormatMap.emplace(_ToMapKey(weight, style, stretch), textFormat); - return textFormat; - } - else - { - return textFormatIt->second; - } -} - -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) -{ - const auto fontFaceIt = _fontFaceMap.find(_ToMapKey(weight, style, stretch)); - if (fontFaceIt == _fontFaceMap.end()) - { - auto fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); - - auto fontLocaleName = UserLocaleName(); - auto fontFace = fontInfo.ResolveFontFaceWithFallback(fontLocaleName); - - _fontFaceMap.emplace(_ToMapKey(weight, style, stretch), fontFace); - return fontFace; - } - else - { - return fontFaceIt->second; - } -} - -// Routine Description: -// - Updates the font used for drawing -// Arguments: -// - desired - Information specifying the font that is requested -// - actual - Filled with the nearest font actually chosen for drawing -// - dpi - The DPI of the screen -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxFontRenderData::UpdateFont(const FontInfoDesired& desired, FontInfo& actual, const int dpi, const std::unordered_map& features, const std::unordered_map& axes) noexcept -{ - try - { - _userLocaleName.clear(); - _textFormatMap.clear(); - _fontFaceMap.clear(); - _boxDrawingEffect.Reset(); - - // Initialize the default font info and build everything from here. - _defaultFontInfo = DxFontInfo( - _dwriteFactory.Get(), - desired.GetFaceName(), - static_cast(desired.GetWeight()), - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL); - - _SetFeatures(features); - _SetAxes(axes); - - _BuildFontRenderData(desired, actual, dpi); - } - CATCH_RETURN(); - - return S_OK; -} - -// Routine Description: -// - Calculates the box drawing scale/translate matrix values to fit a box glyph into the cell as perfectly as possible. -// Arguments: -// - format - Text format used to determine line spacing (height including ascent & descent) as calculated from the base font. -// - widthPixels - The pixel width of the available cell. -// - face - The font face that is currently being used, may differ from the base font from the layout. -// - fontScale - if the given font face is going to be scaled versus the format, we need to know so we can compensate for that. pass 1.0f for no scaling. -// - effect - Receives the effect to apply to box drawing characters. If no effect is received, special treatment isn't required. -// Return Value: -// - S_OK, GSL/WIL errors, DirectWrite errors, or math errors. -[[nodiscard]] HRESULT STDMETHODCALLTYPE DxFontRenderData::s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept -try -{ - // Check for bad in parameters. - RETURN_HR_IF(E_INVALIDARG, !format); - RETURN_HR_IF(E_INVALIDARG, !face); - - // Check the out parameter and fill it up with null. - RETURN_HR_IF(E_INVALIDARG, !effect); - *effect = nullptr; - - // The format is based around the main font that was specified by the user. - // We need to know its size as well as the final spacing that was calculated around - // it when it was first selected to get an idea of how large the bounding box is. - const auto fontSize = format->GetFontSize(); - - DWRITE_LINE_SPACING_METHOD spacingMethod; - float lineSpacing; // total height of the cells - float baseline; // vertical position counted down from the top where the characters "sit" - RETURN_IF_FAILED(format->GetLineSpacing(&spacingMethod, &lineSpacing, &baseline)); - - const auto ascentPixels = baseline; - const auto descentPixels = lineSpacing - baseline; - - // We need this for the designUnitsPerEm which will be required to move back and forth between - // Design Units and Pixels. I'll elaborate below. - DWRITE_FONT_METRICS1 fontMetrics; - face->GetMetrics(&fontMetrics); - - // If we had font fallback occur, the size of the font given to us (IDWriteFontFace1) can be different - // than the font size used for the original format (IDWriteTextFormat). - const auto scaledFontSize = fontScale * fontSize; - - // This is Unicode FULL BLOCK U+2588. - // We presume that FULL BLOCK should be filling its entire cell in all directions so it should provide a good basis - // in knowing exactly where to touch every single edge. - // We're also presuming that the other box/line drawing glyphs were authored in this font to perfectly inscribe - // inside of FULL BLOCK, with the same left/top/right/bottom bearings so they would look great when drawn adjacent. - const UINT32 blockCodepoint = L'\x2588'; - - // Get the index of the block out of the font. - UINT16 glyphIndex; - RETURN_IF_FAILED(face->GetGlyphIndicesW(&blockCodepoint, 1, &glyphIndex)); - - // If it was 0, it wasn't found in the font. We're going to try again with - // Unicode BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL U+253C which should be touching - // all the edges of the possible rectangle, much like a full block should. - if (glyphIndex == 0) - { - const UINT32 alternateCp = L'\x253C'; - RETURN_IF_FAILED(face->GetGlyphIndicesW(&alternateCp, 1, &glyphIndex)); - } - - // If we still didn't find the glyph index, we haven't implemented any further logic to figure out the box dimensions. - // So we're just going to leave successfully as is and apply no scaling factor. It might look not-right, but it won't - // stop the rendering pipeline. - RETURN_HR_IF(S_FALSE, glyphIndex == 0); - - // Get the metrics of the given glyph, which we're going to treat as the outline box in which all line/block drawing - // glyphs will be inscribed within, perfectly touching each edge as to align when two cells meet. - DWRITE_GLYPH_METRICS boxMetrics = { 0 }; - RETURN_IF_FAILED(face->GetDesignGlyphMetrics(&glyphIndex, 1, &boxMetrics)); - - // NOTE: All metrics we receive from DWRITE are going to be in "design units" which are a somewhat agnostic - // way of describing proportions. - // Converting back and forth between real pixels and design units is possible using - // any font's specific fontSize and the designUnitsPerEm FONT_METRIC value. - // - // Here's what to know about the boxMetrics: - // - // - // - // topLeft --> +--------------------------------+ --- - // | ^ | | - // | | topSide | | - // | | Bearing | | - // | v | | - // | +-----------------+ | | - // | | | | | - // | | | | | a - // | | | | | d - // | | | | | v - // +<---->+ | | | a - // | | | | | n - // | left | | | | c - // | Side | | | | e - // | Bea- | | | | H - // | ring | | right | | e - // vertical | | | Side | | i - // OriginY --> x | | Bea- | | g - // | | | ring | | h - // | | | | | t - // | | +<----->+ | - // | +-----------------+ | | - // | ^ | | - // | bottomSide | | | - // | Bearing | | | - // | v | | - // +--------------------------------+ --- - // - // - // | | - // +--------------------------------+ - // | advanceWidth | - // - // - // NOTE: The bearings can be negative, in which case it is specifying that the glyphs overhang the box - // as defined by the advanceHeight/width. - // See also: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_glyph_metrics - - // The scale is a multiplier and the translation is addition. So *1 and +0 will mean nothing happens. - const auto defaultBoxVerticalScaleFactor = 1.0f; - auto boxVerticalScaleFactor = defaultBoxVerticalScaleFactor; - const auto defaultBoxVerticalTranslation = 0.0f; - auto boxVerticalTranslation = defaultBoxVerticalTranslation; - { - // First, find the dimensions of the glyph representing our fully filled box. - - // Ascent is how far up from the baseline we'll draw. - // verticalOriginY is the measure from the topLeft corner of the bounding box down to where - // the glyph's version of the baseline is. - // topSideBearing is how much "gap space" is left between that topLeft and where the glyph - // starts drawing. Subtract the gap space to find how far is drawn upward from baseline. - const auto boxAscentDesignUnits = boxMetrics.verticalOriginY - boxMetrics.topSideBearing; - - // Descent is how far down from the baseline we'll draw. - // advanceHeight is the total height of the drawn bounding box. - // verticalOriginY is how much was given to the ascent, so subtract that out. - // What remains is then the descent value. Remove the - // bottomSideBearing as the "gap space" on the bottom to find how far is drawn downward from baseline. - const auto boxDescentDesignUnits = boxMetrics.advanceHeight - boxMetrics.verticalOriginY - boxMetrics.bottomSideBearing; - - // The height, then, of the entire box is just the sum of the ascent above the baseline and the descent below. - const auto boxHeightDesignUnits = boxAscentDesignUnits + boxDescentDesignUnits; - - // Second, find the dimensions of the cell we're going to attempt to fit within. - // We know about the exact ascent/descent units in pixels as calculated when we chose a font and - // adjusted the ascent/descent for a nice perfect baseline and integer total height. - // All we need to do is adapt it into Design Units so it meshes nicely with the Design Units above. - // Use the formula: Pixels * Design Units Per Em / Font Size = Design Units - const auto cellAscentDesignUnits = ascentPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellDescentDesignUnits = descentPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellHeightDesignUnits = cellAscentDesignUnits + cellDescentDesignUnits; - - // OK, now do a few checks. If the drawn box touches the top and bottom of the cell - // and the box is overall tall enough, then we'll not bother adjusting. - // We will presume the font author has set things as they wish them to be. - const auto boxTouchesCellTop = boxAscentDesignUnits >= cellAscentDesignUnits; - const auto boxTouchesCellBottom = boxDescentDesignUnits >= cellDescentDesignUnits; - const auto boxIsTallEnoughForCell = boxHeightDesignUnits >= cellHeightDesignUnits; - - // If not... - if (!(boxTouchesCellTop && boxTouchesCellBottom && boxIsTallEnoughForCell)) - { - // Find a scaling factor that will make the total height drawn of this box - // perfectly fit the same number of design units as the cell. - // Since scale factor is a multiplier, it doesn't matter that this is design units. - // The fraction between the two heights in pixels should be exactly the same - // (which is what will matter when we go to actually render it... the pixels that is.) - // Don't scale below 1.0. If it'd shrink, just center it at the prescribed scale. - boxVerticalScaleFactor = std::max(cellHeightDesignUnits / boxHeightDesignUnits, 1.0f); - - // The box as scaled might be hanging over the top or bottom of the cell (or both). - // We find out the amount of overhang/underhang on both the top and the bottom. - const auto extraAscent = boxAscentDesignUnits * boxVerticalScaleFactor - cellAscentDesignUnits; - const auto extraDescent = boxDescentDesignUnits * boxVerticalScaleFactor - cellDescentDesignUnits; - - // This took a bit of time and effort and it's difficult to put into words, but here goes. - // We want the average of the two magnitudes to find out how much to "take" from one and "give" - // to the other such that both are equal. We presume the glyphs are designed to be drawn - // centered in their box vertically to look good. - // The ordering around subtraction is required to ensure that the direction is correct with a negative - // translation moving up (taking excess descent and adding to ascent) and positive is the opposite. - const auto boxVerticalTranslationDesignUnits = (extraAscent - extraDescent) / 2; - - // The translation is just a raw movement of pixels up or down. Since we were working in Design Units, - // we need to run the opposite algorithm shown above to go from Design Units to Pixels. - boxVerticalTranslation = boxVerticalTranslationDesignUnits * scaledFontSize / fontMetrics.designUnitsPerEm; - } - } - - // The horizontal adjustments follow the exact same logic as the vertical ones. - const auto defaultBoxHorizontalScaleFactor = 1.0f; - auto boxHorizontalScaleFactor = defaultBoxHorizontalScaleFactor; - const auto defaultBoxHorizontalTranslation = 0.0f; - auto boxHorizontalTranslation = defaultBoxHorizontalTranslation; - { - // This is the only difference. We don't have a horizontalOriginX from the metrics. - // However, https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwrite_glyph_metrics says - // the X coordinate is specified by half the advanceWidth to the right of the horizontalOrigin. - // So we'll use that as the "center" and apply it the role that verticalOriginY had above. - - const auto boxCenterDesignUnits = boxMetrics.advanceWidth / 2; - const auto boxLeftDesignUnits = boxCenterDesignUnits - boxMetrics.leftSideBearing; - const auto boxRightDesignUnits = boxMetrics.advanceWidth - boxMetrics.rightSideBearing - boxCenterDesignUnits; - const auto boxWidthDesignUnits = boxLeftDesignUnits + boxRightDesignUnits; - - const auto cellWidthDesignUnits = widthPixels * fontMetrics.designUnitsPerEm / scaledFontSize; - const auto cellLeftDesignUnits = cellWidthDesignUnits / 2; - const auto cellRightDesignUnits = cellLeftDesignUnits; - - const auto boxTouchesCellLeft = boxLeftDesignUnits >= cellLeftDesignUnits; - const auto boxTouchesCellRight = boxRightDesignUnits >= cellRightDesignUnits; - const auto boxIsWideEnoughForCell = boxWidthDesignUnits >= cellWidthDesignUnits; - - if (!(boxTouchesCellLeft && boxTouchesCellRight && boxIsWideEnoughForCell)) - { - boxHorizontalScaleFactor = std::max(cellWidthDesignUnits / boxWidthDesignUnits, 1.0f); - const auto extraLeft = boxLeftDesignUnits * boxHorizontalScaleFactor - cellLeftDesignUnits; - const auto extraRight = boxRightDesignUnits * boxHorizontalScaleFactor - cellRightDesignUnits; - - const auto boxHorizontalTranslationDesignUnits = (extraLeft - extraRight) / 2; - - boxHorizontalTranslation = boxHorizontalTranslationDesignUnits * scaledFontSize / fontMetrics.designUnitsPerEm; - } - } - - // If we set anything, make a drawing effect. Otherwise, there isn't one. - if (defaultBoxVerticalScaleFactor != boxVerticalScaleFactor || - defaultBoxVerticalTranslation != boxVerticalTranslation || - defaultBoxHorizontalScaleFactor != boxHorizontalScaleFactor || - defaultBoxHorizontalTranslation != boxHorizontalTranslation) - { - // OK, make the object that will represent our effect, stuff the metrics into it, and return it. - RETURN_IF_FAILED(WRL::MakeAndInitialize(effect, boxVerticalScaleFactor, boxVerticalTranslation, boxHorizontalScaleFactor, boxHorizontalTranslation)); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Returns whether the user set or updated any of the font features to be applied -bool DxFontRenderData::DidUserSetFeatures() const noexcept -{ - return _didUserSetFeatures; -} - -// Routine Description: -// - Returns whether the user set or updated any of the font axes to be applied -bool DxFontRenderData::DidUserSetAxes() const noexcept -{ - return _didUserSetAxes; -} - -// Routine Description: -// - Function called to inform us whether to use the user set weight -// in the font axes -// - Called by CustomTextLayout, when the text attribute is intense we should -// ignore the user set weight, otherwise setting the bold font axis -// breaks the bold font attribute -// Arguments: -// - inhibitUserWeight: boolean that tells us if we should use the user set weight -// in the font axes -void DxFontRenderData::InhibitUserWeight(bool inhibitUserWeight) noexcept -{ - _inhibitUserWeight = inhibitUserWeight; -} - -// Routine Description: -// - Returns whether the set italic in the font axes -// Return Value: -// - True if the user set the italic axis to 1, -// false if the italic axis is not present or the italic axis is set to 0 -bool DxFontRenderData::DidUserSetItalic() const noexcept -{ - return _didUserSetItalic; -} - -// Routine Description: -// - Updates our internal map of font features with the given features -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the feature changes -// to take place -// Arguments: -// - features - the features to update our map with -void DxFontRenderData::_SetFeatures(const std::unordered_map& features) -{ - // Populate the feature map with the standard list first - std::unordered_map featureMap{ - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'a', 'l', 't'), 1 }, // Contextual Alternates - { DWRITE_MAKE_FONT_FEATURE_TAG('l', 'i', 'g', 'a'), 1 }, // Standard Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'l', 'i', 'g'), 1 }, // Contextual Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('k', 'e', 'r', 'n'), 1 } // Kerning - }; - - // Update our feature map with the provided features - if (!features.empty()) - { - for (const auto& [tag, param] : features) - { - if (tag.length() == TAG_LENGTH) - { - featureMap.insert_or_assign(DWRITE_MAKE_FONT_FEATURE_TAG(til::at(tag, 0), til::at(tag, 1), til::at(tag, 2), til::at(tag, 3)), param); - } - } - _didUserSetFeatures = true; - } - else - { - _didUserSetFeatures = false; - } - - // Convert the data to DWRITE_FONT_FEATURE and store it in a vector for CustomTextLayout - _featureVector.clear(); - for (const auto [tag, param] : featureMap) - { - _featureVector.push_back(DWRITE_FONT_FEATURE{ tag, param }); - } -} - -// Routine Description: -// - Updates our internal map of font axes with the given axes -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the axes changes -// to take place -// Arguments: -// - axes - the axes to update our map with -void DxFontRenderData::_SetAxes(const std::unordered_map& axes) -{ - // Clear out the old vector and booleans in case this is a hot reload - _axesVector = std::vector{}; - _didUserSetAxes = false; - _didUserSetItalic = false; - - // Update our axis map with the provided axes - if (!axes.empty()) - { - // Store the weight aside: we will be creating a span of all the axes in the vector except the weight, - // and then we will add the weight to the vector - // We are doing this so that when the text attribute is intense, we can apply all the axes except the weight - std::optional weightAxis; - - // Since we are calling an 'emplace_back' after creating the span, - // there is a chance a reallocation happens (if the vector needs to grow), which would make the span point to - // deallocated memory. To avoid this, make sure to reserve enough memory in the vector. - _axesVector.reserve(axes.size()); - -#pragma warning(suppress : 26445) // the analyzer doesn't like reference to string_view - for (const auto& [axis, value] : axes) - { - if (axis.length() == TAG_LENGTH) - { - const auto dwriteFontAxis = DWRITE_FONT_AXIS_VALUE{ DWRITE_MAKE_FONT_AXIS_TAG(til::at(axis, 0), til::at(axis, 1), til::at(axis, 2), til::at(axis, 3)), value }; - if (dwriteFontAxis.axisTag != DWRITE_FONT_AXIS_TAG_WEIGHT) - { - _axesVector.emplace_back(dwriteFontAxis); - } - else - { - weightAxis = dwriteFontAxis; - } - _didUserSetItalic |= dwriteFontAxis.axisTag == DWRITE_FONT_AXIS_TAG_ITALIC && value == 1; - } - } - - // Make the span, which has all the axes except the weight - _axesVectorWithoutWeight = std::span{ _axesVector }; - - // Add the weight axis to the vector if needed - if (weightAxis) - { - _axesVector.emplace_back(weightAxis.value()); - } - _didUserSetAxes = true; - } -} - -// Method Description: -// - Converts a DWRITE_FONT_STRETCH enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStretch -float DxFontRenderData::_FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept -{ - // 10 elements from DWRITE_FONT_STRETCH_UNDEFINED (0) to DWRITE_FONT_STRETCH_ULTRA_EXPANDED (9) - static constexpr auto fontStretchEnumToVal = std::array{ 100.0f, 50.0f, 62.5f, 75.0f, 87.5f, 100.0f, 112.5f, 125.0f, 150.0f, 200.0f }; - - if (gsl::narrow_cast(fontStretch) > fontStretchEnumToVal.size()) - { - fontStretch = DWRITE_FONT_STRETCH_NORMAL; - } - - return til::at(fontStretchEnumToVal, fontStretch); -} - -// Method Description: -// - Converts a DWRITE_FONT_STYLE enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStyle -float DxFontRenderData::_FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept -{ - // DWRITE_FONT_STYLE_NORMAL (0), DWRITE_FONT_STYLE_OBLIQUE (1), DWRITE_FONT_STYLE_ITALIC (2) - static constexpr auto fontStyleEnumToVal = std::array{ 0.0f, -20.0f, -12.0f }; - - // Both DWRITE_FONT_STYLE_OBLIQUE and DWRITE_FONT_STYLE_ITALIC default to having slant. - // Though an italic font technically need not have slant (there exist upright ones), the - // vast majority of italic fonts are also slanted. Ideally the slant comes from the - // 'slnt' value in the STAT or fvar table, or the post table italic angle. - - if (gsl::narrow_cast(fontStyle) > fontStyleEnumToVal.size()) - { - fontStyle = DWRITE_FONT_STYLE_NORMAL; - } - - return til::at(fontStyleEnumToVal, fontStyle); -} - -// Method Description: -// - Fill any missing axis values that might be known but were unspecified, such as omitting -// the 'wght' axis tag but specifying the old DWRITE_FONT_WEIGHT enum -// - This function will only be called with a valid IDWriteTextFormat3 -// (on platforms where IDWriteTextFormat3 is supported) -// Arguments: -// - fontWeight: the old DWRITE_FONT_WEIGHT enum to be converted into an axis value -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// - fontSize: the number to convert into an axis value -// - format: the IDWriteTextFormat3 to get the defined axes from -// Return value: -// - The fully formed axes vector -#pragma warning(suppress : 26429) // the analyzer doesn't detect that our FAIL_FAST_IF_NULL macro \ - // checks format for nullness -std::vector DxFontRenderData::GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format) -{ - FAIL_FAST_IF_NULL(format); - - const auto axesCount = format->GetFontAxisValueCount(); - std::vector axesVector; - axesVector.resize(axesCount); - format->GetFontAxisValues(axesVector.data(), axesCount); - - auto axisTagPresence = AxisTagPresence::None; - for (const auto& fontAxisValue : axesVector) - { - switch (fontAxisValue.axisTag) - { - case DWRITE_FONT_AXIS_TAG_WEIGHT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Weight); - break; - case DWRITE_FONT_AXIS_TAG_WIDTH: - WI_SetFlag(axisTagPresence, AxisTagPresence::Width); - break; - case DWRITE_FONT_AXIS_TAG_ITALIC: - WI_SetFlag(axisTagPresence, AxisTagPresence::Italic); - break; - case DWRITE_FONT_AXIS_TAG_SLANT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Slant); - break; - } - } - - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Weight)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, gsl::narrow(fontWeight) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Width)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WIDTH, _FontStretchToWidthAxisValue(fontStretch) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Italic)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, (fontStyle == DWRITE_FONT_STYLE_ITALIC ? 1.0f : 0.0f) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Slant)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, _FontStyleToSlantFixedAxisValue(fontStyle) }); - } - - return axesVector; -} - -// Routine Description: -// - Build the needed data for rendering according to the font used -// Arguments: -// - desired - Information specifying the font that is requested -// - actual - Filled with the nearest font actually chosen for drawing -// - dpi - The DPI of the screen -// Return Value: -// - None -void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi) -{ - const auto dpiF = static_cast(dpi); - auto fontLocaleName = UserLocaleName(); - // This is the first attempt to resolve font face after `UpdateFont`. - // Note that the following line may cause property changes _inside_ `_defaultFontInfo` because the desired font may not exist. - // See the implementation of `ResolveFontFaceWithFallback` for details. - const auto face = _defaultFontInfo.ResolveFontFaceWithFallback(fontLocaleName); - - DWRITE_FONT_METRICS1 fontMetrics; - face->GetMetrics(&fontMetrics); - - const UINT32 spaceCodePoint = L'M'; - UINT16 spaceGlyphIndex; - THROW_IF_FAILED(face->GetGlyphIndicesW(&spaceCodePoint, 1, &spaceGlyphIndex)); - - INT32 advanceInDesignUnits; - THROW_IF_FAILED(face->GetDesignGlyphAdvances(1, &spaceGlyphIndex, &advanceInDesignUnits)); - - DWRITE_GLYPH_METRICS spaceMetrics = { 0 }; - THROW_IF_FAILED(face->GetDesignGlyphMetrics(&spaceGlyphIndex, 1, &spaceMetrics)); - - // The math here is actually: - // Requested Size in Points * DPI scaling factor * Points to Pixels scaling factor. - // - DPI = dots per inch - // - PPI = points per inch or "points" as usually seen when choosing a font size - // - The DPI scaling factor is the current monitor DPI divided by 96, the default DPI. - // - The Points to Pixels factor is based on the typography definition of 72 points per inch. - // As such, converting requires taking the 96 pixel per inch default and dividing by the 72 points per inch - // to get a factor of 1 and 1/3. - // This turns into something like: - // - 12 ppi font * (96 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 16 pixels tall font for 100% display (96 dpi is 100%) - // - 12 ppi font * (144 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 24 pixels tall font for 150% display (144 dpi is 150%) - // - 12 ppi font * (192 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 32 pixels tall font for 200% display (192 dpi is 200%) - const auto heightDesired = desired.GetEngineSize().height / POINTS_PER_INCH * dpiF; - - // The advance is the number of pixels left-to-right (X dimension) for the given font. - // We're finding a proportional factor here with the design units in "ems", not an actual pixel measurement. - const auto widthAdvance = static_cast(advanceInDesignUnits) / fontMetrics.designUnitsPerEm; - - // Use the real pixel height desired by the "em" factor for the width to get the number of pixels - // we will need per character in width. This will almost certainly result in fractional X-dimension pixels. - const auto widthAdvanceInPx = heightDesired * widthAdvance; - - // Now reverse the "em" factor from above to turn the exact pixel width into a (probably) fractional - // height in pixels of each character. It's easier for us to pad out height and align vertically - // than it is horizontally. - const auto fontSize = roundf(widthAdvanceInPx) / widthAdvance; - _fontSize = fontSize; - - // Now figure out the basic properties of the character height which include ascent and descent - // for this specific font size. - const auto ascent = (fontSize * fontMetrics.ascent) / fontMetrics.designUnitsPerEm; - const auto descent = (fontSize * fontMetrics.descent) / fontMetrics.designUnitsPerEm; - - // Get the gap. - const auto gap = (fontSize * fontMetrics.lineGap) / fontMetrics.designUnitsPerEm; - const auto halfGap = gap / 2; - - // We're going to build a line spacing object here to track all of this data in our format. - DWRITE_LINE_SPACING lineSpacing = {}; - lineSpacing.method = DWRITE_LINE_SPACING_METHOD_UNIFORM; - - // We need to make sure the baseline falls on a round pixel (not a fractional pixel). - // If the baseline is fractional, the text appears blurry, especially at small scales. - // Since we also need to make sure the bounding box as a whole is round pixels - // (because the entire console system maths in full cell units), - // we're just going to ceiling up the ascent and descent to make a full pixel amount - // and set the baseline to the full round pixel ascent value. - // - // For reference, for the letters "ag": - // ... - // gggggg bottom of previous line - // - // ----------------- <===========================================| - // | topSideBearing | 1/2 lineGap | - // aaaaaa ggggggg <-------------------------|-------------| | - // a g g | | | - // aaaaa ggggg |<-ascent | | - // a a g | | |---- lineHeight - // aaaaa a gggggg <----baseline, verticalOriginY----------|---| - // g g |<-descent | | - // gggggg <-------------------------|-------------| | - // | bottomSideBearing | 1/2 lineGap | - // ----------------- <===========================================| - // - // aaaaaa ggggggg top of next line - // ... - // - // Also note... - // We're going to add half the line gap to the ascent and half the line gap to the descent - // to ensure that the spacing is balanced vertically. - // Generally speaking, the line gap is added to the ascent by DirectWrite itself for - // horizontally drawn text which can place the baseline and glyphs "lower" in the drawing - // box than would be desired for proper alignment of things like line and box characters - // which will try to sit centered in the area and touch perfectly with their neighbors. - - const auto fullPixelAscent = ceil(ascent + halfGap); - const auto fullPixelDescent = ceil(descent + halfGap); - const auto defaultHeight = fullPixelAscent + fullPixelDescent; - const auto lineHeight = desired.GetCellHeight().Resolve(defaultHeight, dpiF, heightDesired, widthAdvanceInPx); - const auto baseline = fullPixelAscent + (lineHeight - defaultHeight) / 2.0f; - - lineSpacing.height = roundf(lineHeight); - lineSpacing.baseline = roundf(baseline); - - // According to MSDN (https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ne-dwrite_3-dwrite_font_line_gap_usage) - // Setting "ENABLED" means we've included the line gapping in the spacing numbers given. - lineSpacing.fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED; - - _lineSpacing = lineSpacing; - - const auto widthApprox = desired.GetCellWidth().Resolve(widthAdvanceInPx, dpiF, heightDesired, widthAdvanceInPx); - const auto widthExact = roundf(widthApprox); - - // The scaled size needs to represent the pixel box that each character will fit within for the purposes - // of hit testing math and other such multiplication/division. - til::size coordSize; - coordSize.width = static_cast(widthExact); - coordSize.height = static_cast(lineSpacing.height); - - // Unscaled is for the purposes of re-communicating this font back to the renderer again later. - // As such, we need to give the same original size parameter back here without padding - // or rounding or scaling manipulation. - const auto unscaled = desired.GetEngineSize(); - - const auto scaled = coordSize; - - actual.SetFromEngine(_defaultFontInfo.GetFamilyName(), - desired.GetFamily(), - DefaultTextFormat()->GetFontWeight(), - false, - scaled, - unscaled); - - actual.SetFallback(_defaultFontInfo.GetFallback()); - - LineMetrics lineMetrics; - // There is no font metric for the grid line width, so we use a small - // multiple of the font size, which typically rounds to a pixel. - lineMetrics.gridlineWidth = std::round(fontSize * 0.025f); - - // All other line metrics are in design units, so to get a pixel value, - // we scale by the font size divided by the design-units-per-em. - const auto scale = fontSize / fontMetrics.designUnitsPerEm; - lineMetrics.underlineOffset = std::round(fontMetrics.underlinePosition * scale); - lineMetrics.underlineWidth = std::round(fontMetrics.underlineThickness * scale); - lineMetrics.strikethroughOffset = std::round(fontMetrics.strikethroughPosition * scale); - lineMetrics.strikethroughWidth = std::round(fontMetrics.strikethroughThickness * scale); - - // We always want the lines to be visible, so if a stroke width ends up - // at zero after rounding, we need to make it at least 1 pixel. - lineMetrics.gridlineWidth = std::max(lineMetrics.gridlineWidth, 1.0f); - lineMetrics.underlineWidth = std::max(lineMetrics.underlineWidth, 1.0f); - lineMetrics.strikethroughWidth = std::max(lineMetrics.strikethroughWidth, 1.0f); - - // Offsets are relative to the base line of the font, so we subtract - // from the ascent to get an offset relative to the top of the cell. - lineMetrics.underlineOffset = lineSpacing.baseline - lineMetrics.underlineOffset; - lineMetrics.strikethroughOffset = lineSpacing.baseline - lineMetrics.strikethroughOffset; - - // For double underlines we need a second offset, just below the first, - // but with a bit of a gap (about double the grid line width). - lineMetrics.underlineOffset2 = lineMetrics.underlineOffset + - lineMetrics.underlineWidth + - std::round(fontSize * 0.05f); - - // However, we don't want the underline to extend past the bottom of the - // cell, so we clamp the offset to fit just inside. - const auto maxUnderlineOffset = lineSpacing.height - lineMetrics.underlineWidth; - lineMetrics.underlineOffset2 = std::min(lineMetrics.underlineOffset2, maxUnderlineOffset); - - // But if the resulting gap isn't big enough even to register as a thicker - // line, it's better to place the second line slightly above the first. - if (lineMetrics.underlineOffset2 < lineMetrics.underlineOffset + lineMetrics.gridlineWidth) - { - lineMetrics.underlineOffset2 = lineMetrics.underlineOffset - lineMetrics.gridlineWidth; - } - - // We also add half the stroke width to the offsets, since the line - // coordinates designate the center of the line. - lineMetrics.underlineOffset += lineMetrics.underlineWidth / 2.0f; - lineMetrics.underlineOffset2 += lineMetrics.underlineWidth / 2.0f; - lineMetrics.strikethroughOffset += lineMetrics.strikethroughWidth / 2.0f; - - _lineMetrics = lineMetrics; - - _glyphCell = actual.GetSize(); -} - -Microsoft::WRL::ComPtr DxFontRenderData::_BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName) -{ - Microsoft::WRL::ComPtr format; - THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontInfo.GetFamilyName().data(), - fontInfo.GetFontCollection(), - fontInfo.GetWeight(), - fontInfo.GetStyle(), - fontInfo.GetStretch(), - _fontSize, - localeName.data(), - &format)); - - // If the OS supports IDWriteTextFormat3, set the font axes - ::Microsoft::WRL::ComPtr format3; - if (!FAILED(format->QueryInterface(IID_PPV_ARGS(&format3)))) - { - if (_inhibitUserWeight && !_axesVectorWithoutWeight.empty()) - { - format3->SetFontAxisValues(_axesVectorWithoutWeight.data(), gsl::narrow(_axesVectorWithoutWeight.size())); - } - else if (!_inhibitUserWeight && !_axesVector.empty()) - { - format3->SetFontAxisValues(_axesVector.data(), gsl::narrow(_axesVector.size())); - } - } - - return format; -} diff --git a/src/renderer/dx/DxFontRenderData.h b/src/renderer/dx/DxFontRenderData.h deleted file mode 100644 index e12c6da91bb..00000000000 --- a/src/renderer/dx/DxFontRenderData.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../../renderer/inc/FontInfoDesired.hpp" -#include "DxFontInfo.h" -#include "BoxDrawingEffect.h" - -#include -#include -#include -#include - -#include - -namespace Microsoft::Console::Render -{ - enum class AxisTagPresence : BYTE - { - None = 0x00, - Weight = 0x01, - Width = 0x02, - Italic = 0x04, - Slant = 0x08, - }; - DEFINE_ENUM_FLAG_OPERATORS(AxisTagPresence); - - class DxFontRenderData - { - public: - struct LineMetrics - { - float gridlineWidth; - float underlineOffset; - float underlineOffset2; - float underlineWidth; - float strikethroughOffset; - float strikethroughWidth; - }; - - DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory); - - // DirectWrite text analyzer from the factory - [[nodiscard]] Microsoft::WRL::ComPtr Analyzer(); - - [[nodiscard]] Microsoft::WRL::ComPtr SystemFontFallback(); - - // A locale that can be used on construction of assorted DX objects that want to know one. - [[nodiscard]] std::wstring UserLocaleName(); - - [[nodiscard]] til::size GlyphCell() noexcept; - [[nodiscard]] LineMetrics GetLineMetrics() noexcept; - - // The weight of default font - [[nodiscard]] DWRITE_FONT_WEIGHT DefaultFontWeight() noexcept; - - // The style of default font - [[nodiscard]] DWRITE_FONT_STYLE DefaultFontStyle() noexcept; - - // The stretch of default font - [[nodiscard]] DWRITE_FONT_STRETCH DefaultFontStretch() noexcept; - - // The font features of the default font - [[nodiscard]] const std::vector& DefaultFontFeatures() const noexcept; - - // The DirectWrite format object representing the size and other text properties to be applied (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultTextFormat(); - - // The DirectWrite font face to use while calculating layout (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultFontFace(); - - // Box drawing scaling effects that are cached for the base font across layouts - [[nodiscard]] Microsoft::WRL::ComPtr DefaultBoxDrawingEffect(); - - // The attributed variants of the format object representing the size and other text properties - [[nodiscard]] Microsoft::WRL::ComPtr TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - // The attributed variants of the font face to use while calculating layout - [[nodiscard]] Microsoft::WRL::ComPtr FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& desired, FontInfo& fiFontInfo, const int dpi, const std::unordered_map& features = {}, const std::unordered_map& axes = {}) noexcept; - - [[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept; - - bool DidUserSetFeatures() const noexcept; - bool DidUserSetAxes() const noexcept; - void InhibitUserWeight(bool inhibitUserWeight) noexcept; - bool DidUserSetItalic() const noexcept; - - std::vector GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format); - - private: - using FontAttributeMapKey = uint32_t; - - bool _inhibitUserWeight{ false }; - bool _didUserSetItalic{ false }; - bool _didUserSetFeatures{ false }; - bool _didUserSetAxes{ false }; - // The font features to apply to the text - std::vector _featureVector; - - // The font axes to apply to the text - std::vector _axesVector; - std::span _axesVectorWithoutWeight; - - // We use this to identify font variants with different attributes. - static FontAttributeMapKey _ToMapKey(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch) noexcept - { - return (weight << 16) | (style << 8) | stretch; - }; - - void _SetFeatures(const std::unordered_map& features); - void _SetAxes(const std::unordered_map& axes); - float _FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept; - float _FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept; - void _BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi); - Microsoft::WRL::ComPtr _BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName); - - std::unordered_map> _textFormatMap; - std::unordered_map> _fontFaceMap; - - ::Microsoft::WRL::ComPtr _boxDrawingEffect; - ::Microsoft::WRL::ComPtr _systemFontFallback; - ::Microsoft::WRL::ComPtr _dwriteFactory; - ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; - - std::wstring _userLocaleName; - DxFontInfo _defaultFontInfo; - til::size _glyphCell; - DWRITE_LINE_SPACING _lineSpacing; - LineMetrics _lineMetrics; - float _fontSize; - }; -} diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp deleted file mode 100644 index e03cfb20081..00000000000 --- a/src/renderer/dx/DxRenderer.cpp +++ /dev/null @@ -1,2396 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "DxRenderer.hpp" -#include "CustomTextLayout.h" - -#include "../../interactivity/win32/CustomWindowMessages.h" -#include "../../types/inc/Viewport.hpp" -#include "../../inc/unicode.hpp" -#include "../../inc/DefaultSettings.h" -#include - -#include "ScreenPixelShader.h" -#include "ScreenVertexShader.h" -#include -#include -#include - -using namespace DirectX; - -std::atomic Microsoft::Console::Render::DxEngine::_tracelogCount{ 0 }; -#pragma warning(suppress : 26477) // We don't control tracelogging macros -TRACELOGGING_DEFINE_PROVIDER(g_hDxRenderProvider, - "Microsoft.Windows.Terminal.Renderer.DirectX", - // {c93e739e-ae50-5a14-78e7-f171e947535d} - (0xc93e739e, 0xae50, 0x5a14, 0x78, 0xe7, 0xf1, 0x71, 0xe9, 0x47, 0x53, 0x5d), ); - -// Quad where we draw the terminal. -// pos is world space coordinates where origin is at the center of screen. -// tex is texel coordinates where origin is top left. -// Layout the quad as a triangle strip where the _screenQuadVertices are place like so. -// 2 0 -// 3 1 -struct ShaderInput -{ - XMFLOAT3 pos; - XMFLOAT2 tex; -} const _screenQuadVertices[] = { - { XMFLOAT3(1.f, 1.f, 0.f), XMFLOAT2(1.f, 0.f) }, - { XMFLOAT3(1.f, -1.f, 0.f), XMFLOAT2(1.f, 1.f) }, - { XMFLOAT3(-1.f, 1.f, 0.f), XMFLOAT2(0.f, 0.f) }, - { XMFLOAT3(-1.f, -1.f, 0.f), XMFLOAT2(0.f, 1.f) }, -}; - -D3D11_INPUT_ELEMENT_DESC _shaderInputLayout[] = { - { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 } -}; - -namespace -{ - bool operator==(const D2D1::Matrix3x2F& lhs, const D2D1::Matrix3x2F& rhs) noexcept - { - return ::memcmp(&lhs.m[0][0], &rhs.m[0][0], sizeof(lhs.m)) == 0; - }; -} - -#pragma hdrstop - -using namespace Microsoft::Console::Render; -using namespace Microsoft::Console::Types; - -// Routine Description: -// - Constructs a DirectX-based renderer for console text -// which primarily uses DirectWrite on a Direct2D surface -#pragma warning(suppress : 26455) -// TODO GH 2683: The default constructor should not throw. -DxEngine::DxEngine() : - RenderEngineBase(), - _pool{ til::pmr::get_default_resource() }, - _invalidMap{ &_pool }, - _invalidScroll{}, - _allInvalid{ false }, - _firstFrame{ true }, - _presentParams{ 0 }, - _presentReady{ false }, - _presentScroll{ 0 }, - _presentDirty{ 0 }, - _presentOffset{ 0 }, - _isEnabled{ false }, - _isPainting{ false }, - _displaySizePixels{}, - _foregroundColor{ 0 }, - _backgroundColor{ 0 }, - _selectionBackground{}, - _currentLineRendition{ LineRendition::SingleWidth }, - _currentLineTransform{ D2D1::Matrix3x2F::Identity() }, - _haveDeviceResources{ false }, - _swapChainHandle{ INVALID_HANDLE_VALUE }, - _swapChainDesc{ 0 }, - _swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE }, - _recreateDeviceRequested{ false }, - _terminalEffectsEnabled{ false }, - _retroTerminalEffect{ false }, - _pixelShaderPath{}, - _forceFullRepaintRendering{ false }, - _softwareRendering{ false }, - _antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE }, - _defaultBackgroundIsTransparent{ true }, - _hwndTarget{ static_cast(INVALID_HANDLE_VALUE) }, - _sizeTarget{}, - _dpi{ USER_DEFAULT_SCREEN_DPI }, - _scale{ 1.0f }, - _prevScale{ 1.0f }, - _chainMode{ SwapChainMode::ForComposition }, - _customLayout{}, - _customRenderer{ ::Microsoft::WRL::Make() }, - _drawingContext{} -{ - const auto was = _tracelogCount.fetch_add(1); - if (0 == was) - { - TraceLoggingRegister(g_hDxRenderProvider); - } - - THROW_IF_FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&_d2dFactory))); - - THROW_IF_FAILED(DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(_dwriteFactory), - reinterpret_cast(_dwriteFactory.GetAddressOf()))); - - // Initialize our default selection color to DEFAULT_FOREGROUND, but make - // sure to set to to a D2D1::ColorF - SetSelectionBackground(DEFAULT_FOREGROUND); - - _fontRenderData = std::make_unique(_dwriteFactory); -} - -// Routine Description: -// - Destroys an instance of the DirectX rendering engine -DxEngine::~DxEngine() -{ - _ReleaseDeviceResources(); - - const auto was = _tracelogCount.fetch_sub(1); - if (1 == was) - { - TraceLoggingUnregister(g_hDxRenderProvider); - } -} - -// Routine Description: -// - Sets this engine to enabled allowing painting and presentation to occur -// Arguments: -// - -// Return Value: -// - Generally S_OK, but might return a DirectX or memory error if -// resources need to be created or adjusted when enabling to prepare for draw -// Can give invalid state if you enable an enabled class. -[[nodiscard]] HRESULT DxEngine::Enable() noexcept -{ - return _EnableDisplayAccess(true); -} - -// Routine Description: -// - Sets this engine to disabled to prevent painting and presentation from occurring -// Arguments: -// - -// Return Value: -// - Should be OK. We might close/free resources, but that shouldn't error. -// Can give invalid state if you disable a disabled class. -[[nodiscard]] HRESULT DxEngine::Disable() noexcept -{ - return _EnableDisplayAccess(false); -} - -// Routine Description: -// - Helper to enable/disable painting/display access/presentation in a unified -// manner between enable/disable functions. -// Arguments: -// - outputEnabled - true to enable, false to disable -// Return Value: -// - Generally OK. Can return invalid state if you set to the state that is already -// active (enabling enabled, disabling disabled). -[[nodiscard]] HRESULT DxEngine::_EnableDisplayAccess(const bool outputEnabled) noexcept -{ - // Invalid state if we're setting it to the same as what we already have. - RETURN_HR_IF(E_NOT_VALID_STATE, outputEnabled == _isEnabled); - - _isEnabled = outputEnabled; - if (!_isEnabled) - { - _ReleaseDeviceResources(); - } - - return S_OK; -} - -// Routine Description: -// - Compiles a shader source into binary blob. -// Arguments: -// - source - Shader source -// - target - What kind of shader this is -// - entry - Entry function of shader -// Return Value: -// - Compiled binary. Errors are thrown and logged. -static Microsoft::WRL::ComPtr _CompileShader(const std::string_view& source, const char* target) -{ -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED - THROW_HR(E_UNEXPECTED); - return 0; -#else - Microsoft::WRL::ComPtr code{}; - Microsoft::WRL::ComPtr error{}; - - const auto hr = D3DCompile( - source.data(), - source.size(), - nullptr, - nullptr, - nullptr, - "main", - target, - D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_OPTIMIZATION_LEVEL3, - 0, - &code, - &error); - - if (FAILED(hr)) - { - LOG_HR_MSG(hr, "D3DCompile failed with %08x", hr); - if (error) - { - LOG_HR_MSG(hr, "D3DCompile error\n%S", static_cast(error->GetBufferPointer())); - } - - THROW_HR(hr); - } - - return code; -#endif -} - -// Routine Description: -// - Checks if terminal effects are enabled. -// Arguments: -// Return Value: -// - True if terminal effects are enabled -bool DxEngine::_HasTerminalEffects() const noexcept -{ - return _terminalEffectsEnabled && (_retroTerminalEffect || !_pixelShaderPath.empty()); -} - -// Routine Description: -// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath -// Arguments: -// Return Value: -// - Pixel shader source code -std::string DxEngine::_LoadPixelShaderFile() const -{ - // If the user specified the new pixel shader, it has precedence - if (!_pixelShaderPath.empty()) - { - try - { - wil::unique_hfile hFile{ CreateFileW(_pixelShaderPath.c_str(), - GENERIC_READ, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr) }; - - THROW_LAST_ERROR_IF(!hFile); // This will be caught below. - - // fileSize is in bytes - const auto fileSize = GetFileSize(hFile.get(), nullptr); - THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE); - - std::vector utf8buffer; - utf8buffer.reserve(fileSize); - - DWORD bytesRead = 0; - THROW_LAST_ERROR_IF(!ReadFile(hFile.get(), utf8buffer.data(), fileSize, &bytesRead, nullptr)); - - // convert buffer to UTF-8 string - std::string utf8string(utf8buffer.data(), fileSize); - - return utf8string; - } - catch (...) - { - // If we ran into any problems during loading pixel shader, call to - // the warning callback to surface the file not found error - const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); - if (_pfnWarningCallback) - { - _pfnWarningCallback(exceptionHr); - } - - return std::string{}; - } - } - else if (_retroTerminalEffect) - { - return std::string{ retroPixelShaderString }; - } - - return std::string{}; -} - -// Routine Description: -// - Setup D3D objects for doing shader things for terminal effects. -// Arguments: -// Return Value: -// - HRESULT status. -HRESULT DxEngine::_SetupTerminalEffects() -{ - _pixelShaderLoaded = false; - - const auto pixelShaderSource = _LoadPixelShaderFile(); - if (pixelShaderSource.empty()) - { - // There's no shader to compile. This might be due to failing to load, - // or because there's just no shader enabled at all. - // Turn the effects off for now. - _terminalEffectsEnabled = false; - - return S_FALSE; - } - - ::Microsoft::WRL::ComPtr swapBuffer; - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&swapBuffer)); - - // Setup render target. - RETURN_IF_FAILED(_d3dDevice->CreateRenderTargetView(swapBuffer.Get(), nullptr, &_renderTargetView)); - - // Setup _framebufferCapture, to where we'll copy current frame when rendering effects. - D3D11_TEXTURE2D_DESC framebufferCaptureDesc{}; - swapBuffer->GetDesc(&framebufferCaptureDesc); - WI_SetFlag(framebufferCaptureDesc.BindFlags, D3D11_BIND_SHADER_RESOURCE); - RETURN_IF_FAILED(_d3dDevice->CreateTexture2D(&framebufferCaptureDesc, nullptr, &_framebufferCapture)); - - // Setup the viewport. - D3D11_VIEWPORT vp; - vp.Width = static_cast(_displaySizePixels.width); - vp.Height = static_cast(_displaySizePixels.height); - vp.MinDepth = 0.0f; - vp.MaxDepth = 1.0f; - vp.TopLeftX = 0; - vp.TopLeftY = 0; - _d3dDeviceContext->RSSetViewports(1, &vp); - - const char* shaderTargetVS = nullptr; - const char* shaderTargetPS = nullptr; - switch (_d3dDevice->GetFeatureLevel()) - { - case D3D_FEATURE_LEVEL_10_0: - shaderTargetVS = "vs_4_0"; - shaderTargetPS = "ps_4_0"; - break; - case D3D_FEATURE_LEVEL_10_1: - shaderTargetVS = "vs_4_1"; - shaderTargetPS = "ps_4_1"; - break; - default: - shaderTargetVS = "vs_5_0"; - shaderTargetPS = "ps_5_0"; - break; - } - - // Prepare shaders. - auto vertexBlob = _CompileShader(&screenVertexShaderString[0], shaderTargetVS); - Microsoft::WRL::ComPtr pixelBlob; - // As the pixel shader source is user provided it's possible there's a problem with it - // so load it inside a try catch, on any error log and fallback on the error pixel shader - // If even the error pixel shader fails to load rely on standard exception handling - try - { - pixelBlob = _CompileShader(pixelShaderSource, shaderTargetPS); - } - catch (...) - { - // Call to the warning callback to surface the shader compile error - const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); - if (_pfnWarningCallback) - { - // If this fails, it'll return E_FAIL, which is terribly - // uninformative. Instead, raise something more useful. - _pfnWarningCallback(D2DERR_SHADER_COMPILE_FAILED); - } - return exceptionHr; - } - - RETURN_IF_FAILED(_d3dDevice->CreateVertexShader( - vertexBlob->GetBufferPointer(), - vertexBlob->GetBufferSize(), - nullptr, - &_vertexShader)); - - RETURN_IF_FAILED(_d3dDevice->CreatePixelShader( - pixelBlob->GetBufferPointer(), - pixelBlob->GetBufferSize(), - nullptr, - &_pixelShader)); - - RETURN_IF_FAILED(_d3dDevice->CreateInputLayout( - static_cast(_shaderInputLayout), - ARRAYSIZE(_shaderInputLayout), - vertexBlob->GetBufferPointer(), - vertexBlob->GetBufferSize(), - &_vertexLayout)); - - // Create vertex buffer for screen quad. - D3D11_BUFFER_DESC bd{}; - bd.Usage = D3D11_USAGE_DEFAULT; - bd.ByteWidth = sizeof(ShaderInput) * ARRAYSIZE(_screenQuadVertices); - bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; - bd.CPUAccessFlags = 0; - - D3D11_SUBRESOURCE_DATA InitData{}; - InitData.pSysMem = static_cast(_screenQuadVertices); - - RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&bd, &InitData, &_screenQuadVertexBuffer)); - - D3D11_BUFFER_DESC pixelShaderSettingsBufferDesc{}; - pixelShaderSettingsBufferDesc.Usage = D3D11_USAGE_DEFAULT; - pixelShaderSettingsBufferDesc.ByteWidth = sizeof(_pixelShaderSettings); - pixelShaderSettingsBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - - _shaderStartTime = std::chrono::steady_clock::now(); - - _ComputePixelShaderSettings(); - - D3D11_SUBRESOURCE_DATA pixelShaderSettingsInitData{}; - pixelShaderSettingsInitData.pSysMem = &_pixelShaderSettings; - - RETURN_IF_FAILED(_d3dDevice->CreateBuffer(&pixelShaderSettingsBufferDesc, &pixelShaderSettingsInitData, &_pixelShaderSettingsBuffer)); - - // Sampler state is needed to use texture as input to shader. - D3D11_SAMPLER_DESC samplerDesc{}; - samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; - samplerDesc.MipLODBias = 0.0f; - samplerDesc.MaxAnisotropy = 1; - samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - samplerDesc.BorderColor[0] = 0; - samplerDesc.BorderColor[1] = 0; - samplerDesc.BorderColor[2] = 0; - samplerDesc.BorderColor[3] = 0; - samplerDesc.MinLOD = 0; - samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; - - // Create the texture sampler state. - RETURN_IF_FAILED(_d3dDevice->CreateSamplerState(&samplerDesc, &_samplerState)); - - _pixelShaderLoaded = true; - return S_OK; -} - -// Routine Description: -// - Puts the correct values in _pixelShaderSettings, so the struct can be -// passed the GPU and updates the GPU resource. -// Arguments: -// - -// Return Value: -// - -void DxEngine::_ComputePixelShaderSettings() noexcept -{ - if (_HasTerminalEffects() && _d3dDeviceContext && _pixelShaderSettingsBuffer) - { - try - { - // Set the time (seconds since the shader was loaded) - _pixelShaderSettings.Time = std::chrono::duration_cast>(std::chrono::steady_clock::now() - _shaderStartTime).count(); - - // Set the UI Scale - _pixelShaderSettings.Scale = _scale; - - // Set the display resolution - const auto w = static_cast(_displaySizePixels.width); - const auto h = static_cast(_displaySizePixels.height); - _pixelShaderSettings.Resolution = XMFLOAT2{ w, h }; - - // Set the background - DirectX::XMFLOAT4 background{}; - background.x = _backgroundColor.r; - background.y = _backgroundColor.g; - background.z = _backgroundColor.b; - background.w = _backgroundColor.a; - _pixelShaderSettings.Background = background; - - _d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, nullptr, &_pixelShaderSettings, 0, 0); - } - CATCH_LOG(); - } -} - -// Method Description: -// - Use DCompositionCreateSurfaceHandle to create a swapchain handle. This API -// is only present in Windows 8.1+, so we need to delay-load it to make sure -// we can still load on Windows 7. -// - We can't actually hit this on Windows 7, because only the WPF control uses -// us on Windows 7, and they're using the ForHwnd path, which doesn't hit this -// at all. -// Arguments: -// - -// Return Value: -// - An HRESULT for failing to load dcomp.dll, or failing to find the API, or an -// actual failure from the API itself. -[[nodiscard]] HRESULT DxEngine::_CreateSurfaceHandle() noexcept -{ -#pragma warning(suppress : 26447) - wil::unique_hmodule hDComp{ LoadLibraryEx(L"Dcomp.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) }; - RETURN_LAST_ERROR_IF(hDComp.get() == nullptr); - - auto fn = GetProcAddressByFunctionDeclaration(hDComp.get(), DCompositionCreateSurfaceHandle); - RETURN_LAST_ERROR_IF(fn == nullptr); - - return fn(GENERIC_ALL, nullptr, &_swapChainHandle); -} - -// Routine Description; -// - Creates device-specific resources required for drawing -// which generally means those that are represented on the GPU and can -// vary based on the monitor, display adapter, etc. -// - These may need to be recreated during the course of painting a frame -// should something about that hardware pipeline change. -// - Will free device resources that already existed as first operation. -// Arguments: -// - createSwapChain - If true, we create the entire rendering pipeline -// - If false, we just set up the adapter. -// Return Value: -// - Could be any DirectX/D3D/D2D/DXGI/DWrite error or memory issue. -[[nodiscard]] HRESULT DxEngine::_CreateDeviceResources(const bool createSwapChain) noexcept -try -{ - if (_haveDeviceResources) - { - _ReleaseDeviceResources(); - } - - auto freeOnFail = wil::scope_exit([&]() noexcept { _ReleaseDeviceResources(); }); - - RETURN_IF_FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&_dxgiFactory2))); - - const DWORD DeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | - // clang-format off -// This causes problems for folks who do not have the whole DirectX SDK installed -// when they try to run the rest of the project in debug mode. -// As such, I'm leaving this flag here for people doing DX-specific work to toggle it -// only when they need it and shutting it off otherwise. -// Find out more about the debug layer here: -// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers -// You can find out how to install it here: -// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features - // clang-format on - // D3D11_CREATE_DEVICE_DEBUG | - D3D11_CREATE_DEVICE_SINGLETHREADED; - - static constexpr std::array FeatureLevels{ - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - }; - - // Trying hardware first for maximum performance, then trying WARP (software) renderer second - // in case we're running inside a downlevel VM where hardware passthrough isn't enabled like - // for Windows 7 in a VM. - auto hardwareResult = E_NOT_SET; - - // If we're not forcing software rendering, try hardware first. - // Otherwise, let the error state fall down and create with the software renderer directly. - if (!_softwareRendering) - { - hardwareResult = D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_HARDWARE, - nullptr, - DeviceFlags, - FeatureLevels.data(), - gsl::narrow_cast(FeatureLevels.size()), - D3D11_SDK_VERSION, - &_d3dDevice, - nullptr, - &_d3dDeviceContext); - } - - if (FAILED(hardwareResult)) - { - RETURN_IF_FAILED(D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_WARP, - nullptr, - DeviceFlags, - FeatureLevels.data(), - gsl::narrow_cast(FeatureLevels.size()), - D3D11_SDK_VERSION, - &_d3dDevice, - nullptr, - &_d3dDeviceContext)); - } - - _displaySizePixels = _GetClientSize(); - - // Get the other device types so we have deeper access to more functionality - // in our pipeline than by just walking straight from the D3D device. - - RETURN_IF_FAILED(_d3dDevice.As(&_dxgiDevice)); - RETURN_IF_FAILED(_d2dFactory->CreateDevice(_dxgiDevice.Get(), _d2dDevice.ReleaseAndGetAddressOf())); - - // Create a device context out of it (supersedes render targets) - RETURN_IF_FAILED(_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &_d2dDeviceContext)); - - if (createSwapChain) - { - _swapChainDesc = { 0 }; - _swapChainDesc.Flags = 0; - - // requires DXGI 1.3 which was introduced in Windows 8.1 - WI_SetFlagIf(_swapChainDesc.Flags, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT, IsWindows8Point1OrGreater()); - - _swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - _swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - _swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - _swapChainDesc.BufferCount = 2; - _swapChainDesc.SampleDesc.Count = 1; - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - _swapChainDesc.Scaling = DXGI_SCALING_NONE; - - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - // use the HWND's dimensions for the swap chain dimensions. - RECT rect{}; - RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect)); - - _swapChainDesc.Width = rect.right - rect.left; - _swapChainDesc.Height = rect.bottom - rect.top; - - // We can't do alpha for HWNDs. Set to ignore. It will fail otherwise. - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; - const auto createSwapChainResult = _dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &_swapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain); - if (FAILED(createSwapChainResult)) - { - _swapChainDesc.Scaling = DXGI_SCALING_STRETCH; - RETURN_IF_FAILED(_dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), - _hwndTarget, - &_swapChainDesc, - nullptr, - nullptr, - &_dxgiSwapChain)); - } - - break; - } - case SwapChainMode::ForComposition: - { - if (!_swapChainHandle) - { - RETURN_IF_FAILED(_CreateSurfaceHandle()); - } - - RETURN_IF_FAILED(_dxgiFactory2.As(&_dxgiFactoryMedia)); - - // Use the given target size for compositions. - _swapChainDesc.Width = _displaySizePixels.narrow_width(); - _swapChainDesc.Height = _displaySizePixels.narrow_height(); - - // We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on. - _swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; - // It's 100% required to use scaling mode stretch for composition. There is no other choice. - _swapChainDesc.Scaling = DXGI_SCALING_STRETCH; - - RETURN_IF_FAILED(_dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(_d3dDevice.Get(), - _swapChainHandle.get(), - &_swapChainDesc, - nullptr, - &_dxgiSwapChain)); - break; - } - default: - THROW_HR(E_NOTIMPL); - } - - if (IsWindows8Point1OrGreater()) - { - ::Microsoft::WRL::ComPtr swapChain2; - const auto asResult = _dxgiSwapChain.As(&swapChain2); - if (SUCCEEDED(asResult)) - { - _swapChainFrameLatencyWaitableObject = wil::unique_handle{ swapChain2->GetFrameLatencyWaitableObject() }; - } - else - { - LOG_HR_MSG(asResult, "Failed to obtain IDXGISwapChain2 from swap chain"); - } - } - - if (_HasTerminalEffects()) - { - const auto hr = _SetupTerminalEffects(); - if (FAILED(hr)) - { - LOG_HR_MSG(hr, "Failed to setup terminal effects. Disabling."); - _terminalEffectsEnabled = false; - } - } - - // With a new swap chain, mark the entire thing as invalid. - RETURN_IF_FAILED(InvalidateAll()); - - // This is our first frame on this new target. - _firstFrame = true; - - RETURN_IF_FAILED(_PrepareRenderTarget()); - } - - _haveDeviceResources = true; - if (_isPainting) - { - // TODO: MSFT: 21169176 - remove this or restore the "try a few times to render" code... I think - _d2dDeviceContext->BeginDraw(); - } - - freeOnFail.release(); // don't need to release if we made it to the bottom and everything was good. - - // Notify that swap chain changed. - - if (_pfn) - { - try - { - _pfn(_swapChainHandle.get()); - } - CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on. - } - - _recreateDeviceRequested = false; - - return S_OK; -} -CATCH_RETURN(); - -static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noexcept -{ - switch (mode) - { - case DXGI_ALPHA_MODE_PREMULTIPLIED: - return D2D1_ALPHA_MODE_PREMULTIPLIED; - case DXGI_ALPHA_MODE_STRAIGHT: - return D2D1_ALPHA_MODE_STRAIGHT; - case DXGI_ALPHA_MODE_IGNORE: - return D2D1_ALPHA_MODE_IGNORE; - case DXGI_ALPHA_MODE_FORCE_DWORD: - return D2D1_ALPHA_MODE_FORCE_DWORD; - default: - case DXGI_ALPHA_MODE_UNSPECIFIED: - return D2D1_ALPHA_MODE_UNKNOWN; - } -} - -[[nodiscard]] HRESULT DxEngine::_PrepareRenderTarget() noexcept -{ - try - { - // Pull surface out of swap chain. - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface))); - - // Make a bitmap and bind it to the swap chain surface - const auto bitmapProperties = D2D1::BitmapProperties1( - D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, - D2D1::PixelFormat(_swapChainDesc.Format, _dxgiAlphaToD2d1Alpha(_swapChainDesc.AlphaMode))); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateBitmapFromDxgiSurface(_dxgiSurface.Get(), bitmapProperties, &_d2dBitmap)); - - // Assign that bitmap as the target of the D2D device context. Draw commands hit the context - // and are backed by the bitmap which is bound to the swap chain which goes on to be presented. - // (The foot bone connected to the leg bone, - // The leg bone connected to the knee bone, - // The knee bone connected to the thigh bone - // ... and so on) - - _d2dDeviceContext->SetTarget(_d2dBitmap.Get()); - - // We need the AntialiasMode for non-text object to be Aliased to ensure - // that background boxes line up with each other and don't leave behind - // stray colors. - // See GH#3626 for more details. - _d2dDeviceContext->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); - _d2dDeviceContext->SetTextAntialiasMode(_antialiasingMode); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), - &_d2dBrushBackground)); - - RETURN_IF_FAILED(_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), - &_d2dBrushForeground)); - - _strokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{ - D2D1_CAP_STYLE_SQUARE, // startCap - D2D1_CAP_STYLE_SQUARE, // endCap - D2D1_CAP_STYLE_SQUARE, // dashCap - D2D1_LINE_JOIN_MITER, // lineJoin - 0.f, // miterLimit - D2D1_DASH_STYLE_SOLID, // dashStyle - 0.f, // dashOffset - }; - RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_strokeStyleProperties, nullptr, 0, &_strokeStyle)); - - _dashStrokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{ - D2D1_CAP_STYLE_SQUARE, // startCap - D2D1_CAP_STYLE_SQUARE, // endCap - D2D1_CAP_STYLE_FLAT, // dashCap - D2D1_LINE_JOIN_MITER, // lineJoin - 0.f, // miterLimit - D2D1_DASH_STYLE_CUSTOM, // dashStyle - 0.f, // dashOffset - }; - // Custom dashes: - // # # # # - // 1234123412341234 - static constexpr std::array hyperlinkDashes{ 1.f, 3.f }; - RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_dashStrokeStyleProperties, hyperlinkDashes.data(), gsl::narrow_cast(hyperlinkDashes.size()), &_dashStrokeStyle)); - - // If in composition mode, apply scaling factor matrix - if (_chainMode == SwapChainMode::ForComposition) - { - DXGI_MATRIX_3X2_F inverseScale = { 0 }; - inverseScale._11 = 1.0f / _scale; - inverseScale._22 = inverseScale._11; - - ::Microsoft::WRL::ComPtr sc2; - RETURN_IF_FAILED(_dxgiSwapChain.As(&sc2)); - RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale)); - } - - _prevScale = _scale; - return S_OK; - } - CATCH_RETURN(); -} - -// Routine Description: -// - Releases device-specific resources (typically held on the GPU) -// Arguments: -// - -// Return Value: -// - -void DxEngine::_ReleaseDeviceResources() noexcept -{ - try - { - _haveDeviceResources = false; - - // Destroy Terminal Effect resources - _renderTargetView.Reset(); - _vertexShader.Reset(); - _pixelShader.Reset(); - _vertexLayout.Reset(); - _screenQuadVertexBuffer.Reset(); - _pixelShaderSettingsBuffer.Reset(); - _samplerState.Reset(); - _framebufferCapture.Reset(); - - _d2dBrushForeground.Reset(); - _d2dBrushBackground.Reset(); - - _d2dBitmap.Reset(); - - _softFont.Reset(); - - if (nullptr != _d2dDeviceContext.Get() && _isPainting) - { - _d2dDeviceContext->EndDraw(); - } - - _d2dDeviceContext.Reset(); - - _dxgiSurface.Reset(); - _dxgiSwapChain.Reset(); - _swapChainFrameLatencyWaitableObject.reset(); - - _d2dDevice.Reset(); - _dxgiDevice.Reset(); - - if (nullptr != _d3dDeviceContext.Get()) - { - // To ensure the swap chain goes away we must unbind any views from the - // D3D pipeline - _d3dDeviceContext->OMSetRenderTargets(0, nullptr, nullptr); - } - _d3dDeviceContext.Reset(); - - _d3dDevice.Reset(); - - _dxgiFactory2.Reset(); - } - CATCH_LOG(); -} - -// Routine Description: -// - Calculates whether or not we should force grayscale AA based on the -// current renderer state. -// Arguments: -// - - Uses internal state of _antialiasingMode, _defaultTextBackgroundOpacity, -// _backgroundColor, and _defaultBackgroundColor. -// Return Value: -// - True if we must render this text in grayscale AA as cleartype simply won't work. False otherwise. -[[nodiscard]] bool DxEngine::_ShouldForceGrayscaleAA() noexcept -{ - // GH#5098: If we're rendering with cleartype text, we need to always - // render onto an opaque background. If our background's opacity is - // 1.0f, that's great, we can use that. Otherwise, we need to force the - // text renderer to render this text in grayscale. In - // UpdateDrawingBrushes, we'll set the backgroundColor's a channel to - // 1.0 if we're in cleartype mode and the background's opacity is 1.0. - // Otherwise, at this point, the _backgroundColor's alpha is <1.0. - // - // Currently, only text with the default background color uses an alpha - // of 0, every other background uses 1.0 - // - // DANGER: Layers slow us down. Only do this in the specific case where - // someone has chosen the slower ClearType antialiasing (versus the faster - // grayscale antialiasing) - const auto usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - const auto usingTransparency = _defaultBackgroundIsTransparent; - // Another way of naming "bgIsDefault" is "bgHasTransparency" - const auto bgIsDefault = (_backgroundColor.a == _defaultBackgroundColor.a) && - (_backgroundColor.r == _defaultBackgroundColor.r) && - (_backgroundColor.g == _defaultBackgroundColor.g) && - (_backgroundColor.b == _defaultBackgroundColor.b); - const auto forceGrayscaleAA = usingCleartype && - usingTransparency && - bgIsDefault; - - return forceGrayscaleAA; -} - -// Routine Description: -// - Helper to create a DirectWrite text layout object -// out of a string. -// Arguments: -// - string - The text to attempt to layout -// - stringLength - Length of string above in characters -// - ppTextLayout - Location to receive new layout object -// Return Value: -// - S_OK if layout created successfully, otherwise a DirectWrite error -[[nodiscard]] HRESULT DxEngine::_CreateTextLayout( - _In_reads_(stringLength) PCWCHAR string, - _In_ size_t stringLength, - _Out_ IDWriteTextLayout** ppTextLayout) noexcept -try -{ - return _dwriteFactory->CreateTextLayout(string, - gsl::narrow(stringLength), - _fontRenderData->DefaultTextFormat().Get(), - static_cast(_displaySizePixels.width), - _fontRenderData->GlyphCell().height != 0 ? _fontRenderData->GlyphCell().narrow_height() : _displaySizePixels.narrow_height(), - ppTextLayout); -} -CATCH_RETURN() - -// Routine Description: -// - Sets the target window handle for our display pipeline -// - We will take over the surface of this window for drawing -// Arguments: -// - hwnd - Window handle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::SetHwnd(const HWND hwnd) noexcept -{ - _hwndTarget = hwnd; - _chainMode = SwapChainMode::ForHwnd; - return S_OK; -} - -[[nodiscard]] HRESULT DxEngine::SetWindowSize(const til::size Pixels) noexcept -try -{ - _sizeTarget = Pixels; - return S_OK; -} -CATCH_RETURN(); - -void DxEngine::SetCallback(std::function pfn) noexcept -{ - _pfn = std::move(pfn); -} - -void DxEngine::SetWarningCallback(std::function pfn) noexcept -{ - _pfnWarningCallback = std::move(pfn); -} - -bool DxEngine::GetRetroTerminalEffect() const noexcept -{ - return _retroTerminalEffect; -} - -void DxEngine::SetRetroTerminalEffect(bool enable) noexcept -try -{ - if (_retroTerminalEffect != enable) - { - // Enable shader effects if the path isn't empty. Otherwise leave it untouched. - _terminalEffectsEnabled = enable ? true : _terminalEffectsEnabled; - _retroTerminalEffect = enable; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -std::wstring_view DxEngine::GetPixelShaderPath() noexcept -{ - return _pixelShaderPath; -} - -void DxEngine::SetPixelShaderPath(std::wstring_view value) noexcept -try -{ - if (_pixelShaderPath != value) - { - // Enable shader effects if the path isn't empty. Otherwise leave it untouched. - _terminalEffectsEnabled = value.empty() ? _terminalEffectsEnabled : true; - _pixelShaderPath = std::wstring{ value }; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::SetForceFullRepaintRendering(bool enable) noexcept -try -{ - if (_forceFullRepaintRendering != enable) - { - _forceFullRepaintRendering = enable; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::SetSoftwareRendering(bool enable) noexcept -try -{ - if (_softwareRendering != enable) - { - _softwareRendering = enable; - _recreateDeviceRequested = true; - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -void DxEngine::_InvalidateRectangle(const til::rect& rc) -{ - const auto size = _invalidMap.size(); - const auto topLeft = til::point{ 0, std::clamp(rc.top, 0, size.height) }; - const auto bottomRight = til::point{ size.width, std::clamp(rc.bottom, 0, size.height) }; - _invalidMap.set({ topLeft, bottomRight }); -} - -bool DxEngine::_IsAllInvalid() const noexcept -{ - return std::abs(_invalidScroll.y) >= _invalidMap.size().height; -} - -// Routine Description: -// - Invalidates a rectangle described in characters -// Arguments: -// - psrRegion - Character rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::Invalidate(const til::rect* const psrRegion) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion); - - if (!_allInvalid) - { - _InvalidateRectangle(*psrRegion); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Invalidates the cells of the cursor -// Arguments: -// - psrRegion - the region covered by the cursor -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const til::rect* const psrRegion) noexcept -{ - return Invalidate(psrRegion); -} - -// Routine Description: -// - Invalidates a rectangle describing a pixel area on the display -// Arguments: -// - prcDirtyClient - pixel rectangle -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateSystem(const til::rect* const prcDirtyClient) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient); - - if (!_allInvalid) - { - // Dirty client is in pixels. Use divide specialization against glyph factor to make conversion - // to cells. - _InvalidateRectangle(prcDirtyClient->scale_down(_fontRenderData->GlyphCell())); - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Invalidates a series of character rectangles -// Arguments: -// - rectangles - One or more rectangles describing character positions on the grid -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateSelection(const std::vector& rectangles) noexcept -{ - if (!_allInvalid) - { - for (const auto& rect : rectangles) - { - RETURN_IF_FAILED(Invalidate(&rect)); - } - } - return S_OK; -} - -// Routine Description: -// - Scrolls the existing dirty region (if it exists) and -// invalidates the area that is uncovered in the window. -// Arguments: -// - pcoordDelta - The number of characters to move and uncover. -// - -Y is up, Y is down, -X is left, X is right. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateScroll(const til::point* const pcoordDelta) noexcept -try -{ - RETURN_HR_IF(E_INVALIDARG, !pcoordDelta); - - const auto deltaCells{ *pcoordDelta }; - - if (!_allInvalid) - { - if (deltaCells != til::point{}) - { - // Shift the contents of the map and fill in revealed area. - _invalidMap.translate(deltaCells, true); - _invalidScroll += deltaCells; - _allInvalid = _IsAllInvalid(); - } - } - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Invalidates the entire window area -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept -try -{ - _invalidMap.set_all(); - _allInvalid = true; - - // Since everything is invalidated here, mark this as a "first frame", so - // that we won't use incremental drawing on it. The caller of this intended - // for _everything_ to get redrawn, so setting _firstFrame will force us to - // redraw the entire frame. This will make sure that things like the gutters - // get cleared correctly. - // - // Invalidating everything is supposed to happen with resizes of the - // entire canvas, changes of the font, and other such adjustments. - _firstFrame = true; - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the area in pixels of the surface we are targeting -// Arguments: -// - -// Return Value: -// - X by Y area in pixels of the surface -[[nodiscard]] til::size DxEngine::_GetClientSize() const -{ - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - RECT clientRect{}; - LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect)); - - return til::rect{ clientRect }.size(); - } - case SwapChainMode::ForComposition: - { - return _sizeTarget; - } - default: - FAIL_FAST_HR(E_NOTIMPL); - } -} - -// Routine Description: -// - Helper to multiply all parameters of a rectangle by the font size -// to convert from characters to pixels. -// Arguments: -// - cellsToPixels - rectangle to update -// - fontSize - scaling factors -// Return Value: -// - - Updates reference -void _ScaleByFont(til::rect& cellsToPixels, til::size fontSize) noexcept -{ - cellsToPixels.left *= fontSize.width; - cellsToPixels.right *= fontSize.width; - cellsToPixels.top *= fontSize.height; - cellsToPixels.bottom *= fontSize.height; -} - -// Routine Description: -// - This is unused by this renderer. -// Arguments: -// - pForcePaint - always filled with false. -// Return Value: -// - S_FALSE because this is unused. -[[nodiscard]] HRESULT DxEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); - - *pForcePaint = false; - return S_FALSE; -} - -// Routine description: -// - Prepares the surfaces for painting and begins a drawing batch -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept -try -{ - RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting. - - // If full repaints are needed then we need to invalidate everything - // so the entire frame is repainted. - if (_FullRepaintNeeded()) - { - RETURN_IF_FAILED(InvalidateAll()); - } - - if (TraceLoggingProviderEnabled(g_hDxRenderProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) - { - const auto invalidatedStr = _invalidMap.to_string(); - const auto invalidated = invalidatedStr.c_str(); - -#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite - TraceLoggingWrite(g_hDxRenderProvider, - "Invalid", - TraceLoggingWideString(invalidated), - TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), - TraceLoggingKeyword(TIL_KEYWORD_TRACE)); - } - - if (_isEnabled) - { - const auto clientSize = _GetClientSize(); - const auto glyphCellSize = _fontRenderData->GlyphCell(); - - // If we don't have device resources or if someone has requested that we - // recreate the device... then make new resources. (Create will dump the old ones.) - if (!_haveDeviceResources || _recreateDeviceRequested) - { - RETURN_IF_FAILED(_CreateDeviceResources(true)); - } - else if (_displaySizePixels != clientSize || _prevScale != _scale) - { - // OK, we're going to play a dangerous game here for the sake of optimizing resize - // First, set up a complete clear of all device resources if something goes terribly wrong. - auto resetDeviceResourcesOnFailure = wil::scope_exit([&]() noexcept { - _ReleaseDeviceResources(); - }); - - // Now let go of a few of the device resources that get in the way of resizing buffers in the swap chain - _dxgiSurface.Reset(); - _d2dDeviceContext->SetTarget(nullptr); - _d2dBitmap.Reset(); - - // Change the buffer size and recreate the render target (and surface) - RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.narrow_width(), clientSize.narrow_height(), _swapChainDesc.Format, _swapChainDesc.Flags)); - RETURN_IF_FAILED(_PrepareRenderTarget()); - - // OK we made it past the parts that can cause errors. We can release our failure handler. - resetDeviceResourcesOnFailure.release(); - - // And persist the new size. - _displaySizePixels = clientSize; - } - - if (const auto size = clientSize / glyphCellSize; size != _invalidMap.size()) - { - _invalidMap.resize(size); - RETURN_IF_FAILED(InvalidateAll()); - } - - _d2dDeviceContext->BeginDraw(); - _isPainting = true; - - { - // Get the baseline for this font as that's where we draw from - DWRITE_LINE_SPACING spacing; - RETURN_IF_FAILED(_fontRenderData->DefaultTextFormat()->GetLineSpacing(&spacing.method, &spacing.height, &spacing.baseline)); - - // Assemble the drawing context information - _drawingContext = std::make_unique(_d2dDeviceContext.Get(), - _d2dBrushForeground.Get(), - _d2dBrushBackground.Get(), - _ShouldForceGrayscaleAA(), - _dwriteFactory.Get(), - spacing, - glyphCellSize.to_d2d_size(), - _d2dDeviceContext->GetSize(), - std::nullopt, - D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT); - } - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Ends batch drawing and captures any state necessary for presentation -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::EndPaint() noexcept -try -{ - RETURN_HR_IF(E_INVALIDARG, !_isPainting); // invalid to end paint when we're not painting - - auto hr = S_OK; - - if (_haveDeviceResources) - { - _isPainting = false; - - // If there's still a clip hanging around, remove it. We're all done. - LOG_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - - hr = _d2dDeviceContext->EndDraw(); - - if (SUCCEEDED(hr)) - { - if (_invalidScroll != til::point{ 0, 0 }) - { - // Copy `til::rects` into RECT map. - _presentDirty.assign(_invalidMap.begin(), _invalidMap.end()); - - // Scale all dirty rectangles into pixels - std::transform(_presentDirty.begin(), _presentDirty.end(), _presentDirty.begin(), [&](const til::rect& rc) { - return rc.scale_up(_fontRenderData->GlyphCell()); - }); - - // Invalid scroll is in characters, convert it to pixels. - const auto scrollPixels = (_invalidScroll * _fontRenderData->GlyphCell()); - - // The scroll rect is the entire field of cells, but in pixels. - til::rect scrollArea{ _invalidMap.size() * _fontRenderData->GlyphCell() }; - - // Reduce the size of the rectangle by the scroll. - scrollArea.left = std::clamp(scrollArea.left + scrollPixels.x, scrollArea.left, scrollArea.right); - scrollArea.top = std::clamp(scrollArea.top + scrollPixels.y, scrollArea.top, scrollArea.bottom); - scrollArea.right = std::clamp(scrollArea.right + scrollPixels.x, scrollArea.left, scrollArea.right); - scrollArea.bottom = std::clamp(scrollArea.bottom + scrollPixels.y, scrollArea.top, scrollArea.bottom); - - // Assign the area to the present storage - _presentScroll = scrollArea.to_win32_rect(); - - // Pass the offset. - _presentOffset = scrollPixels.to_win32_point(); - - // Now fill up the parameters structure from the member variables. - _presentParams.DirtyRectsCount = gsl::narrow(_presentDirty.size()); - - // It's not nice to use reinterpret_cast between til::rect and RECT, - // but to be honest... it does save a ton of type juggling. - static_assert(sizeof(decltype(_presentDirty)::value_type) == sizeof(RECT)); -#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1). - _presentParams.pDirtyRects = reinterpret_cast(_presentDirty.data()); - - _presentParams.pScrollOffset = &_presentOffset; - _presentParams.pScrollRect = &_presentScroll; - - // The scroll rect will be empty if we scrolled >= 1 full screen size. - // Present1 doesn't like that. So clear it out. Everything will be dirty anyway. - if (IsRectEmpty(&_presentScroll)) - { - _presentParams.pScrollRect = nullptr; - _presentParams.pScrollOffset = nullptr; - } - } - - _presentReady = true; - } - else - { - _presentReady = false; - _ReleaseDeviceResources(); - } - } - - _invalidMap.reset_all(); - _allInvalid = false; - - _invalidScroll = {}; - - return hr; -} -CATCH_RETURN() - -// Routine Description: -// - Copies the front surface of the swap chain (the one being displayed) -// to the back surface of the swap chain (the one we draw on next) -// so we can draw on top of what's already there. -// Arguments: -// - -// Return Value: -// - Any DirectX error, a memory error, etc. -[[nodiscard]] HRESULT DxEngine::_CopyFrontToBack() noexcept -{ - try - { - Microsoft::WRL::ComPtr backBuffer; - Microsoft::WRL::ComPtr frontBuffer; - - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))); - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(1, IID_PPV_ARGS(&frontBuffer))); - - _d3dDeviceContext->CopyResource(backBuffer.Get(), frontBuffer.Get()); - } - CATCH_RETURN(); - - return S_OK; -} - -// Method Description: -// - When the shaders are on, say that we need to keep redrawing every -// possible frame in case they have some smooth action on every frame tick. -// It is presumed that if you're using shaders, you're not about performance... -// You're instead about OOH SHINY. And that's OK. But returning true here is 100% -// a perf detriment. -[[nodiscard]] bool DxEngine::RequiresContinuousRedraw() noexcept -{ - // We're only going to request continuous redraw if someone is using - // a pixel shader from a path because we cannot tell if those are using the - // time parameter or not. - // And if they are using time, they probably need it to tick continuously. - // - // By contrast, the in-built retro effect does NOT need it, - // so let's not tick for it and save some amount of performance. - // - // Finally... if we're not using effects at all... let the render thread - // go to sleep. It deserves it. That thread works hard. Also it sleeping - // saves battery power and all sorts of related perf things. - return _terminalEffectsEnabled && !_pixelShaderPath.empty(); -} - -// Method Description: -// - Blocks until the engine is able to render without blocking. -// - See https://docs.microsoft.com/en-us/windows/uwp/gaming/reduce-latency-with-dxgi-1-3-swap-chains. -void DxEngine::WaitUntilCanRender() noexcept -{ - // Throttle the DxEngine a bit down to ~60 FPS. - // This improves throughput for rendering complex or colored text. - Sleep(8); - - if (_swapChainFrameLatencyWaitableObject) - { - WaitForSingleObjectEx(_swapChainFrameLatencyWaitableObject.get(), 100, true); - } -} - -// Routine Description: -// - Takes queued drawing information and presents it to the screen. -// - This is separated out so it can be done outside the lock as it's expensive. -// Arguments: -// - -// Return Value: -// - S_OK on success, E_PENDING to indicate a retry or a relevant DirectX error -[[nodiscard]] HRESULT DxEngine::Present() noexcept -{ - if (_presentReady) - { - if (_HasTerminalEffects() && _pixelShaderLoaded) - { - const auto hr2 = _PaintTerminalEffects(); - if (FAILED(hr2)) - { - _pixelShaderLoaded = false; - LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling."); - } - } - - try - { - auto hr = S_OK; - - auto recreate = false; - - // On anything but the first frame, try partial presentation. - // We'll do it first because if it fails, we'll try again with full presentation. - if (!_firstFrame) - { - hr = _dxgiSwapChain->Present1(1, 0, &_presentParams); - - // These two error codes are indicated for destroy-and-recreate - // If we were told to destroy-and-recreate, we're going to skip straight into doing that - // and not try again with full presentation. - recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET; - - // Log this as we actually don't expect it to happen, we just will try again - // below for robustness of our drawing. - if (FAILED(hr) && !recreate) - { - LOG_HR(hr); - } - } - - // If it's the first frame through, we cannot do partial presentation. - // Also if partial presentation failed above and we weren't told to skip straight to - // device recreation. - // In both of these circumstances, do a full presentation. - if (_firstFrame || (FAILED(hr) && !recreate)) - { - hr = _dxgiSwapChain->Present(1, 0); - _firstFrame = false; - - // These two error codes are indicated for destroy-and-recreate - recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET; - } - - // Now check for failure cases from either presentation mode. - if (FAILED(hr)) - { - // If we were told to recreate the device surface, do that. - if (recreate) - { - // We don't need to end painting here, as the renderer has done it for us. - _ReleaseDeviceResources(); - FAIL_FAST_IF_FAILED(InvalidateAll()); - return E_PENDING; // Indicate a retry to the renderer. - } - // Otherwise, we don't know what to do with this error. Report it. - else - { - FAIL_FAST_HR(hr); - } - } - - // If we are doing full repaints we don't need to copy front buffer to back buffer - if (!_FullRepaintNeeded()) - { - // Finally copy the front image (being presented now) onto the backing buffer - // (where we are about to draw the next frame) so we can draw only the differences - // next frame. - RETURN_IF_FAILED(_CopyFrontToBack()); - } - - _presentReady = false; - - _presentDirty.clear(); - _presentOffset = { 0 }; - _presentScroll = { 0 }; - _presentParams = { 0 }; - } - CATCH_RETURN(); - } - - return S_OK; -} - -// Routine Description: -// - This is currently unused. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::ScrollFrame() noexcept -{ - return S_OK; -} - -// Routine Description: -// - This paints in the back most layer of the frame with the background color. -// Arguments: -// - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept -try -{ - D2D1_COLOR_F nothing{ 0 }; - if (_chainMode == SwapChainMode::ForHwnd) - { - // When we're drawing over an HWND target, we need to fully paint the background color. - nothing = _backgroundColor; - } - - // If the entire thing is invalid, just use one big clear operation. - if (_invalidMap.all()) - { - _d2dDeviceContext->Clear(nothing); - } - else - { - // Runs are counts of cells. - // Use a transform by the size of one cell to convert cells-to-pixels - // as we clear. - _d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Scale(_fontRenderData->GlyphCell().to_d2d_size())); - for (const auto& rect : _invalidMap.runs()) - { - // Use aliased. - // For graphics reasons, it'll look better because it will ensure that - // the edges are cut nice and sharp (not blended by anti-aliasing). - // For performance reasons, it takes a lot less work to not - // do anti-alias blending. - _d2dDeviceContext->PushAxisAlignedClip(rect.to_d2d_rect(), D2D1_ANTIALIAS_MODE_ALIASED); - _d2dDeviceContext->Clear(nothing); - _d2dDeviceContext->PopAxisAlignedClip(); - } - _d2dDeviceContext->SetTransform(D2D1::Matrix3x2F::Identity()); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Places one line of text onto the screen at the given position -// Arguments: -// - clusters - Iterable collection of cluster information (text and columns it should consume) -// - coord - Character coordinate position in the cell grid -// - fTrimLeft - Whether or not to trim off the left half of a double wide character -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::PaintBufferLine(const std::span clusters, - const til::point coord, - const bool /*trimLeft*/, - const bool /*lineWrapped*/) noexcept -try -{ - // Calculate positioning of our origin. - const auto origin = (coord * _fontRenderData->GlyphCell()).to_d2d_point(); - - if (_usingSoftFont) - { - // We need to reset the clipping rect applied by the CustomTextRenderer, - // since the soft font will want to set its own clipping rect. - RETURN_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - RETURN_IF_FAILED(_softFont.Draw(*_drawingContext, clusters, origin.x, origin.y)); - } - else - { - // Create the text layout - RETURN_IF_FAILED(_customLayout->Reset()); - RETURN_IF_FAILED(_customLayout->AppendClusters(clusters)); - - // Layout then render the text - RETURN_IF_FAILED(_customLayout->Draw(_drawingContext.get(), _customRenderer.Get(), origin.x, origin.y)); - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Paints lines around cells (draws in pieces of the grid) -// Arguments: -// - lines - Which grid lines (top, left, bottom, right) to draw -// - gridlineColor - The color to use for drawing the gridlines -// - underlineColor - The color to use for drawing the underlines -// - cchLine - Length of the line to draw in character cells -// - coordTarget - The X,Y character position in the grid where we should start drawing -// - We will draw rightward (+X) from here -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(const GridLineSet lines, - const COLORREF gridlineColor, - const COLORREF underlineColor, - const size_t cchLine, - const til::point coordTarget) noexcept -try -{ - const auto existingColor = _d2dBrushForeground->GetColor(); - const auto restoreBrushOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - - const auto font = _fontRenderData->GlyphCell().to_d2d_size(); - const D2D_POINT_2F target = { coordTarget.x * font.width, coordTarget.y * font.height }; - const auto fullRunWidth = font.width * gsl::narrow_cast(cchLine); - - const auto DrawLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { - _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get()); - }; - - const auto DrawDottedLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept { - _d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _dashStrokeStyle.Get()); - }; - - _d2dBrushForeground->SetColor(_ColorFFromColorRef(gridlineColor | 0xff000000)); - - // NOTE: Line coordinates are centered within the line, so they need to be - // offset by half the stroke width. For the start coordinate we add half - // the stroke width, and for the end coordinate we subtract half the width. - const auto lineMetrics = _fontRenderData->GetLineMetrics(); - if (lines.any(GridLines::Left, GridLines::Right)) - { - const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; - const auto startY = target.y + halfGridlineWidth; - const auto endY = target.y + font.height - halfGridlineWidth; - - if (lines.test(GridLines::Left)) - { - auto x = target.x + halfGridlineWidth; - for (size_t i = 0; i < cchLine; i++, x += font.width) - { - DrawLine(x, startY, x, endY, lineMetrics.gridlineWidth); - } - } - - if (lines.test(GridLines::Right)) - { - auto x = target.x + font.width - halfGridlineWidth; - for (size_t i = 0; i < cchLine; i++, x += font.width) - { - DrawLine(x, startY, x, endY, lineMetrics.gridlineWidth); - } - } - } - - if (lines.any(GridLines::Top, GridLines::Bottom)) - { - const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; - const auto startX = target.x + halfGridlineWidth; - const auto endX = target.x + fullRunWidth - halfGridlineWidth; - - if (lines.test(GridLines::Top)) - { - const auto y = target.y + halfGridlineWidth; - DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); - } - - if (lines.test(GridLines::Bottom)) - { - const auto y = target.y + font.height - halfGridlineWidth; - DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); - } - } - - if (lines.test(GridLines::Strikethrough)) - { - const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; - const auto startX = target.x + halfStrikethroughWidth; - const auto endX = target.x + fullRunWidth - halfStrikethroughWidth; - const auto y = target.y + lineMetrics.strikethroughOffset; - - DrawLine(startX, y, endX, y, lineMetrics.strikethroughWidth); - } - - _d2dBrushForeground->SetColor(_ColorFFromColorRef(underlineColor | 0xff000000)); - - // In the case of the underline and strikethrough offsets, the stroke width - // is already accounted for, so they don't require further adjustments. - - if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) - { - const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; - const auto startX = target.x + halfUnderlineWidth; - const auto endX = target.x + fullRunWidth - halfUnderlineWidth; - const auto y = target.y + lineMetrics.underlineOffset; - - if (lines.test(GridLines::Underline)) - { - DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); - } - - if (lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline)) - { - DrawDottedLine(startX, y, endX, y, lineMetrics.underlineWidth); - } - - if (lines.test(GridLines::DoubleUnderline)) - { - DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); - const auto y2 = target.y + lineMetrics.underlineOffset2; - DrawLine(startX, y2, endX, y2, lineMetrics.underlineWidth); - } - } - - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Paints an overlay highlight on a portion of the frame to represent selected text -// Arguments: -// - rect - Rectangle to invert or highlight to make the selection area -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::PaintSelection(const til::rect& rect) noexcept -try -{ - // If a clip rectangle is in place from drawing the text layer, remove it here. - LOG_IF_FAILED(_customRenderer->EndClip(_drawingContext.get())); - - const auto existingColor = _d2dBrushForeground->GetColor(); - - _d2dBrushForeground->SetColor(_selectionBackground); - const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); }); - - const D2D1_RECT_F draw = rect.scale_up(_fontRenderData->GlyphCell()).to_d2d_rect(); - - _d2dDeviceContext->FillRectangle(draw, _d2dBrushForeground.Get()); - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] HRESULT DxEngine::PaintSelections(const std::vector& rects) noexcept -try -{ - UNREFERENCED_PARAMETER(rects); - return S_OK; -} -CATCH_RETURN() - -// Routine Description: -// - Does nothing. Our cursor is drawn in CustomTextRenderer::DrawGlyphRun, -// either above or below the text. -// Arguments: -// - options - unused -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PaintCursor(const CursorOptions& /*options*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Paint terminal effects. -// Arguments: -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::_PaintTerminalEffects() noexcept -try -{ - // Should have been initialized. - RETURN_HR_IF(E_NOT_VALID_STATE, !_framebufferCapture); - - // Capture current frame in swap chain to a texture. - ::Microsoft::WRL::ComPtr swapBuffer; - RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&swapBuffer))); - _d3dDeviceContext->CopyResource(_framebufferCapture.Get(), swapBuffer.Get()); - - // Prepare captured texture as input resource to shader program. - D3D11_TEXTURE2D_DESC desc; - _framebufferCapture->GetDesc(&desc); - - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MostDetailedMip = 0; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Format = desc.Format; - - ::Microsoft::WRL::ComPtr shaderResource; - RETURN_IF_FAILED(_d3dDevice->CreateShaderResourceView(_framebufferCapture.Get(), &srvDesc, &shaderResource)); - - // Render the screen quad with shader effects. - const UINT stride = sizeof(ShaderInput); - const UINT offset = 0; - - _d3dDeviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr); - _d3dDeviceContext->IASetVertexBuffers(0, 1, _screenQuadVertexBuffer.GetAddressOf(), &stride, &offset); - _d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - _d3dDeviceContext->IASetInputLayout(_vertexLayout.Get()); - _d3dDeviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0); - _d3dDeviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0); - _d3dDeviceContext->PSSetShaderResources(0, 1, shaderResource.GetAddressOf()); - _d3dDeviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf()); - _d3dDeviceContext->PSSetConstantBuffers(0, 1, _pixelShaderSettingsBuffer.GetAddressOf()); - _d3dDeviceContext->Draw(ARRAYSIZE(_screenQuadVertices), 0); - - return S_OK; -} -CATCH_RETURN() - -[[nodiscard]] bool DxEngine::_FullRepaintNeeded() const noexcept -{ - // If someone explicitly requested differential rendering off, then we need to invalidate everything - // so the entire frame is repainted. - // - // If terminal effects are on, we must invalidate everything for them to draw correctly. - // Yes, this will further impact the performance of terminal effects. - // But we're talking about running the entire display pipeline through a shader for - // cosmetic effect, so performance isn't likely the top concern with this feature. - return _forceFullRepaintRendering || _HasTerminalEffects(); -} - -// Routine Description: -// - Updates the default brush colors used for drawing -// Arguments: -// - textAttributes - Text attributes to use for the brush color -// - renderSettings - The color table and modes required for rendering -// - pData - The interface to console data structures required for rendering -// - usingSoftFont - Whether we're rendering characters from a soft font -// - isSettingDefaultBrushes - Lets us know that these are the default brushes to paint the swapchain background or selection -// Return Value: -// - S_OK or relevant DirectX error. -[[nodiscard]] HRESULT DxEngine::UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null /*pData*/, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept -try -{ - const auto [colorForeground, colorBackground] = renderSettings.GetAttributeColorsWithAlpha(textAttributes); - - const auto usingCleartype = _antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - const auto usingTransparency = _defaultBackgroundIsTransparent; - const auto forceOpaqueBG = usingCleartype && !usingTransparency; - - _foregroundColor = _ColorFFromColorRef(OPACITY_OPAQUE | colorForeground); - // October 2021: small changes were made to the way BG color interacts with - // grayscale AA, esp. with regards to acrylic and GH#5098. See comment in - // _ShouldForceGrayscaleAA for more details. - _backgroundColor = _ColorFFromColorRef((forceOpaqueBG ? OPACITY_OPAQUE : 0) | colorBackground); - - _d2dBrushForeground->SetColor(_foregroundColor); - _d2dBrushBackground->SetColor(_backgroundColor); - - _usingSoftFont = usingSoftFont; - if (_usingSoftFont) - { - _softFont.SetColor(_foregroundColor); - } - - // If this flag is set, then we need to update the default brushes too and the swap chain background. - if (isSettingDefaultBrushes) - { - _defaultForegroundColor = _foregroundColor; - _defaultBackgroundColor = _backgroundColor; - - // If we have a swap chain, set the background color there too so the area - // outside the chain on a resize can be filled in with an appropriate color value. - /*if (_dxgiSwapChain) - { - const auto dxgiColor = s_RgbaFromColorF(_defaultBackgroundColor); - RETURN_IF_FAILED(_dxgiSwapChain->SetBackgroundColor(&dxgiColor)); - }*/ - } - - // If we have a drawing context, it may be choosing its antialiasing based - // on the colors. Update it if it exists. - // Also record whether we need to render the text with an italic font. - // We only need to do this here because this is called all the time on painting frames - // and will update it in a timely fashion. Changing the AA mode or opacity do affect - // it, but we will always hit updating the drawing brushes so we don't - // need to update this in those locations. - if (_drawingContext) - { - _drawingContext->forceGrayscaleAA = _ShouldForceGrayscaleAA(); - _drawingContext->useBoldFont = textAttributes.IsIntense() && renderSettings.GetRenderMode(RenderSettings::Mode::IntenseIsBold); - _drawingContext->useItalicFont = textAttributes.IsItalic(); - } - - // Update pixel shader settings as background color might have changed - _ComputePixelShaderSettings(); - - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Updates the font used for drawing -// - This is the version that complies with the IRenderEngine interface -// Arguments: -// - pfiFontInfoDesired - Information specifying the font that is requested -// - fiFontInfo - Filled with the nearest font actually chosen for drawing -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept -{ - return UpdateFont(pfiFontInfoDesired, fiFontInfo, {}, {}); -} - -// Routine Description: -// - Updates the font used for drawing -// Arguments: -// - pfiFontInfoDesired - Information specifying the font that is requested -// - fiFontInfo - Filled with the nearest font actually chosen for drawing -// - features - The map of font features to use -// - axes - The map of font axes to use -// Return Value: -// - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept -try -{ - RETURN_IF_FAILED(_fontRenderData->UpdateFont(pfiFontInfoDesired, fiFontInfo, _dpi, features, axes)); - - // Prepare the text layout. - _customLayout = WRL::Make(_fontRenderData.get()); - - // Inform the soft font of the new cell size so it can scale appropriately. - return _softFont.SetTargetSize(_fontRenderData->GlyphCell()); -} -CATCH_RETURN(); - -[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) const noexcept -{ - const auto widthInChars = viewInPixels.Width() / _fontRenderData->GlyphCell().width; - const auto heightInChars = viewInPixels.Height() / _fontRenderData->GlyphCell().height; - - return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars }); -} - -[[nodiscard]] Viewport DxEngine::GetViewportInPixels(const Viewport& viewInCharacters) const noexcept -{ - const auto widthInPixels = viewInCharacters.Width() * _fontRenderData->GlyphCell().width; - const auto heightInPixels = viewInCharacters.Height() * _fontRenderData->GlyphCell().height; - - return Viewport::FromDimensions(viewInCharacters.Origin(), { widthInPixels, heightInPixels }); -} - -// Routine Description: -// - Sets the DPI in this renderer -// Arguments: -// - iDpi - DPI -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::UpdateDpi(const int iDpi) noexcept -{ - _dpi = iDpi; - - // The scale factor may be necessary for composition contexts, so save it once here. - _scale = _dpi / static_cast(USER_DEFAULT_SCREEN_DPI); - - RETURN_IF_FAILED(InvalidateAll()); - - // Update pixel shader settings as scale might have changed - _ComputePixelShaderSettings(); - - return S_OK; -} - -// Method Description: -// - Get the current scale factor of this renderer. The actual DPI the renderer -// is USER_DEFAULT_SCREEN_DPI * GetScaling() -// Arguments: -// - -// Return Value: -// - the scaling multiplier of this render engine -float DxEngine::GetScaling() const noexcept -{ - return _scale; -} - -// Method Description: -// - This method will update our internal reference for how big the viewport is. -// Does nothing for DX. -// Arguments: -// - srNewViewport - The bounds of the new viewport. -// Return Value: -// - HRESULT S_OK -[[nodiscard]] HRESULT DxEngine::UpdateViewport(const til::inclusive_rect& /*srNewViewport*/) noexcept -{ - return S_OK; -} - -// Routine Description: -// - Currently unused by this renderer -// Arguments: -// - pfiFontInfoDesired - -// - pfiFontInfo - -// - iDpi - -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetProposedFont(const FontInfoDesired& pfiFontInfoDesired, - FontInfo& pfiFontInfo, - const int iDpi) noexcept -try -{ - DxFontRenderData fontRenderData(_dwriteFactory); - return fontRenderData.UpdateFont(pfiFontInfoDesired, pfiFontInfo, iDpi); -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the area that we currently believe is dirty within the character cell grid -// Arguments: -// - area - Rectangle describing dirty area in characters. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetDirtyArea(std::span& area) noexcept -try -{ - area = _invalidMap.runs(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Gets the current font size -// Arguments: -// - pFontSize - Filled with the font size. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::GetFontSize(_Out_ til::size* const pFontSize) noexcept -try -{ - *pFontSize = _fontRenderData->GlyphCell(); - return S_OK; -} -CATCH_RETURN(); - -// Routine Description: -// - Currently unused by this renderer. -// Arguments: -// - glyph - The glyph run to process for column width. -// - pResult - True if it should take two columns. False if it should take one. -// Return Value: -// - S_OK or relevant DirectWrite error. -[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept -try -{ - RETURN_HR_IF_NULL(E_INVALIDARG, pResult); - - const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout. - - RETURN_IF_FAILED(_customLayout->Reset()); - RETURN_IF_FAILED(_customLayout->AppendClusters({ &cluster, 1 })); - - UINT32 columns = 0; - RETURN_IF_FAILED(_customLayout->GetColumns(&columns)); - - *pResult = columns != 1; - - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Updates the window's title string. -// Arguments: -// - newTitle: the new string to use for the title of the window -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::_DoUpdateTitle(_In_ const std::wstring_view /*newTitle*/) noexcept -{ - if (_hwndTarget != INVALID_HANDLE_VALUE) - { - return PostMessageW(_hwndTarget, CM_UPDATE_TITLE, 0, 0) ? S_OK : E_FAIL; - } - return S_FALSE; -} - -// Routine Description: -// - Helps convert a GDI COLORREF into a Direct2D ColorF -// Arguments: -// - color - GDI color -// Return Value: -// - D2D color -[[nodiscard]] D2D1_COLOR_F DxEngine::_ColorFFromColorRef(const COLORREF color) noexcept -{ - // Converts BGR color order to RGB. - const UINT32 rgb = ((color & 0x0000FF) << 16) | (color & 0x00FF00) | ((color & 0xFF0000) >> 16); - - switch (_chainMode) - { - case SwapChainMode::ForHwnd: - { - return D2D1::ColorF(rgb); - } - case SwapChainMode::ForComposition: - { - // Get the A value we've snuck into the highest byte - const BYTE a = ((color >> 24) & 0xFF); - const auto aFloat = a / 255.0f; - - return D2D1::ColorF(rgb, aFloat); - } - default: - FAIL_FAST_HR(E_NOTIMPL); - } -} - -// Routine Description: -// - Updates the selection background color of the DxEngine -// Arguments: -// - color - GDI Color -// Return Value: -// - N/A -void DxEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept -{ - _selectionBackground = D2D1::ColorF(GetRValue(color) / 255.0f, - GetGValue(color) / 255.0f, - GetBValue(color) / 255.0f, - alpha); -} - -// Routine Description: -// - Changes the antialiasing mode of the renderer. This must be called before -// _PrepareRenderTarget, otherwise the renderer will default to -// D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE. -// Arguments: -// - antialiasingMode: a value from the D2D1_TEXT_ANTIALIAS_MODE enum. See: -// https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_text_antialias_mode -// Return Value: -// - N/A -void DxEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept -try -{ - if (_antialiasingMode != antialiasingMode) - { - _antialiasingMode = antialiasingMode; - _recreateDeviceRequested = true; - LOG_IF_FAILED(_softFont.SetAntialiasing(antialiasingMode != D2D1_TEXT_ANTIALIAS_MODE_ALIASED)); - LOG_IF_FAILED(InvalidateAll()); - } -} -CATCH_LOG() - -// Method Description: -// - Update our tracker of the opacity of our background. We can only -// effectively render cleartype text onto fully-opaque backgrounds. If we're -// rendering onto a transparent surface (like acrylic), then cleartype won't -// work correctly, and will actually just additively blend with the -// background. This is here to support GH#5098. -// - We'll use this, along with whether cleartype was requested, to manually set -// the alpha channel of the background brush to 1.0. We need to do that to -// make cleartype work without blending. However, we don't want to do that too -// often - if we do that on top of a transparent BG, then the entire swap -// chain will be fully opaque. -// Arguments: -// - isTransparent: true if our BG is transparent (acrylic, or anything that's not fully opaque) -// Return Value: -// - -void DxEngine::EnableTransparentBackground(const bool isTransparent) noexcept -try -{ - _defaultBackgroundIsTransparent = isTransparent; - - // Make sure we redraw all the cells, to update whether they're actually - // drawn with cleartype or not. - // We don't terribly care if this fails. - LOG_IF_FAILED(InvalidateAll()); -} -CATCH_LOG() - -// Method Description: -// - Updates our internal tracker for which hyperlink ID we are hovering over -// This is needed for UpdateDrawingBrushes to know where we need to set a different style -// Arguments: -// - The new link ID we are hovering over -void DxEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept -{ - _hyperlinkHoveredId = hoveredId; -} - -// Routine Description: -// - This method will replace the active soft font with the given bit pattern. -// Arguments: -// - bitPattern - An array of scanlines representing all the glyphs in the font. -// - cellSize - The cell size for an individual glyph. -// - centeringHint - The horizontal extent that glyphs are offset from center. -// Return Value: -// - S_OK if successful. E_FAIL if there was an error. -HRESULT DxEngine::UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept -try -{ - _softFont.SetFont(bitPattern, cellSize, _fontRenderData->GlyphCell(), centeringHint); - return S_OK; -} -CATCH_RETURN(); - -// Method Description: -// - Informs this render engine about certain state for this frame at the -// beginning of this frame. We'll use it to get information about the cursor -// before PaintCursor is called. This enables the DX renderer to draw the -// cursor underneath the text. -// - This is called every frame. When the cursor is Off or out of frame, the -// info's cursorInfo will be set to std::nullopt; -// Arguments: -// - info - a RenderFrameInfo with information about the state of the cursor in this frame. -// Return Value: -// - S_OK -[[nodiscard]] HRESULT DxEngine::PrepareRenderInfo(const RenderFrameInfo& info) noexcept -{ - _drawingContext->cursorInfo = info.cursorInfo; - return S_OK; -} - -// Routine Description -// - Resets the world transform to the identity matrix. -// Arguments: -// - -// Return Value: -// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error. -[[nodiscard]] HRESULT DxEngine::ResetLineTransform() noexcept -{ - // Return early if the current transform is already the identity matrix. - RETURN_HR_IF(S_FALSE, _currentLineTransform.IsIdentity()); - // Reset the active transform to the identity matrix. - _drawingContext->renderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); - // Reset the clipping offsets. - _drawingContext->topClipOffset = 0; - _drawingContext->bottomClipOffset = 0; - // Reset the current state. - _currentLineTransform = D2D1::Matrix3x2F::Identity(); - _currentLineRendition = LineRendition::SingleWidth; - return S_OK; -} - -// Routine Description -// - Applies an appropriate transform for the given line rendition and viewport offset. -// Arguments: -// - lineRendition - The line rendition specifying the scaling of the line. -// - targetRow - The row on which the line is expected to be rendered. -// - viewportLeft - The left offset of the current viewport. -// Return Value: -// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error. -[[nodiscard]] HRESULT DxEngine::PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept -{ - auto lineTransform = D2D1::Matrix3x2F{ 0, 0, 0, 0, 0, 0 }; - const auto fontSize = _fontRenderData->GlyphCell(); - // The X delta is to account for the horizontal viewport offset. - lineTransform.dx = viewportLeft ? -1.0f * viewportLeft * fontSize.width : 0.0f; - switch (lineRendition) - { - case LineRendition::SingleWidth: - lineTransform.m11 = 1; // single width - lineTransform.m22 = 1; // single height - break; - case LineRendition::DoubleWidth: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 1; // single height - break; - case LineRendition::DoubleHeightTop: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - lineTransform.dy = -1.0f * targetRow * fontSize.height; - break; - case LineRendition::DoubleHeightBottom: - lineTransform.m11 = 2; // double width - lineTransform.m22 = 2; // double height - // The Y delta is to negate the offset caused by the scaled height. - // An extra row is added because we need the bottom half of the line. - lineTransform.dy = -1.0f * (targetRow + 1) * fontSize.height; - break; - } - // Return early if the new matrix is the same as the current transform. - RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform); - // Set the active transform with the new matrix. - _drawingContext->renderTarget->SetTransform(lineTransform); - // If the line rendition is double height, we need to adjust the top or bottom - // of the clipping rect to clip half the height of the rendered characters. - const auto halfHeight = _drawingContext->cellSize.height / 2; - _drawingContext->topClipOffset = lineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0; - _drawingContext->bottomClipOffset = lineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0; - // Save the current state. - _currentLineTransform = lineTransform; - _currentLineRendition = lineRendition; - return S_OK; -} diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp deleted file mode 100644 index 990c77e18e5..00000000000 --- a/src/renderer/dx/DxRenderer.hpp +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../../renderer/inc/RenderEngineBase.hpp" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "CustomTextLayout.h" -#include "CustomTextRenderer.h" -#include "DxFontRenderData.h" -#include "DxSoftFont.h" - -#include "../../types/inc/Viewport.hpp" - -#include - -TRACELOGGING_DECLARE_PROVIDER(g_hDxRenderProvider); - -namespace Microsoft::Console::Render -{ - class DxEngine final : public RenderEngineBase - { - public: - DxEngine(); - ~DxEngine(); - DxEngine(const DxEngine&) = default; - DxEngine(DxEngine&&) = default; - DxEngine& operator=(const DxEngine&) = default; - DxEngine& operator=(DxEngine&&) = default; - - // Used to release device resources so that another instance of - // conhost can render to the screen (i.e. only one DirectX - // application may control the screen at a time.) - [[nodiscard]] HRESULT Enable() noexcept override; - [[nodiscard]] HRESULT Disable() noexcept; - - [[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept override; - - [[nodiscard]] HRESULT SetWindowSize(const til::size pixels) noexcept override; - - void SetCallback(std::function pfn) noexcept override; - void SetWarningCallback(std::function pfn) noexcept override; - - bool GetRetroTerminalEffect() const noexcept override; - void SetRetroTerminalEffect(bool enable) noexcept override; - - std::wstring_view GetPixelShaderPath() noexcept override; - void SetPixelShaderPath(std::wstring_view value) noexcept override; - - void SetForceFullRepaintRendering(bool enable) noexcept override; - - void SetSoftwareRendering(bool enable) noexcept override; - - // IRenderEngine Members - [[nodiscard]] HRESULT Invalidate(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateCursor(const til::rect* const psrRegion) noexcept override; - [[nodiscard]] HRESULT InvalidateSystem(const til::rect* const prcDirtyClient) noexcept override; - [[nodiscard]] HRESULT InvalidateSelection(const std::vector& rectangles) noexcept override; - [[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override; - [[nodiscard]] HRESULT InvalidateAll() noexcept override; - [[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override; - - [[nodiscard]] HRESULT StartPaint() noexcept override; - [[nodiscard]] HRESULT EndPaint() noexcept override; - - [[nodiscard]] bool RequiresContinuousRedraw() noexcept override; - - void WaitUntilCanRender() noexcept override; - [[nodiscard]] HRESULT Present() noexcept override; - - [[nodiscard]] HRESULT ScrollFrame() noexcept override; - - [[nodiscard]] HRESULT UpdateSoftFont(const std::span bitPattern, - const til::size cellSize, - const size_t centeringHint) noexcept override; - - [[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override; - - [[nodiscard]] HRESULT ResetLineTransform() noexcept override; - [[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, - const til::CoordType targetRow, - const til::CoordType viewportLeft) noexcept override; - - [[nodiscard]] HRESULT PaintBackground() noexcept override; - [[nodiscard]] HRESULT PaintBufferLine(const std::span clusters, - const til::point coord, - const bool fTrimLeft, - const bool lineWrapped) noexcept override; - - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override; - [[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override; - [[nodiscard]] HRESULT PaintSelections(const std::vector& rect) noexcept override; - - [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; - - [[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, - const RenderSettings& renderSettings, - const gsl::not_null pData, - const bool usingSoftFont, - const bool isSettingDefaultBrushes) noexcept override; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept override; - [[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override; - [[nodiscard]] HRESULT UpdateViewport(const til::inclusive_rect& srNewViewport) noexcept override; - - [[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const int iDpi) noexcept override; - - [[nodiscard]] HRESULT GetDirtyArea(std::span& area) noexcept override; - - [[nodiscard]] HRESULT GetFontSize(_Out_ til::size* pFontSize) noexcept override; - [[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override; - - [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInCharacters(const ::Microsoft::Console::Types::Viewport& viewInPixels) const noexcept override; - [[nodiscard]] ::Microsoft::Console::Types::Viewport GetViewportInPixels(const ::Microsoft::Console::Types::Viewport& viewInCharacters) const noexcept override; - - float GetScaling() const noexcept override; - - void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept override; - void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept override; - void EnableTransparentBackground(const bool isTransparent) noexcept override; - - void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept override; - - protected: - [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring_view newTitle) noexcept override; - [[nodiscard]] HRESULT _PaintTerminalEffects() noexcept; - [[nodiscard]] bool _FullRepaintNeeded() const noexcept; - - private: - enum class SwapChainMode - { - ForHwnd, - ForComposition - }; - - SwapChainMode _chainMode; - - HWND _hwndTarget; - til::size _sizeTarget; - int _dpi; - float _scale; - float _prevScale; - - std::function _pfn; - std::function _pfnWarningCallback; - - bool _isEnabled; - bool _isPainting; - - til::size _displaySizePixels; - - D2D1_COLOR_F _defaultForegroundColor; - D2D1_COLOR_F _defaultBackgroundColor; - - D2D1_COLOR_F _foregroundColor; - D2D1_COLOR_F _backgroundColor; - D2D1_COLOR_F _selectionBackground; - - LineRendition _currentLineRendition; - D2D1::Matrix3x2F _currentLineTransform; - - uint16_t _hyperlinkHoveredId; - - bool _firstFrame; - std::pmr::unsynchronized_pool_resource _pool; - til::pmr::bitmap _invalidMap; - til::point _invalidScroll; - bool _allInvalid; - - bool _presentReady; - std::vector _presentDirty; - RECT _presentScroll; - POINT _presentOffset; - DXGI_PRESENT_PARAMETERS _presentParams; - - static std::atomic _tracelogCount; - - wil::unique_handle _swapChainHandle; - - // Device-Independent Resources - ::Microsoft::WRL::ComPtr _d2dFactory; - - ::Microsoft::WRL::ComPtr _dwriteFactory; - ::Microsoft::WRL::ComPtr _customLayout; - ::Microsoft::WRL::ComPtr _customRenderer; - ::Microsoft::WRL::ComPtr _strokeStyle; - ::Microsoft::WRL::ComPtr _dashStrokeStyle; - - std::unique_ptr _fontRenderData; - DxSoftFont _softFont; - bool _usingSoftFont; - - D2D1_STROKE_STYLE_PROPERTIES _strokeStyleProperties; - D2D1_STROKE_STYLE_PROPERTIES _dashStrokeStyleProperties; - - // Device-Dependent Resources - bool _recreateDeviceRequested; - bool _haveDeviceResources; - ::Microsoft::WRL::ComPtr _d3dDevice; - ::Microsoft::WRL::ComPtr _d3dDeviceContext; - - ::Microsoft::WRL::ComPtr _d2dDevice; - ::Microsoft::WRL::ComPtr _d2dDeviceContext; - ::Microsoft::WRL::ComPtr _d2dBitmap; - ::Microsoft::WRL::ComPtr _d2dBrushForeground; - ::Microsoft::WRL::ComPtr _d2dBrushBackground; - - ::Microsoft::WRL::ComPtr _dxgiFactory2; - ::Microsoft::WRL::ComPtr _dxgiFactoryMedia; - ::Microsoft::WRL::ComPtr _dxgiDevice; - ::Microsoft::WRL::ComPtr _dxgiSurface; - - DXGI_SWAP_CHAIN_DESC1 _swapChainDesc; - ::Microsoft::WRL::ComPtr _dxgiSwapChain; - wil::unique_handle _swapChainFrameLatencyWaitableObject; - std::unique_ptr _drawingContext; - - // Terminal effects resources. - - // Controls if configured terminal effects are enabled - bool _terminalEffectsEnabled; - - // Experimental and deprecated retro terminal effect - // Preserved for backwards compatibility - // Implemented in terms of the more generic pixel shader effect - // Has precedence over pixel shader effect - bool _retroTerminalEffect; - - // Experimental and pixel shader effect - // Allows user to load a pixel shader from a few presets or from a file path - std::wstring _pixelShaderPath; - bool _pixelShaderLoaded{ false }; - - std::chrono::steady_clock::time_point _shaderStartTime; - - // DX resources needed for terminal effects - ::Microsoft::WRL::ComPtr _renderTargetView; - ::Microsoft::WRL::ComPtr _vertexShader; - ::Microsoft::WRL::ComPtr _pixelShader; - ::Microsoft::WRL::ComPtr _vertexLayout; - ::Microsoft::WRL::ComPtr _screenQuadVertexBuffer; - ::Microsoft::WRL::ComPtr _pixelShaderSettingsBuffer; - ::Microsoft::WRL::ComPtr _samplerState; - ::Microsoft::WRL::ComPtr _framebufferCapture; - - // Preferences and overrides - bool _softwareRendering; - bool _forceFullRepaintRendering; - - D2D1_TEXT_ANTIALIAS_MODE _antialiasingMode; - - bool _defaultBackgroundIsTransparent; - - // DirectX constant buffers need to be a multiple of 16; align to pad the size. - __declspec(align(16)) struct - { - // Note: This can be seen as API endpoint towards user provided pixel shaders. - // Changes here can break existing pixel shaders so be careful with changing datatypes - // and order of parameters - float Time; - float Scale; - DirectX::XMFLOAT2 Resolution; - DirectX::XMFLOAT4 Background; -#pragma warning(suppress : 4324) // structure was padded due to __declspec(align()) - } _pixelShaderSettings; - - [[nodiscard]] HRESULT _CreateDeviceResources(const bool createSwapChain) noexcept; - [[nodiscard]] HRESULT _CreateSurfaceHandle() noexcept; - - bool _HasTerminalEffects() const noexcept; - std::string _LoadPixelShaderFile() const; - HRESULT _SetupTerminalEffects(); - void _ComputePixelShaderSettings() noexcept; - - [[nodiscard]] HRESULT _PrepareRenderTarget() noexcept; - - void _ReleaseDeviceResources() noexcept; - - bool _ShouldForceGrayscaleAA() noexcept; - - [[nodiscard]] HRESULT _CreateTextLayout( - _In_reads_(StringLength) PCWCHAR String, - _In_ size_t StringLength, - _Out_ IDWriteTextLayout** ppTextLayout) noexcept; - - [[nodiscard]] HRESULT _CopyFrontToBack() noexcept; - - [[nodiscard]] HRESULT _EnableDisplayAccess(const bool outputEnabled) noexcept; - - [[nodiscard]] til::size _GetClientSize() const; - - void _InvalidateRectangle(const til::rect& rc); - bool _IsAllInvalid() const noexcept; - - [[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept; - - // Routine Description: - // - Helps convert a Direct2D ColorF into a DXGI RGBA - // Arguments: - // - color - Direct2D Color F - // Return Value: - // - DXGI RGBA - [[nodiscard]] constexpr DXGI_RGBA s_RgbaFromColorF(const D2D1_COLOR_F color) noexcept - { - return { color.r, color.g, color.b, color.a }; - } - }; -} diff --git a/src/renderer/dx/DxSoftFont.cpp b/src/renderer/dx/DxSoftFont.cpp deleted file mode 100644 index 2bfa932d878..00000000000 --- a/src/renderer/dx/DxSoftFont.cpp +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "DxSoftFont.h" -#include "CustomTextRenderer.h" - -#include - -#pragma comment(lib, "dxguid.lib") - -using namespace Microsoft::Console::Render; -using Microsoft::WRL::ComPtr; - -// The soft font is rendered into a bitmap laid out in a 12x8 grid, which is -// enough space for the 96 characters expected in the font, and which minimizes -// the dimensions for a typical 2:1 cell size. Each position in the grid is -// surrounded by a 2 pixel border which helps avoid bleed across the character -// boundaries when the output is scaled. - -constexpr size_t BITMAP_GRID_WIDTH = 12; -constexpr size_t BITMAP_GRID_HEIGHT = 8; -constexpr size_t PADDING = 2; - -void DxSoftFont::SetFont(const std::span bitPattern, - const til::size sourceSize, - const til::size targetSize, - const size_t centeringHint) -{ - Reset(); - - // If the font is being reset, just free up the memory and return. - if (bitPattern.empty()) - { - _bitmapBits.clear(); - return; - } - - const auto maxGlyphCount = BITMAP_GRID_WIDTH * BITMAP_GRID_HEIGHT; - _glyphCount = std::min(bitPattern.size() / sourceSize.height, maxGlyphCount); - _sourceSize = sourceSize; - _targetSize = targetSize; - _centeringHint = centeringHint; - - const auto bitmapWidth = BITMAP_GRID_WIDTH * (_sourceSize.width + PADDING * 2); - const auto bitmapHeight = BITMAP_GRID_HEIGHT * (_sourceSize.height + PADDING * 2); - _bitmapBits = std::vector(bitmapWidth * bitmapHeight); - _bitmapSize = { gsl::narrow_cast(bitmapWidth), gsl::narrow_cast(bitmapHeight) }; - - const auto bitmapScanline = [=](const auto lineNumber) noexcept { - return _bitmapBits.begin() + lineNumber * bitmapWidth; - }; - - // The source bitPattern is just a list of the scanlines making up the - // glyphs one after the other, but we want to lay them out in a grid, so - // we need to process each glyph individually. - auto srcPointer = bitPattern.begin(); - for (auto glyphNumber = 0u; glyphNumber < _glyphCount; glyphNumber++) - { - // We start by calculating the position in the bitmap where the glyph - // needs to be stored. - const auto xOffset = _xOffsetForGlyph(glyphNumber); - const auto yOffset = _yOffsetForGlyph(glyphNumber); - auto dstPointer = bitmapScanline(yOffset) + xOffset; - for (auto y = 0; y < sourceSize.height; y++) - { - // Then for each scanline in the source, we need to expand the bits - // into 8-bit values. For every bit that is set we write out an FF - // value, and if not set, we write out 00. In the end, all we care - // about is a single red component for the R8_UNORM bitmap format, - // since we'll later remap that to RGBA with a color matrix. - auto srcBits = *(srcPointer++); - for (auto x = 0; x < sourceSize.width; x++) - { - const auto srcBitIsSet = (srcBits & 0x8000) != 0; - *(dstPointer++) = srcBitIsSet ? 0xFF : 0x00; - srcBits <<= 1; - } - // When glyphs in this bitmap are output, they will typically need - // to scaled, and this can result in some bleed from the surrounding - // pixels. So to keep the borders clean, we pad the areas to the left - // and right by repeating the first and last pixels of each scanline. - std::fill_n(dstPointer, PADDING, til::at(dstPointer, -1)); - dstPointer -= sourceSize.width; - std::fill_n(dstPointer - PADDING, PADDING, til::at(dstPointer, 0)); - dstPointer += bitmapWidth; - } - } - - // In the same way that we padded the left and right of each glyph in the - // code above, we also need to pad the top and bottom. But in this case we - // can simply do a whole row of glyphs from the grid at the same time. - for (auto gridRow = 0u; gridRow < BITMAP_GRID_HEIGHT; gridRow++) - { - const auto rowOffset = _yOffsetForGlyph(gridRow); - const auto rowTop = bitmapScanline(rowOffset); - const auto rowBottom = bitmapScanline(rowOffset + _sourceSize.height - 1); - for (auto i = 1; i <= PADDING; i++) - { - std::copy_n(rowTop, bitmapWidth, rowTop - i * bitmapWidth); - std::copy_n(rowBottom, bitmapWidth, rowBottom + i * bitmapWidth); - } - } -} - -HRESULT DxSoftFont::SetTargetSize(const til::size targetSize) -{ - _targetSize = targetSize; - return _scaleEffect ? _scaleEffect->SetValue(D2D1_SCALE_PROP_SCALE, _scaleForTargetSize()) : S_OK; -} - -HRESULT DxSoftFont::SetAntialiasing(const bool antialiased) -{ - _interpolation = (antialiased ? ANTIALIASED_INTERPOLATION : ALIASED_INTERPOLATION); - return _scaleEffect ? _scaleEffect->SetValue(D2D1_SCALE_PROP_INTERPOLATION_MODE, _interpolation) : S_OK; -} - -HRESULT DxSoftFont::SetColor(const D2D1_COLOR_F& color) -{ - // Since our source image is monochrome, we don't care about the - // individual color components. We just multiply the red component - // by the active color value to get the output color. And note that - // the alpha matrix entry is already set to 1 in the constructor, - // so we don't need to keep updating it here. - _colorMatrix.m[0][0] = color.r; - _colorMatrix.m[0][1] = color.g; - _colorMatrix.m[0][2] = color.b; - return _colorEffect ? _colorEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, _colorMatrix) : S_OK; -} - -HRESULT DxSoftFont::Draw(const DrawingContext& drawingContext, - const std::span clusters, - const float originX, - const float originY) -{ - ComPtr d2dContext; - RETURN_IF_FAILED(drawingContext.renderTarget->QueryInterface(d2dContext.GetAddressOf())); - - // We start by creating a clipping rectangle for the region we're going to - // draw, and this is initially filled with the active background color. - D2D1_RECT_F rect; - rect.top = originY + drawingContext.topClipOffset; - rect.bottom = originY + _targetSize.height - drawingContext.bottomClipOffset; - rect.left = originX; - rect.right = originX + _targetSize.width * clusters.size(); - d2dContext->FillRectangle(rect, drawingContext.backgroundBrush); - d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED); - auto resetClippingRect = wil::scope_exit([&]() noexcept { d2dContext->PopAxisAlignedClip(); }); - - // The bitmap and associated scaling/coloring effects are created on demand - // so we need make sure they're generated now. - RETURN_IF_FAILED(_createResources(d2dContext.Get())); - - // We use the CustomTextRenderer to draw the first pass of the cursor. - RETURN_IF_FAILED(CustomTextRenderer::DrawCursor(d2dContext.Get(), rect, drawingContext, true)); - - // Then we draw the associated glyph for each entry in the cluster list. - auto targetPoint = D2D1_POINT_2F{ originX, originY }; - for (auto& cluster : clusters) - { - // For DRCS, we only care about the character's lower 7 bits, then - // codepoint 0x20 will be the first glyph in the set. - const auto glyphNumber = (cluster.GetTextAsSingle() & 0x7f) - 0x20; - const auto x = _xOffsetForGlyph(glyphNumber); - const auto y = _yOffsetForGlyph(glyphNumber); - const auto sourceRect = D2D1_RECT_F{ x, y, x + _targetSize.width, y + _targetSize.height }; - LOG_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_CENTER_POINT, D2D1::Point2F(x, y))); - d2dContext->DrawImage(_colorEffect.Get(), targetPoint, sourceRect); - targetPoint.x += _targetSize.width; - } - - // We finish by the drawing the second pass of the cursor. - return CustomTextRenderer::DrawCursor(d2dContext.Get(), rect, drawingContext, false); -} - -void DxSoftFont::Reset() -{ - _colorEffect.Reset(); - _scaleEffect.Reset(); - _bitmap.Reset(); -} - -HRESULT DxSoftFont::_createResources(gsl::not_null d2dContext) -{ - if (!_bitmap) - { - D2D1_BITMAP_PROPERTIES bitmapProperties{}; - bitmapProperties.pixelFormat.format = DXGI_FORMAT_R8_UNORM; - bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; - const auto bitmapPitch = gsl::narrow_cast(_bitmapSize.width); - RETURN_IF_FAILED(d2dContext->CreateBitmap(_bitmapSize, _bitmapBits.data(), bitmapPitch, bitmapProperties, _bitmap.GetAddressOf())); - } - - if (!_scaleEffect) - { - RETURN_IF_FAILED(d2dContext->CreateEffect(CLSID_D2D1Scale, _scaleEffect.GetAddressOf())); - RETURN_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_INTERPOLATION_MODE, _interpolation)); - RETURN_IF_FAILED(_scaleEffect->SetValue(D2D1_SCALE_PROP_SCALE, _scaleForTargetSize())); - _scaleEffect->SetInput(0, _bitmap.Get()); - - if (_colorEffect) - { - _colorEffect->SetInputEffect(0, _scaleEffect.Get()); - } - } - - if (!_colorEffect) - { - RETURN_IF_FAILED(d2dContext->CreateEffect(CLSID_D2D1ColorMatrix, _colorEffect.GetAddressOf())); - RETURN_IF_FAILED(_colorEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, _colorMatrix)); - _colorEffect->SetInputEffect(0, _scaleEffect.Get()); - } - - return S_OK; -} - -D2D1_VECTOR_2F DxSoftFont::_scaleForTargetSize() const noexcept -{ - // If the text in the font is not perfectly centered, the _centeringHint - // gives us the offset needed to correct that misalignment. So to ensure - // the scaling is evenly balanced around the center point of the glyphs, - // we can use that hint to adjust the dimensions of our source and target - // widths when calculating the horizontal scale. - const auto targetCenteringHint = std::lround((float)_centeringHint * _targetSize.width / _sourceSize.width); - const auto xScale = gsl::narrow_cast(_targetSize.width - targetCenteringHint) / (_sourceSize.width - _centeringHint); - const auto yScale = gsl::narrow_cast(_targetSize.height) / _sourceSize.height; - return D2D1::Vector2F(xScale, yScale); -} - -template -T DxSoftFont::_xOffsetForGlyph(const size_t glyphNumber) const noexcept -{ - const auto xOffsetInGrid = glyphNumber / BITMAP_GRID_HEIGHT; - const auto paddedGlyphWidth = _sourceSize.width + PADDING * 2; - return gsl::narrow_cast(xOffsetInGrid * paddedGlyphWidth + PADDING); -} - -template -T DxSoftFont::_yOffsetForGlyph(const size_t glyphNumber) const noexcept -{ - const auto yOffsetInGrid = glyphNumber % BITMAP_GRID_HEIGHT; - const auto paddedGlyphHeight = _sourceSize.height + PADDING * 2; - return gsl::narrow_cast(yOffsetInGrid * paddedGlyphHeight + PADDING); -} diff --git a/src/renderer/dx/DxSoftFont.h b/src/renderer/dx/DxSoftFont.h deleted file mode 100644 index 5dd83b3f4d2..00000000000 --- a/src/renderer/dx/DxSoftFont.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "../inc/Cluster.hpp" - -#include -#include -#include -#include - -namespace Microsoft::Console::Render -{ - struct DrawingContext; - - class DxSoftFont - { - public: - void SetFont(const std::span bitPattern, - const til::size sourceSize, - const til::size targetSize, - const size_t centeringHint); - HRESULT SetTargetSize(const til::size targetSize); - HRESULT SetAntialiasing(const bool antialiased); - HRESULT SetColor(const D2D1_COLOR_F& color); - HRESULT Draw(const DrawingContext& drawingContext, - const std::span clusters, - const float originX, - const float originY); - void Reset(); - - private: - static constexpr auto ANTIALIASED_INTERPOLATION = D2D1_SCALE_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; - static constexpr auto ALIASED_INTERPOLATION = D2D1_SCALE_INTERPOLATION_MODE_NEAREST_NEIGHBOR; - - HRESULT _createResources(gsl::not_null d2dContext); - D2D1_VECTOR_2F _scaleForTargetSize() const noexcept; - template - T _xOffsetForGlyph(const size_t glyphNumber) const noexcept; - template - T _yOffsetForGlyph(const size_t glyphNumber) const noexcept; - - size_t _glyphCount = 0; - til::size _sourceSize; - til::size _targetSize; - size_t _centeringHint = 0; - D2D1_SCALE_INTERPOLATION_MODE _interpolation = ALIASED_INTERPOLATION; - D2D1_MATRIX_5X4_F _colorMatrix{ ._14 = 1 }; - D2D1_SIZE_U _bitmapSize{}; - std::vector _bitmapBits; - ::Microsoft::WRL::ComPtr _bitmap; - ::Microsoft::WRL::ComPtr _scaleEffect; - ::Microsoft::WRL::ComPtr _colorEffect; - }; -} diff --git a/src/renderer/dx/IBoxDrawingEffect.idl b/src/renderer/dx/IBoxDrawingEffect.idl deleted file mode 100644 index 14443a05442..00000000000 --- a/src/renderer/dx/IBoxDrawingEffect.idl +++ /dev/null @@ -1,20 +0,0 @@ -import "oaidl.idl"; -import "ocidl.idl"; - -typedef struct BoxScale -{ - float VerticalScale; - float VerticalTranslation; - float HorizontalScale; - float HorizontalTranslation; -} BoxScale; - -[ - uuid("C164926F-1A4D-470D-BB8A-3D2CC4B035E4"), - object, - local -] -interface IBoxDrawingEffect : IUnknown -{ - HRESULT GetScale([out] BoxScale* scale); -}; diff --git a/src/renderer/dx/ScreenPixelShader.h b/src/renderer/dx/ScreenPixelShader.h deleted file mode 100644 index fa532205740..00000000000 --- a/src/renderer/dx/ScreenPixelShader.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED -constexpr std::string_view retroPixelShaderString{ "" }; -#else -constexpr std::string_view retroPixelShaderString{ R"( -// The original retro pixel shader -Texture2D shaderTexture; -SamplerState samplerState; - -cbuffer PixelShaderSettings { - float Time; - float Scale; - float2 Resolution; - float4 Background; -}; - -#define SCANLINE_FACTOR 0.5 -#define SCALED_SCANLINE_PERIOD Scale -#define SCALED_GAUSSIAN_SIGMA (2.0*Scale) - -static const float M_PI = 3.14159265f; - -float Gaussian2D(float x, float y, float sigma) -{ - return 1/(sigma*sqrt(2*M_PI)) * exp(-0.5*(x*x + y*y)/sigma/sigma); -} - -float4 Blur(Texture2D input, float2 tex_coord, float sigma) -{ - uint width, height; - shaderTexture.GetDimensions(width, height); - - float texelWidth = 1.0f/width; - float texelHeight = 1.0f/height; - - float4 color = { 0, 0, 0, 0 }; - - int sampleCount = 13; - - for (int x = 0; x < sampleCount; x++) - { - float2 samplePos = { 0, 0 }; - - samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth; - for (int y = 0; y < sampleCount; y++) - { - samplePos.y = tex_coord.y + (y - sampleCount/2) * texelHeight; - if (samplePos.x <= 0 || samplePos.y <= 0 || samplePos.x >= width || samplePos.y >= height) continue; - - color += input.Sample(samplerState, samplePos) * Gaussian2D((x - sampleCount/2), (y - sampleCount/2), sigma); - } - } - - return color; -} - -float SquareWave(float y) -{ - return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR; -} - -float4 Scanline(float4 color, float4 pos) -{ - float wave = SquareWave(pos.y); - - // TODO:GH#3929 make this configurable. - // Remove the && false to draw scanlines everywhere. - if (length(color.rgb) < 0.2 && false) - { - return color + wave*0.1; - } - else - { - return color * wave; - } -} - -float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET -{ - Texture2D input = shaderTexture; - - // TODO:GH#3930 Make these configurable in some way. - float4 color = input.Sample(samplerState, tex); - color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3; - color = Scanline(color, pos); - - return color; -} -)" }; -#endif diff --git a/src/renderer/dx/ScreenVertexShader.h b/src/renderer/dx/ScreenVertexShader.h deleted file mode 100644 index c0a4c8eaf6a..00000000000 --- a/src/renderer/dx/ScreenVertexShader.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#if !TIL_FEATURE_DXENGINESHADERSUPPORT_ENABLED -const char screenVertexShaderString[] = ""; -#else -const char screenVertexShaderString[] = R"( -struct VS_OUTPUT -{ - float4 pos : SV_POSITION; - float2 tex : TEXCOORD; -}; -VS_OUTPUT main(float4 pos : POSITION, float2 tex : TEXCOORD) -{ - VS_OUTPUT output; - output.pos = pos; - output.tex = tex; - return output; -} -)"; -#endif diff --git a/src/renderer/dx/dirs b/src/renderer/dx/dirs deleted file mode 100644 index 3cff781c8ae..00000000000 --- a/src/renderer/dx/dirs +++ /dev/null @@ -1,2 +0,0 @@ -DIRS= \ - lib \ diff --git a/src/renderer/dx/lib/dx.vcxproj b/src/renderer/dx/lib/dx.vcxproj deleted file mode 100644 index facf5a3cb5c..00000000000 --- a/src/renderer/dx/lib/dx.vcxproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - {48D21369-3D7B-4431-9967-24E81292CF62} - Win32Proj - dx - RendererDx - ConRenderDx - StaticLibrary - - - - $(SolutionDir)src\renderer\dx\ - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/dx/lib/dx.vcxproj.filters b/src/renderer/dx/lib/dx.vcxproj.filters deleted file mode 100644 index db30c6d1ae2..00000000000 --- a/src/renderer/dx/lib/dx.vcxproj.filters +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/dx/lib/sources b/src/renderer/dx/lib/sources deleted file mode 100644 index 7b2df6658ed..00000000000 --- a/src/renderer/dx/lib/sources +++ /dev/null @@ -1,8 +0,0 @@ -!include ..\sources.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = ConRenderDx -TARGETTYPE = LIBRARY diff --git a/src/renderer/dx/precomp.cpp b/src/renderer/dx/precomp.cpp deleted file mode 100644 index c51e9b31b2f..00000000000 --- a/src/renderer/dx/precomp.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" diff --git a/src/renderer/dx/precomp.h b/src/renderer/dx/precomp.h deleted file mode 100644 index 9a09c25bb2c..00000000000 --- a/src/renderer/dx/precomp.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -// This includes support libraries from the CRT, STL, WIL, and GSL -#define BLOCK_TIL // We want to include it later, after DX. -#include "LibraryIncludes.h" - -#include -#include - -#include "../host/conddkrefs.h" -#include - -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Re-include TIL at the bottom to gain DX superpowers. -#include "til.h" - -#pragma hdrstop diff --git a/src/renderer/dx/sources.inc b/src/renderer/dx/sources.inc deleted file mode 100644 index 35b2fbc7035..00000000000 --- a/src/renderer/dx/sources.inc +++ /dev/null @@ -1,42 +0,0 @@ -!include ..\..\..\project.inc - -# ------------------------------------- -# Windows Console -# - Console Renderer for DirectX -# ------------------------------------- - -# This module provides a rendering engine implementation that -# draws to a DirectX surface. - -# ------------------------------------- -# CRT Configuration -# ------------------------------------- - -BUILD_FOR_CORESYSTEM = 1 - -# ------------------------------------- -# Sources, Headers, and Libraries -# ------------------------------------- - -PRECOMPILED_CXX = 1 -PRECOMPILED_INCLUDE = ..\precomp.h - -INCLUDES = \ - $(INCLUDES); \ - ..; \ - ..\..\inc; \ - ..\..\..\inc; \ - ..\..\..\host; \ - $(MINWIN_INTERNAL_PRIV_SDK_INC_PATH_L); \ - $(MINWIN_RESTRICTED_PRIV_SDK_INC_PATH_L); \ - -SOURCES = \ - $(SOURCES) \ - ..\DxRenderer.cpp \ - ..\DxFontInfo.cpp \ - ..\DxFontRenderData.cpp \ - ..\DxSoftFont.cpp \ - ..\CustomTextRenderer.cpp \ - ..\CustomTextLayout.cpp \ - -C_DEFINES=$(C_DEFINES) -D__INSIDE_WINDOWS diff --git a/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp b/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp deleted file mode 100644 index 782e105f7ed..00000000000 --- a/src/renderer/dx/ut_dx/CustomTextLayoutTests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" - -#include "../CustomTextLayout.h" - -using namespace WEX::Common; -using namespace WEX::Logging; -using namespace WEX::TestExecution; - -using namespace Microsoft::Console::Render; - -class Microsoft::Console::Render::CustomTextLayoutTests -{ - TEST_CLASS(CustomTextLayoutTests); - - TEST_METHOD(OrderRuns) - { - CustomTextLayout layout; - - // Create linked list runs where a --> c --> b - CustomTextLayout::LinkedRun a; - a.nextRunIndex = 2; - a.textStart = 0; - CustomTextLayout::LinkedRun b; - b.nextRunIndex = 0; - b.textStart = 20; - CustomTextLayout::LinkedRun c; - c.nextRunIndex = 1; - c.textStart = 10; - - // but insert them into the runs as a, b, c - layout._runs.push_back(a); - layout._runs.push_back(b); - layout._runs.push_back(c); - - // Now order them. - layout._OrderRuns(); - - // Validate that they've been reordered to a, c, b by index so they can be iterated to go in order. - - // The text starts should be in order 0, 10, 20. - // The next run indexes should point at each other. - VERIFY_ARE_EQUAL(a.textStart, layout._runs.at(0).textStart); - VERIFY_ARE_EQUAL(1u, layout._runs.at(0).nextRunIndex); - VERIFY_ARE_EQUAL(c.textStart, layout._runs.at(1).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).nextRunIndex); - VERIFY_ARE_EQUAL(b.textStart, layout._runs.at(2).textStart); - VERIFY_ARE_EQUAL(0u, layout._runs.at(2).nextRunIndex); - } - - TEST_METHOD(SplitCurrentRunIncludingGlyphs) - { - CustomTextLayout layout; - - // Put glyph data into the layout as if we've already gone through analysis. - // This data matches the verbose comment from the CustomTextLayout.cpp file - // and is derived from - // https://social.msdn.microsoft.com/Forums/en-US/993365bc-8689-45ff-a675-c5ed0c011788/dwriteglyphrundescriptionclustermap-explained - - layout._text = L"fiñe"; - - layout._glyphIndices.push_back(19); - layout._glyphIndices.push_back(81); - layout._glyphIndices.push_back(23); - layout._glyphIndices.push_back(72); - - layout._glyphClusters.push_back(0); - layout._glyphClusters.push_back(0); - layout._glyphClusters.push_back(1); - layout._glyphClusters.push_back(3); - - // Set up the layout to have a run that already has glyph data inside of it. - CustomTextLayout::LinkedRun run; - run.textStart = 0; - run.textLength = 4; - run.glyphStart = 0; - run.glyphCount = 4; - - layout._runs.push_back(run); - - // Now split it in the middle per the comment example - layout._SetCurrentRun(2); - layout._SplitCurrentRun(2); - - // And validate that the split state matches what we expected. - VERIFY_ARE_EQUAL(0u, layout._runs.at(0).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(0).textLength); - VERIFY_ARE_EQUAL(0u, layout._runs.at(0).glyphStart); - VERIFY_ARE_EQUAL(1u, layout._runs.at(0).glyphCount); - - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textStart); - VERIFY_ARE_EQUAL(2u, layout._runs.at(1).textLength); - VERIFY_ARE_EQUAL(1u, layout._runs.at(1).glyphStart); - VERIFY_ARE_EQUAL(3u, layout._runs.at(1).glyphCount); - } -}; diff --git a/src/renderer/dx/ut_dx/DefaultResource.rc b/src/renderer/dx/ut_dx/DefaultResource.rc deleted file mode 100644 index 85ec2648d12..00000000000 --- a/src/renderer/dx/ut_dx/DefaultResource.rc +++ /dev/null @@ -1,12 +0,0 @@ -//Autogenerated file name + version resource file for Device Guard whitelisting effort - -#include -#include - -#define VER_FILETYPE VFT_UNKNOWN -#define VER_FILESUBTYPE VFT2_UNKNOWN -#define VER_FILEDESCRIPTION_STR ___TARGETNAME -#define VER_INTERNALNAME_STR ___TARGETNAME -#define VER_ORIGINALFILENAME_STR ___TARGETNAME - -#include "common.ver" diff --git a/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj b/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj deleted file mode 100644 index 5a46c150a7a..00000000000 --- a/src/renderer/dx/ut_dx/Dx.Unit.Tests.vcxproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - {95B136F9-B238-490C-A7C5-5843C1FECAC4} - Win32Proj - DxUnitTests - Dx.Unit.Tests - Dx.Unit.Tests - DynamicLibrary - - - - - - - Create - - - - - {18d09a24-8240-42d6-8cb6-236eee820263} - - - {af0a096a-8b3a-4949-81ef-7df8f0fee91f} - - - {48D21369-3D7B-4431-9967-24E81292CF62} - - - - - - - - ..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories) - - - - - - - diff --git a/src/renderer/dx/ut_dx/product.pbxproj b/src/renderer/dx/ut_dx/product.pbxproj deleted file mode 100644 index 9e5ef983069..00000000000 --- a/src/renderer/dx/ut_dx/product.pbxproj +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/renderer/dx/ut_dx/sources b/src/renderer/dx/ut_dx/sources deleted file mode 100644 index acea323c97c..00000000000 --- a/src/renderer/dx/ut_dx/sources +++ /dev/null @@ -1,36 +0,0 @@ -!include ..\..\..\project.unittest.inc - -# ------------------------------------- -# Program Information -# ------------------------------------- - -TARGETNAME = Microsoft.Console.Renderer.Dx.UnitTests -TARGETTYPE = DYNLINK -DLLDEF = - -# ------------------------------------- -# Sources, Headers, and Libraries -# ------------------------------------- - -SOURCES = \ - $(SOURCES) \ - CustomTextLayoutTests.cpp \ - DefaultResource.rc \ - -INCLUDES = \ - .. \ - $(INCLUDES) \ - -TARGETLIBS = \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\dx\lib\$(O)\ConRenderDx.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\renderer\base\lib\$(O)\ConRenderBase.lib \ - $(WINCORE_OBJ_PATH)\console\open\src\types\lib\$(O)\ConTypes.lib \ - $(TARGETLIBS) \ - -# ------------------------------------- -# Localization -# ------------------------------------- - -# Autogenerated. Sets file name for Device Guard whitelisting effort, used in RC.exe. -C_DEFINES = $(C_DEFINES) -D___TARGETNAME="""$(TARGETNAME).$(TARGETTYPE)""" -MUI_VERIFY_NO_LOC_RESOURCE = 1 From 9c8058c3260d39e37416154a181441839e0570f6 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Feb 2024 12:53:02 +0100 Subject: [PATCH 21/50] AtlasEngine: Improve dotted, dashed and curly underlines (#16719) This changeset makes 3 improvements: * Dotted lines now use a 2:1 ratio between gaps and dots (from 1:1). This makes the dots a lot easier to spot at small font sizes. * Dashed lines use a 1:2 ratio and a cells-size independent stride. By being cell-size independent it works more consistently with a wider variety of fonts with weird cell aspect ratios. * Curly lines are now cell-size independent as well and have a height that equals the double-underline size. This ensures that the curve isn't cut off anymore and just like with dashed lines, that it works under weird aspect ratios. Closes #16712 ## Validation Steps Performed This was tested using RenderingTests using Cascadia Mono, Consolas, Courier New, Lucida Console and MS Gothic. --- src/renderer/atlas/BackendD3D.cpp | 38 +++++++++++-------------------- src/renderer/atlas/BackendD3D.h | 7 +++--- src/renderer/atlas/shader_ps.hlsl | 25 ++++++++------------ 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 69d597ed0bd..221cf3b5ba3 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -310,29 +310,18 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) // baseline of curlyline is at the middle of singly underline. When there's // limited space to draw a curlyline, we apply a limit on the peak height. { - // initialize curlyline peak height to a desired value. Clamp it to at - // least 1. - constexpr auto curlyLinePeakHeightEm = 0.075f; - _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); - - // calc the limit we need to apply - const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; - - // if the limit is <= 0 (no height at all), stick with the desired height. - // This is how we force a curlyline even when there's no space, though it - // might be clipped at the bottom. - if (maxDrawableCurlyLinePeakHeight > 0.0f) - { - _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); - } + const auto cellHeight = static_cast(font.cellSize.y); + const auto strokeWidth = static_cast(font.thinLineWidth); + + // This gives it the same position and height as our double-underline. There's no particular reason for that, apart from + // it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc. + // We still need to ensure though that it doesn't clip out of the cellHeight at the bottom. + const auto height = std::max(3.0f, static_cast(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position)); + const auto top = std::min(static_cast(font.doubleUnderline[0].position), floorf(cellHeight - height - strokeWidth)); - const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; - const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); - _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + _curlyLineHalfHeight = height * 0.5f; + _curlyUnderline.position = gsl::narrow_cast(lrintf(top)); + _curlyUnderline.height = gsl::narrow_cast(lrintf(height)); } DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); @@ -573,9 +562,8 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; - data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLinePeakHeight = _curlyLinePeakHeight; - data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; + data.thinLineWidth = p.s->font->thinLineWidth; + data.curlyLineHalfHeight = _curlyLineHalfHeight; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 4c248ed3ff3..cdf9937cc61 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,9 +42,8 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; - alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; - alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; - alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; + alignas(sizeof(f32)) f32 thinLineWidth = 0; + alignas(sizeof(f32)) f32 curlyLineHalfHeight = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -291,7 +290,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLinePeakHeight = 0.0f; + f32 _curlyLineHalfHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index e19ba955fe5..d3c0bfac6c3 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,9 +12,8 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; - float curlyLinePeakHeight; - float curlyLineWaveFreq; - float curlyLineCellOffset; + float thinLineWidth; + float curlyLineHalfHeight; } Texture2D background : register(t0); @@ -76,31 +75,25 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (3.0f * underlineWidth * data.renditionScale.x)) < (1.0f / 3.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (6.0f * underlineWidth * data.renditionScale.x)) < (4.0f / 6.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_CURLY_LINE: { - uint cellRow = floor(data.position.y / backgroundCellSize.y); - // Use the previous cell when drawing 'Double Height' curly line. - cellRow -= data.renditionScale.y - 1; - const float cellTop = cellRow * backgroundCellSize.y; - const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; - const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; - const float amp = curlyLinePeakHeight * data.renditionScale.y; - const float freq = curlyLineWaveFreq / data.renditionScale.x; - - const float s = sin(data.position.x * freq); - const float d = abs(centerY - (s * amp) - data.position.y); + const float strokeWidthHalf = thinLineWidth * data.renditionScale.y * 0.5f; + const float amp = (curlyLineHalfHeight - strokeWidthHalf) * data.renditionScale.y; + const float freq = data.renditionScale.x / curlyLineHalfHeight * 1.57079632679489661923f; + const float s = sin(data.position.x * freq) * amp; + const float d = abs(curlyLineHalfHeight - data.texcoord.y - s); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa; From ec434e3fba2a6ef254123e31f5257c25b04f2547 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Feb 2024 13:31:31 +0100 Subject: [PATCH 22/50] Add an automated conhost benchmark tool (#16453) `ConsoleBench` is capable of launching conhost instances and measuring their performance characteristics. It writes these out as an HTML file with violin graphs (using plotly.js) for easy comparison. Currently, only a small number of tests have been added, but the code is structured in such a way that more tests can be added easily. --- .github/actions/spelling/excludes.txt | 1 + OpenConsole.sln | 23 + src/tools/ConsoleBench/ConsoleBench.vcxproj | 39 ++ .../ConsoleBench/ConsoleBench.vcxproj.filters | 52 ++ src/tools/ConsoleBench/arena.cpp | 268 +++++++++ src/tools/ConsoleBench/arena.h | 111 ++++ src/tools/ConsoleBench/conhost.cpp | 170 ++++++ src/tools/ConsoleBench/conhost.h | 20 + src/tools/ConsoleBench/main.cpp | 555 ++++++++++++++++++ src/tools/ConsoleBench/pch.cpp | 4 + src/tools/ConsoleBench/pch.h | 17 + src/tools/ConsoleBench/utils.cpp | 108 ++++ src/tools/ConsoleBench/utils.h | 47 ++ 13 files changed, 1415 insertions(+) create mode 100644 src/tools/ConsoleBench/ConsoleBench.vcxproj create mode 100644 src/tools/ConsoleBench/ConsoleBench.vcxproj.filters create mode 100644 src/tools/ConsoleBench/arena.cpp create mode 100644 src/tools/ConsoleBench/arena.h create mode 100644 src/tools/ConsoleBench/conhost.cpp create mode 100644 src/tools/ConsoleBench/conhost.h create mode 100644 src/tools/ConsoleBench/main.cpp create mode 100644 src/tools/ConsoleBench/pch.cpp create mode 100644 src/tools/ConsoleBench/pch.h create mode 100644 src/tools/ConsoleBench/utils.cpp create mode 100644 src/tools/ConsoleBench/utils.h diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 57d475dc876..dc0f987c299 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -115,6 +115,7 @@ ^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ ^src/tools/benchcat +^src/tools/ConsoleBench ^src/tools/integrity/dirs$ ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ ^src/tools/RenderingTests/main\.cpp$ diff --git a/OpenConsole.sln b/OpenConsole.sln index 1717f4cc8a5..e699c77cece 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -417,6 +417,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchcat", "src\tools\bench EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleMonitor", "src\tools\ConsoleMonitor\ConsoleMonitor.vcxproj", "{328729E9-6723-416E-9C98-951F1473BBE1}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleBench", "src\tools\ConsoleBench\ConsoleBench.vcxproj", "{BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -2387,6 +2389,26 @@ Global {328729E9-6723-416E-9C98-951F1473BBE1}.Release|ARM64.ActiveCfg = Release|ARM64 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|x64.ActiveCfg = Release|x64 {328729E9-6723-416E-9C98-951F1473BBE1}.Release|x86.ActiveCfg = Release|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.AuditMode|ARM64.ActiveCfg = Debug|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.AuditMode|x64.ActiveCfg = Debug|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.AuditMode|x86.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|ARM64.Build.0 = Debug|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|x64.ActiveCfg = Debug|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|x64.Build.0 = Debug|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Debug|x86.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Fuzzing|Any CPU.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Fuzzing|x64.ActiveCfg = Debug|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Fuzzing|x86.ActiveCfg = Debug|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|Any CPU.ActiveCfg = Release|Win32 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|ARM64.ActiveCfg = Release|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|ARM64.Build.0 = Release|ARM64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|x64.ActiveCfg = Release|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|x64.Build.0 = Release|x64 + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48}.Release|x86.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2492,6 +2514,7 @@ Global {37C995E0-2349-4154-8E77-4A52C0C7F46D} = {A10C4720-DCA4-4640-9749-67F4314F527C} {2C836962-9543-4CE5-B834-D28E1F124B66} = {A10C4720-DCA4-4640-9749-67F4314F527C} {328729E9-6723-416E-9C98-951F1473BBE1} = {A10C4720-DCA4-4640-9749-67F4314F527C} + {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48} = {A10C4720-DCA4-4640-9749-67F4314F527C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj b/src/tools/ConsoleBench/ConsoleBench.vcxproj new file mode 100644 index 00000000000..1e66412cc6b --- /dev/null +++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj @@ -0,0 +1,39 @@ + + + + {be92101c-04f8-48da-99f0-e1f4f1d2dc48} + Win32Proj + ConsoleBench + ConsoleBench + ConsoleBench + Application + + + + + + + + + Create + + + + + + + + + + + + false + pch.h + + + Console + + + + + \ No newline at end of file diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters new file mode 100644 index 00000000000..25a667f9907 --- /dev/null +++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters @@ -0,0 +1,52 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/src/tools/ConsoleBench/arena.cpp b/src/tools/ConsoleBench/arena.cpp new file mode 100644 index 00000000000..d91986c7276 --- /dev/null +++ b/src/tools/ConsoleBench/arena.cpp @@ -0,0 +1,268 @@ +#include "pch.h" +#include "arena.h" + +using namespace mem; + +Arena::Arena(size_t bytes) +{ + m_alloc = static_cast(THROW_IF_NULL_ALLOC(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))); +} + +Arena::~Arena() +{ + VirtualFree(m_alloc, 0, MEM_RELEASE); +} + +void Arena::clear() +{ + m_pos = 0; +} + +size_t Arena::get_pos() const +{ + return m_pos; +} + +void Arena::pop_to(size_t pos) +{ + if (m_pos <= pos) + { + return; + } + +#ifndef NDEBUG + memset(m_alloc + pos, 0xDD, m_pos - pos); +#endif + + m_pos = pos; +} + +void* Arena::_push_raw(size_t bytes, size_t alignment) +{ + const auto mask = alignment - 1; + const auto pos = (m_pos + mask) & ~mask; + const auto ptr = m_alloc + pos; + m_pos = pos + bytes; + return ptr; +} + +void* Arena::_push_zeroed(size_t bytes, size_t alignment) +{ + const auto ptr = _push_raw(bytes, alignment); + memset(ptr, 0, bytes); + return ptr; +} + +void* Arena::_push_uninitialized(size_t bytes, size_t alignment) +{ + const auto ptr = _push_raw(bytes, alignment); +#ifndef NDEBUG + memset(ptr, 0xCD, bytes); +#endif + return ptr; +} + +ScopedArena::ScopedArena(Arena& arena) : + arena{ arena }, + m_pos_backup{ arena.get_pos() } +{ +} + +ScopedArena::~ScopedArena() +{ + arena.pop_to(m_pos_backup); +} + +static [[msvc::noinline]] std::array thread_arenas_init() +{ + return { + Arena{ 64 * 1024 * 1024 }, + Arena{ 64 * 1024 * 1024 }, + }; +} + +// This is based on an idea publicly described by Ryan Fleury as "scratch arena". +// Assuming you have "persistent" data and "scratch" data, where the former is data that is returned to +// the caller (= upwards) and the latter is data that is used locally, including calls (= downwards). +// The fundamental realisation now is that regular, linear function calls (not coroutines) are sufficiently +// covered with just N+1 arenas, where N is the number of in-flight "persistent" arenas across a call stack. +// Often N is 1, because in most code, there's only 1 arena being passed as a parameter at a time. +// This is also what this code specializes for. +// +// For instance, imagine you call A, which calls B, which calls C, and all 3 of those want to +// return data and also allocate data for themselves, and that you have 2 arenas: 1 and 2. +// Down in C the two arenas now look like this: +// 1: [A (return)][B (local) ][C (return)] +// 2: [A (local) ][B (return)][C (local) ] +// +// Now when each call returns and the arena's position is popped to the state before the call, this +// interleaving ensures that you neither pop local data from, nor return data intended for a parent call. +// After C returns: +// 1: [A (return)][B (local) ][C (return)] +// 2: [A (local) ][B (return)] +// After B returns: +// 1: [A (return)] +// 2: [A (local) ][B (return)] +// If this step confused you: B() got passed arena 2 from A() and uses arena 1 for local data. +// When B() returns it restores this local arena to how it was before it used it, which means +// that both, B's local data and C's return data are deallocated simultaneously. Neat! +// After A returns: +// 1: [A (return)] +// 2: +static ScopedArena get_scratch_arena_impl(const Arena* conflict) +{ + thread_local auto thread_arenas = thread_arenas_init(); + auto& ta = thread_arenas; + return ScopedArena{ ta[conflict == &ta[0]] }; +} + +ScopedArena mem::get_scratch_arena() +{ + return get_scratch_arena_impl(nullptr); +} + +ScopedArena mem::get_scratch_arena(const Arena& conflict) +{ + return get_scratch_arena_impl(&conflict); +} + +#pragma warning(push) +#pragma warning(disable : 4996) + +std::string_view mem::format(Arena& arena, const char* fmt, va_list args) +{ + auto len = _vsnprintf(nullptr, 0, fmt, args); + if (len < 0) + { + return {}; + } + + len++; + const auto buffer = arena.push_uninitialized(len); + + len = _vsnprintf(buffer, len, fmt, args); + if (len < 0) + { + return {}; + } + + return { buffer, static_cast(len) }; +} + +std::string_view mem::format(Arena& arena, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + const auto str = format(arena, fmt, args); + va_end(args); + return str; +} + +std::wstring_view mem::format(Arena& arena, const wchar_t* fmt, va_list args) +{ + auto len = _vsnwprintf(nullptr, 0, fmt, args); + if (len < 0) + { + return {}; + } + + len++; + const auto buffer = arena.push_uninitialized(len); + + len = _vsnwprintf(buffer, len, fmt, args); + if (len < 0) + { + return {}; + } + + return { buffer, static_cast(len) }; +} + +std::wstring_view mem::format(Arena& arena, const wchar_t* fmt, ...) +{ + va_list args; + va_start(args, fmt); + const auto str = format(arena, fmt, args); + va_end(args); + return str; +} + +#pragma warning(pop) + +void mem::print_literal(const char* str) +{ + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), str, static_cast(strlen(str)), nullptr, nullptr); +} + +// printf() in the uCRT prints every single char individually and thus breaks surrogate +// pairs apart which Windows Terminal treats as invalid input and replaces it with U+FFFD. +void mem::print_format(Arena& arena, const char* fmt, ...) +{ + const auto scratch = get_scratch_arena(arena); + + va_list args; + va_start(args, fmt); + const auto str = format(scratch.arena, fmt, args); + va_end(args); + + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), str.data(), static_cast(str.size()), nullptr, nullptr); +} + +std::string_view mem::read_line(Arena& arena, size_t max_bytes) +{ + auto read = static_cast(max_bytes); + const auto buffer = arena.push_uninitialized(max_bytes); + if (!ReadConsoleA(GetStdHandle(STD_INPUT_HANDLE), buffer, read, &read, nullptr)) + { + read = 0; + } + return { buffer, read }; +} + +std::wstring_view mem::u8u16(Arena& arena, const char* ptr, size_t count) +{ + if (count == 0 || count > INT_MAX) + { + return {}; + } + + const auto int_count = static_cast(count); + auto length = MultiByteToWideChar(CP_UTF8, 0, ptr, int_count, nullptr, 0); + if (length <= 0) + { + return {}; + } + + const auto buffer = arena.push_uninitialized(length); + length = MultiByteToWideChar(CP_UTF8, 0, ptr, int_count, buffer, length); + if (length <= 0) + { + return {}; + } + + return { buffer, static_cast(length) }; +} + +std::string_view mem::u16u8(Arena& arena, const wchar_t* ptr, size_t count) +{ + if (count == 0 || count > INT_MAX) + { + return {}; + } + + const auto int_count = static_cast(count); + auto length = WideCharToMultiByte(CP_UTF8, 0, ptr, int_count, nullptr, 0, nullptr, nullptr); + if (length <= 0) + { + return {}; + } + + const auto buffer = arena.push_uninitialized(length); + length = WideCharToMultiByte(CP_UTF8, 0, ptr, int_count, buffer, length, nullptr, nullptr); + if (length <= 0) + { + return {}; + } + + return { buffer, static_cast(length) }; +} diff --git a/src/tools/ConsoleBench/arena.h b/src/tools/ConsoleBench/arena.h new file mode 100644 index 00000000000..1519f03377b --- /dev/null +++ b/src/tools/ConsoleBench/arena.h @@ -0,0 +1,111 @@ +#pragma once + +namespace mem +{ + template + struct is_std_view : std::false_type + { + }; + template + struct is_std_view> : std::true_type + { + }; + template + struct is_std_view> : std::true_type + { + }; + + template + concept is_trivially_constructible = std::is_trivially_constructible_v || is_std_view>::value; + + template + concept is_trivially_copyable = std::is_trivially_copyable_v; + + struct Arena + { + explicit Arena(size_t bytes); + ~Arena(); + + void clear(); + size_t get_pos() const; + void pop_to(size_t pos); + + template + T* push_zeroed(size_t count = 1) + { + return static_cast(_push_zeroed(sizeof(T) * count, alignof(T))); + } + + template + std::span push_zeroed_span(size_t count) + { + return { static_cast(_push_zeroed(sizeof(T) * count, alignof(T))), count }; + } + + template + T* push_uninitialized(size_t count = 1) + { + return static_cast(_push_uninitialized(sizeof(T) * count, alignof(T))); + } + + template + std::span push_uninitialized_span(size_t count) + { + return { static_cast(_push_uninitialized(sizeof(T) * count, alignof(T))), count }; + } + + private: + void* _push_raw(size_t bytes, size_t alignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__); + void* _push_zeroed(size_t bytes, size_t alignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__); + void* _push_uninitialized(size_t bytes, size_t alignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__); + + uint8_t* m_alloc = nullptr; + size_t m_pos = 0; + }; + + struct ScopedArena + { + Arena& arena; + + ScopedArena(Arena& arena); + ~ScopedArena(); + + private: + size_t m_pos_backup; + }; + + [[nodiscard]] ScopedArena get_scratch_arena(); + [[nodiscard]] ScopedArena get_scratch_arena(const Arena& conflict); + + std::string_view format(Arena& arena, _Printf_format_string_ const char* fmt, va_list args); + std::string_view format(Arena& arena, _Printf_format_string_ const char* fmt, ...); + std::wstring_view format(Arena& arena, _Printf_format_string_ const wchar_t* fmt, va_list args); + std::wstring_view format(Arena& arena, _Printf_format_string_ const wchar_t* fmt, ...); + + void print_literal(const char* str); + void print_format(Arena& arena, _Printf_format_string_ const char* fmt, ...); + std::string_view read_line(Arena& arena, size_t max_bytes); + + std::wstring_view u8u16(Arena& arena, const char* ptr, size_t count); + std::string_view u16u8(Arena& arena, const wchar_t* ptr, size_t count); + + template + void copy(T* dst, const T* src, size_t count) + { + memcpy(dst, src, count * sizeof(T)); + } + + template + std::basic_string_view repeat_string(Arena& arena, std::basic_string_view in, size_t count) + { + const auto len = count * in.size(); + const auto buf = arena.push_uninitialized(len); + + for (size_t i = 0; i < count; ++i) + { + mem::copy(buf + i * in.size(), in.data(), in.size()); + } + + return { buf, len }; + } +} diff --git a/src/tools/ConsoleBench/conhost.cpp b/src/tools/ConsoleBench/conhost.cpp new file mode 100644 index 00000000000..95a0a3ea8a9 --- /dev/null +++ b/src/tools/ConsoleBench/conhost.cpp @@ -0,0 +1,170 @@ +#include "pch.h" +#include "conhost.h" + +#include +#include + +#include "arena.h" + +static unique_nthandle conhostCreateHandle(HANDLE parent, const wchar_t* typeName, bool inherit, bool synchronous) +{ + UNICODE_STRING name; + RtlInitUnicodeString(&name, typeName); + + ULONG attrFlags = OBJ_CASE_INSENSITIVE; + WI_SetFlagIf(attrFlags, OBJ_INHERIT, inherit); + + OBJECT_ATTRIBUTES attr; + InitializeObjectAttributes(&attr, &name, attrFlags, parent, nullptr); + + ULONG options = 0; + WI_SetFlagIf(options, FILE_SYNCHRONOUS_IO_NONALERT, synchronous); + + HANDLE handle; + IO_STATUS_BLOCK ioStatus; + THROW_IF_NTSTATUS_FAILED(NtCreateFile( + /* FileHandle */ &handle, + /* DesiredAccess */ FILE_GENERIC_READ | FILE_GENERIC_WRITE, + /* ObjectAttributes */ &attr, + /* IoStatusBlock */ &ioStatus, + /* AllocationSize */ nullptr, + /* FileAttributes */ 0, + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + /* CreateDisposition */ FILE_CREATE, + /* CreateOptions */ options, + /* EaBuffer */ nullptr, + /* EaLength */ 0)); + return unique_nthandle{ handle }; +} + +static void conhostCopyToStringBuffer(USHORT& length, auto& buffer, const wchar_t* str) +{ + const auto len = wcsnlen(str, sizeof(buffer) / sizeof(wchar_t)) * sizeof(wchar_t); + length = static_cast(len); + memcpy(buffer, str, len); +} + +ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path) +{ + const auto scratch = mem::get_scratch_arena(arena); + const auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false); + auto reference = conhostCreateHandle(server.get(), L"\\Reference", false, true); + + { + const auto cmd = format(scratch.arena, LR"("%s" --server 0x%zx)", path, server.get()); + + uint8_t attrListBuffer[64]; + + STARTUPINFOEXW siEx{}; + siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW); + siEx.lpAttributeList = reinterpret_cast(&attrListBuffer[0]); + + HANDLE inheritedHandles[1]; + inheritedHandles[0] = server.get(); + + auto listSize = sizeof(attrListBuffer); + THROW_IF_WIN32_BOOL_FALSE(InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &listSize)); + const auto cleanupProcThreadAttribute = wil::scope_exit([&]() noexcept { + DeleteProcThreadAttributeList(siEx.lpAttributeList); + }); + + THROW_IF_WIN32_BOOL_FALSE(UpdateProcThreadAttribute( + siEx.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + &inheritedHandles[0], + sizeof(inheritedHandles), + nullptr, + nullptr)); + + wil::unique_process_information pi; + THROW_IF_WIN32_BOOL_FALSE(CreateProcessW( + /* lpApplicationName */ nullptr, + /* lpCommandLine */ const_cast(cmd.data()), + /* lpProcessAttributes */ nullptr, + /* lpThreadAttributes */ nullptr, + /* bInheritHandles */ TRUE, + /* dwCreationFlags */ EXTENDED_STARTUPINFO_PRESENT, + /* lpEnvironment */ nullptr, + /* lpCurrentDirectory */ nullptr, + /* lpStartupInfo */ &siEx.StartupInfo, + /* lpProcessInformation */ pi.addressof())); + } + + unique_nthandle connection; + { + UNICODE_STRING name; + RtlInitUnicodeString(&name, L"\\Connect"); + + OBJECT_ATTRIBUTES attr; + InitializeObjectAttributes(&attr, &name, OBJ_CASE_INSENSITIVE, reference.get(), nullptr); + + CONSOLE_SERVER_MSG msg{}; + { + // This must be RTL_USER_PROCESS_PARAMETERS::ProcessGroupId unless it's 0, + // but winternl doesn't know about that field. ;) + msg.ProcessGroupId = GetCurrentProcessId(); + msg.ConsoleApp = TRUE; + msg.WindowVisible = TRUE; + + conhostCopyToStringBuffer(msg.TitleLength, msg.Title, L"ConsoleBench.exe"); + conhostCopyToStringBuffer(msg.ApplicationNameLength, msg.ApplicationName, L"ConsoleBench.exe"); + conhostCopyToStringBuffer(msg.CurrentDirectoryLength, msg.CurrentDirectory, L"C:\\Windows\\System32\\"); + } + + // From wdm.h, but without the trailing `CHAR EaName[1];` field, as this makes + // appending the string at the end of the messages unnecessarily difficult. + struct FILE_FULL_EA_INFORMATION + { + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + }; + static constexpr auto bufferAppend = [](uint8_t* out, const auto& in) { + return static_cast(memcpy(out, &in, sizeof(in))) + sizeof(in); + }; + + alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) uint8_t eaBuffer[2048]; + auto eaBufferEnd = &eaBuffer[0]; + // Curiously, EaNameLength is the length without \0, + // but the data payload only starts after the name *with* \0. + eaBufferEnd = bufferAppend(eaBufferEnd, FILE_FULL_EA_INFORMATION{ .EaNameLength = 6, .EaValueLength = sizeof(msg) }); + eaBufferEnd = bufferAppend(eaBufferEnd, "server"); + eaBufferEnd = bufferAppend(eaBufferEnd, msg); + + IO_STATUS_BLOCK ioStatus; + THROW_IF_NTSTATUS_FAILED(NtCreateFile( + /* FileHandle */ connection.addressof(), + /* DesiredAccess */ FILE_GENERIC_READ | FILE_GENERIC_WRITE, + /* ObjectAttributes */ &attr, + /* IoStatusBlock */ &ioStatus, + /* AllocationSize */ nullptr, + /* FileAttributes */ 0, + /* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + /* CreateDisposition */ FILE_CREATE, + /* CreateOptions */ FILE_SYNCHRONOUS_IO_NONALERT, + /* EaBuffer */ &eaBuffer[0], + /* EaLength */ static_cast(eaBufferEnd - &eaBuffer[0]))); + } + + return ConhostHandle{ + .reference = std::move(reference), + .connection = std::move(connection), + }; +} + +HANDLE get_active_connection() +{ + // (Not actually) FUN FACT! The handles don't mean anything and the cake is a lie! + // Whenever you call any console API function, the handle you pass is completely and entirely ignored. + // Instead, condrv will probe the PEB, extract the ConsoleHandle field and use that to send + // the message to the server (conhost). ConsoleHandle happens to be at Reserved2[0]. + // Unfortunately, the reason for this horrifying approach has been lost to time. + return NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Reserved2[0]; +} + +void set_active_connection(HANDLE connection) +{ + NtCurrentTeb()->ProcessEnvironmentBlock->ProcessParameters->Reserved2[0] = connection; +} diff --git a/src/tools/ConsoleBench/conhost.h b/src/tools/ConsoleBench/conhost.h new file mode 100644 index 00000000000..e794eca555e --- /dev/null +++ b/src/tools/ConsoleBench/conhost.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace mem +{ + struct Arena; +} + +using unique_nthandle = wil::unique_any_handle_null; + +struct ConhostHandle +{ + unique_nthandle reference; + unique_nthandle connection; +}; + +ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path); +HANDLE get_active_connection(); +void set_active_connection(HANDLE connection); diff --git a/src/tools/ConsoleBench/main.cpp b/src/tools/ConsoleBench/main.cpp new file mode 100644 index 00000000000..4e380474ebc --- /dev/null +++ b/src/tools/ConsoleBench/main.cpp @@ -0,0 +1,555 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" + +#include "arena.h" +#include "conhost.h" +#include "utils.h" + +using Measurements = std::span; +using MeasurementsPerBenchmark = std::span; + +struct BenchmarkContext +{ + HWND hwnd; + HANDLE input; + HANDLE output; + int64_t time_limit; + mem::Arena& arena; + std::string_view utf8_4Ki; + std::string_view utf8_128Ki; + std::wstring_view utf16_4Ki; + std::wstring_view utf16_128Ki; +}; + +struct Benchmark +{ + const char* title; + void (*exec)(const BenchmarkContext& ctx, Measurements measurements); +}; + +struct AccumulatedResults +{ + size_t trace_count; + // These are arrays of size trace_count + std::string_view* trace_names; + MeasurementsPerBenchmark* measurments; +}; + +constexpr int32_t perf_delta(int64_t beg, int64_t end) +{ + return static_cast(end - beg); +} + +static constexpr Benchmark s_benchmarks[]{ + Benchmark{ + .title = "WriteConsoleA 4Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + for (auto& d : measurements) + { + const auto beg = query_perf_counter(); + WriteConsoleA(ctx.output, ctx.utf8_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "WriteConsoleW 4Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + for (auto& d : measurements) + { + const auto beg = query_perf_counter(); + WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), nullptr, nullptr); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "WriteConsoleA 128Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + for (auto& d : measurements) + { + const auto beg = query_perf_counter(); + WriteConsoleA(ctx.output, ctx.utf8_128Ki.data(), static_cast(ctx.utf8_128Ki.size()), nullptr, nullptr); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "WriteConsoleW 128Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + for (auto& d : measurements) + { + const auto beg = query_perf_counter(); + WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "Copy to clipboard 4Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr); + + for (auto& d : measurements) + { + SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF5 /* ID_CONSOLE_SELECTALL */, 0); + + const auto beg = query_perf_counter(); + SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF0 /* ID_CONSOLE_COPY */, 0); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "Paste from clipboard 4Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + set_clipboard(ctx.hwnd, ctx.utf16_4Ki); + FlushConsoleInputBuffer(ctx.input); + + for (auto& d : measurements) + { + const auto beg = query_perf_counter(); + SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + FlushConsoleInputBuffer(ctx.input); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, + Benchmark{ + .title = "ReadConsoleInputW clipboard 4Ki", + .exec = [](const BenchmarkContext& ctx, Measurements measurements) { + static constexpr DWORD cap = 16 * 1024; + + const auto scratch = mem::get_scratch_arena(ctx.arena); + const auto buf = scratch.arena.push_uninitialized(cap); + DWORD read; + + set_clipboard(ctx.hwnd, ctx.utf16_4Ki); + FlushConsoleInputBuffer(ctx.input); + + for (auto& d : measurements) + { + SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0); + + const auto beg = query_perf_counter(); + ReadConsoleInputW(ctx.input, buf, cap, &read); + debugAssert(read >= 1024 && read < cap); + const auto end = query_perf_counter(); + d = perf_delta(beg, end); + + if (end >= ctx.time_limit) + { + break; + } + } + }, + }, +}; +static constexpr size_t s_benchmarks_count = _countof(s_benchmarks); + +// Each of these strings is 128 columns. +static constexpr std::string_view payload_utf8{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor眠い子猫はマグロ狩りの夢を見る" }; +static constexpr std::wstring_view payload_utf16{ L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor眠い子猫はマグロ狩りの夢を見る" }; + +static bool print_warning(); +static AccumulatedResults* prepare_results(mem::Arena& arena, std::span paths); +static std::span run_benchmarks_for_path(mem::Arena& arena, const wchar_t* path); +static void generate_html(mem::Arena& arena, const AccumulatedResults* results); + +int wmain(int argc, const wchar_t* argv[]) +{ + if (argc < 2) + { + mem::print_literal("Usage: %s [paths to conhost.exe]..."); + return 1; + } + + const auto cp = GetConsoleCP(); + const auto output_cp = GetConsoleOutputCP(); + const auto restore_cp = wil::scope_exit([&]() { + SetConsoleCP(cp); + SetConsoleOutputCP(output_cp); + }); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + const auto scratch = mem::get_scratch_arena(); + + const std::span paths{ &argv[1], static_cast(argc) - 1 }; + const auto results = prepare_results(scratch.arena, paths); + if (!results) + { + return 1; + } + + if (!print_warning()) + { + return 0; + } + + for (size_t trace_idx = 0; trace_idx < results->trace_count; ++trace_idx) + { + const auto title = results->trace_names[trace_idx]; + print_format(scratch.arena, "\r\n# %.*s\r\n", title.size(), title.data()); + results->measurments[trace_idx] = run_benchmarks_for_path(scratch.arena, paths[trace_idx]); + } + + generate_html(scratch.arena, results); + return 0; +} + +static bool print_warning() +{ + mem::print_literal( + "This will overwrite any existing measurements.html in your current working directory.\r\n" + "\r\n" + "For best test results:\r\n" + "* Make sure your system is fully idle and your CPU cool\r\n" + "* Move your cursor to a corner of your screen and don't move it over the conhost window(s)\r\n" + "* Exit or stop any background applications, including Windows Defender (if possible)\r\n" + "\r\n" + "Continue? [Yn] "); + + for (;;) + { + INPUT_RECORD rec; + DWORD read; + if (!ReadConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &rec, 1, &read) || read == 0) + { + return false; + } + + if (rec.EventType == KEY_EVENT && rec.Event.KeyEvent.bKeyDown) + { + // Transforms the character to uppercase if it's lowercase. + const auto ch = rec.Event.KeyEvent.uChar.UnicodeChar & 0xDF; + if (ch == L'N') + { + return false; + } + if (ch == L'\r' || ch == L'Y') + { + break; + } + } + } + + mem::print_literal("\r\n"); + return true; +} + +static AccumulatedResults* prepare_results(mem::Arena& arena, std::span paths) +{ + for (const auto path : paths) + { + const auto attr = GetFileAttributesW(path); + if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + print_format(arena, "Invalid path: %s\r\n", path); + return nullptr; + } + } + + const auto trace_count = paths.size(); + const auto results = arena.push_zeroed(); + + results->trace_count = trace_count; + results->trace_names = arena.push_zeroed(trace_count); + results->measurments = arena.push_zeroed(trace_count); + + for (size_t trace_idx = 0; trace_idx < trace_count; ++trace_idx) + { + const auto path = paths[trace_idx]; + auto trace_name = get_file_version(arena, path); + + if (trace_name.empty()) + { + const auto end = path + wcsnlen(path, SIZE_MAX); + auto filename = end; + for (; filename > path && filename[-1] != L'\\' && filename[-1] != L'/'; --filename) + { + } + trace_name = u16u8(arena, filename, end - filename); + } + + results->trace_names[trace_idx] = trace_name; + } + + return results; +} + +static void prepare_conhost(const BenchmarkContext& ctx, HWND parent_hwnd) +{ + const auto scratch = mem::get_scratch_arena(ctx.arena); + + SetForegroundWindow(parent_hwnd); + + // Ensure conhost is in a consistent state with identical fonts and window sizes, + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + SetConsoleMode(ctx.output, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + { + CONSOLE_SCREEN_BUFFER_INFOEX info{ + .cbSize = sizeof(info), + .dwSize = { 120, 9001 }, + .wAttributes = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, + .srWindow = { 0, 0, 119, 29 }, + .dwMaximumWindowSize = { 120, 30 }, + .wPopupAttributes = FOREGROUND_BLUE | FOREGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY, + .ColorTable = { 0x0C0C0C, 0x1F0FC5, 0x0EA113, 0x009CC1, 0xDA3700, 0x981788, 0xDD963A, 0xCCCCCC, 0x767676, 0x5648E7, 0x0CC616, 0xA5F1F9, 0xFF783B, 0x9E00B4, 0xD6D661, 0xF2F2F2 }, + }; + SetConsoleScreenBufferInfoEx(ctx.output, &info); + } + { + CONSOLE_FONT_INFOEX info{ + .cbSize = sizeof(info), + .dwFontSize = { 0, 16 }, + .FontFamily = 54, + .FontWeight = 400, + .FaceName = L"Consolas", + }; + SetCurrentConsoleFontEx(ctx.output, FALSE, &info); + } + + // Ensure conhost's backing TextBuffer is fully committed and initialized. There's currently no way + // to un-commit it and so not committing it now would be unfair for the first test that runs. + const auto buf = scratch.arena.push_uninitialized(9001); + memset(buf, '\n', 9001); + WriteFile(ctx.output, buf, 9001, nullptr, nullptr); +} + +static std::span run_benchmarks_for_path(mem::Arena& arena, const wchar_t* path) +{ + const auto scratch = mem::get_scratch_arena(arena); + const auto parent_connection = get_active_connection(); + const auto parent_hwnd = GetConsoleWindow(); + const auto freq = query_perf_freq(); + + const auto handle = spawn_conhost(scratch.arena, path); + set_active_connection(handle.connection.get()); + + const auto print_with_parent_connection = [&](auto&&... args) { + set_active_connection(parent_connection); + mem::print_format(scratch.arena, args...); + set_active_connection(handle.connection.get()); + }; + + BenchmarkContext ctx{ + .hwnd = GetConsoleWindow(), + .input = GetStdHandle(STD_INPUT_HANDLE), + .output = GetStdHandle(STD_OUTPUT_HANDLE), + .arena = scratch.arena, + .utf8_4Ki = mem::repeat_string(scratch.arena, payload_utf8, 4 * 1024 / 128), + .utf8_128Ki = mem::repeat_string(scratch.arena, payload_utf8, 128 * 1024 / 128), + .utf16_4Ki = mem::repeat_string(scratch.arena, payload_utf16, 4 * 1024 / 128), + .utf16_128Ki = mem::repeat_string(scratch.arena, payload_utf16, 128 * 1024 / 128), + }; + + prepare_conhost(ctx, parent_hwnd); + Sleep(1000); + + const auto results = arena.push_uninitialized_span(s_benchmarks_count); + for (auto& measurements : results) + { + measurements = arena.push_zeroed_span(2048); + } + + for (size_t bench_idx = 0; bench_idx < s_benchmarks_count; ++bench_idx) + { + const auto& bench = s_benchmarks[bench_idx]; + auto& measurements = results[bench_idx]; + + print_with_parent_connection("- %s", bench.title); + + // Warmup for 0.1s. + WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr); + ctx.time_limit = query_perf_counter() + freq / 10; + bench.exec(ctx, measurements); + + // Actual run for 1s. + WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr); + ctx.time_limit = query_perf_counter() + freq; + bench.exec(ctx, measurements); + + // Trim off trailing 0s that resulted from the time_limit. + size_t len = measurements.size(); + for (; len > 0 && measurements[len - 1] == 0; --len) + { + } + measurements = measurements.subspan(0, len); + + print_with_parent_connection(", done\r\n"); + } + + set_active_connection(parent_connection); + return results; +} + +static void generate_html(mem::Arena& arena, const AccumulatedResults* results) +{ + const auto scratch = mem::get_scratch_arena(arena); + + const wil::unique_hfile file{ THROW_LAST_ERROR_IF_NULL(CreateFileW(L"measurements.html", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)) }; + const auto sec_per_tick = 1.0f / query_perf_freq(); + BufferedWriter writer{ file.get(), scratch.arena.push_uninitialized_span(1024 * 1024) }; + + writer.write(R"( + + + + + + + + + + + + + + + +)"); +} diff --git a/src/tools/ConsoleBench/pch.cpp b/src/tools/ConsoleBench/pch.cpp new file mode 100644 index 00000000000..398a99f6653 --- /dev/null +++ b/src/tools/ConsoleBench/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/src/tools/ConsoleBench/pch.h b/src/tools/ConsoleBench/pch.h new file mode 100644 index 00000000000..ce019754307 --- /dev/null +++ b/src/tools/ConsoleBench/pch.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +#include +#include +#include +#include +#include diff --git a/src/tools/ConsoleBench/utils.cpp b/src/tools/ConsoleBench/utils.cpp new file mode 100644 index 00000000000..03308addeb6 --- /dev/null +++ b/src/tools/ConsoleBench/utils.cpp @@ -0,0 +1,108 @@ +#include "pch.h" +#include "utils.h" + +#include "arena.h" + +BufferedWriter::BufferedWriter(HANDLE out, std::span buffer) : + m_out{ out }, + m_buffer{ buffer } +{ +} + +BufferedWriter::~BufferedWriter() +{ + flush(); +} + +void BufferedWriter::flush() +{ + _write(m_buffer.data(), m_buffer_usage); + m_buffer_usage = 0; +} + +void BufferedWriter::write(std::string_view str) +{ + if (m_buffer_usage + str.size() > m_buffer.size()) + { + flush(); + } + + if (str.size() >= m_buffer.size()) + { + _write(str.data(), str.size()); + } + else + { + mem::copy(m_buffer.data() + m_buffer_usage, str.data(), str.size()); + m_buffer_usage += str.size(); + } +} + +void BufferedWriter::_write(const void* data, size_t bytes) const +{ + DWORD written; + THROW_IF_WIN32_BOOL_FALSE(WriteFile(m_out, data, static_cast(bytes), &written, nullptr)); + THROW_HR_IF(E_FAIL, written != bytes); +} + +std::string_view get_file_version(mem::Arena& arena, const wchar_t* path) +{ + const auto bytes = GetFileVersionInfoSizeExW(0, path, nullptr); + if (!bytes) + { + return {}; + } + + const auto scratch = mem::get_scratch_arena(arena); + const auto buffer = scratch.arena.push_uninitialized(bytes); + if (!GetFileVersionInfoExW(0, path, 0, bytes, buffer)) + { + return {}; + } + + VS_FIXEDFILEINFO* info; + UINT varLen = 0; + if (!VerQueryValueW(buffer, L"\\", reinterpret_cast(&info), &varLen)) + { + return {}; + } + + return mem::format( + arena, + "%d.%d.%d.%d", + HIWORD(info->dwFileVersionMS), + LOWORD(info->dwFileVersionMS), + HIWORD(info->dwFileVersionLS), + LOWORD(info->dwFileVersionLS)); +} + +void set_clipboard(HWND hwnd, std::wstring_view contents) +{ + const auto global = GlobalAlloc(GMEM_MOVEABLE, (contents.size() + 1) * sizeof(wchar_t)); + { + const auto ptr = static_cast(GlobalLock(global)); + + mem::copy(ptr, contents.data(), contents.size()); + ptr[contents.size()] = 0; + + GlobalUnlock(global); + } + + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + break; + } + // 10 iterations + if (sleep > 10000) + { + THROW_LAST_ERROR(); + } + Sleep(sleep); + } + + EmptyClipboard(); + SetClipboardData(CF_UNICODETEXT, global); + CloseClipboard(); +} diff --git a/src/tools/ConsoleBench/utils.h b/src/tools/ConsoleBench/utils.h new file mode 100644 index 00000000000..2c30d77f243 --- /dev/null +++ b/src/tools/ConsoleBench/utils.h @@ -0,0 +1,47 @@ +#pragma once + +// clang-format off +#ifdef NDEBUG +#define debugAssert(cond) ((void)0) +#else +#define debugAssert(cond) if (!(cond)) __debugbreak() +#endif +// clang-format on + +namespace mem +{ + struct Arena; +} + +struct BufferedWriter +{ + BufferedWriter(HANDLE out, std::span buffer); + ~BufferedWriter(); + + void flush(); + void write(std::string_view str); + +private: + void _write(const void* data, size_t bytes) const; + + HANDLE m_out; + std::span m_buffer; + size_t m_buffer_usage = 0; +}; + +inline int64_t query_perf_counter() +{ + LARGE_INTEGER i; + QueryPerformanceCounter(&i); + return i.QuadPart; +} + +inline int64_t query_perf_freq() +{ + LARGE_INTEGER i; + QueryPerformanceFrequency(&i); + return i.QuadPart; +} + +std::string_view get_file_version(mem::Arena& arena, const wchar_t* path); +void set_clipboard(HWND hwnd, std::wstring_view contents); From 6b29ef51e304fb1af8df7c798f87d3dbb0408c92 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 23 Feb 2024 05:34:02 -0600 Subject: [PATCH 23/50] build: switch to NuGetAuthenticate@1 (#16752) It keeps warning us that v0 has been deprecated. --- .../pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml | 2 +- build/pipelines/templates-v2/steps-restore-nuget.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml index b61a22c4fc1..e30e23b88c4 100644 --- a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml +++ b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml @@ -43,7 +43,7 @@ jobs: - template: steps-ensure-nuget-version.yml - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 inputs: nuGetServiceConnections: 'Terminal Public Artifact Feed' diff --git a/build/pipelines/templates-v2/steps-restore-nuget.yml b/build/pipelines/templates-v2/steps-restore-nuget.yml index bd0c067531c..37018efc1a9 100644 --- a/build/pipelines/templates-v2/steps-restore-nuget.yml +++ b/build/pipelines/templates-v2/steps-restore-nuget.yml @@ -1,7 +1,7 @@ steps: - template: steps-ensure-nuget-version.yml -- task: NuGetAuthenticate@0 +- task: NuGetAuthenticate@1 - script: |- echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform) From a6a0e44088ab768325665308367ac016a2534891 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Feb 2024 22:40:29 +0100 Subject: [PATCH 24/50] Add support for custom box drawing and powerline glyphs (#16729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for drawing our own box drawing, block element, and basic Powerline (U+E0Bx) glyphs in AtlasEngine. This PR consists of 4 parts: * AtlasEngine was refactored to simplify `_drawGlyph` because I've wanted to do that for ~1 year now and never got a chance. Well, now I'm doing it and you all will review it muahahaha. The good news is that it removes a goto usage that even for my standards was rather dangerous. Now it's gone and the risk with it. * AtlasEngine was further refactored to properly parse out text that we want to handle different from regular text. Previously, we only did that for soft fonts, but now we want to do that for a lot more, so a refactor was in order. The new code is still extremely disgusting, because I now stuff `wchar_t`s into an array that's intended for glyph indices, but that's the best way to make it fast and not blow up the complexity of the code even further. * Finally this adds a huge LUT for all the aforementioned glyphs. The LUT has 4 "drawing instruction" entries per glyph which describe the shape (rectangle, circle, lines, etc.) and the start/end coord. With a lot of bit packing each entry is only 4 bytes large. * Finally-finally a `builtinGlyphs` setting was added to the font object and it defaults to `true`. Closes #5897 ## Validation Steps Performed * RenderingTests with soft fonts ✅ * All the aforementioned glyphs ✅ * ...with color ✅ * `customGlyphs` setting can be toggled on and off ✅ --- src/cascadia/TerminalControl/ControlCore.cpp | 2 + src/cascadia/TerminalControl/ControlCore.h | 1 + .../TerminalControl/IControlSettings.idl | 1 + .../TerminalSettingsEditor/Appearances.h | 1 + .../TerminalSettingsEditor/Appearances.idl | 1 + .../TerminalSettingsEditor/Appearances.xaml | 9 + .../Resources/en-US/Resources.resw | 8 + .../TerminalSettingsModel/FontConfig.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + src/host/screenInfo.cpp | 4 + src/host/settings.cpp | 5 + src/host/settings.hpp | 2 + src/inc/til/flat_set.h | 36 +- src/inc/til/unicode.h | 13 +- src/propslib/RegistrySerialization.cpp | 5 +- src/renderer/atlas/AtlasEngine.api.cpp | 6 +- src/renderer/atlas/AtlasEngine.cpp | 144 +- src/renderer/atlas/AtlasEngine.h | 16 +- src/renderer/atlas/AtlasEngine.r.cpp | 22 +- src/renderer/atlas/Backend.h | 5 - src/renderer/atlas/BackendD3D.cpp | 480 +++---- src/renderer/atlas/BackendD3D.h | 104 +- src/renderer/atlas/BuiltinGlyphs.cpp | 1276 +++++++++++++++++ src/renderer/atlas/BuiltinGlyphs.h | 18 + src/renderer/atlas/atlas.vcxproj | 2 + src/renderer/atlas/common.h | 19 +- src/renderer/base/FontInfoDesired.cpp | 10 + src/renderer/inc/FontInfoDesired.hpp | 3 + src/til/ut_til/FlatSetTests.cpp | 37 +- 32 files changed, 1775 insertions(+), 460 deletions(-) create mode 100644 src/renderer/atlas/BuiltinGlyphs.cpp create mode 100644 src/renderer/atlas/BuiltinGlyphs.h diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0dd8df67a83..8d3e692b114 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -860,6 +860,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto lock = _terminal->LockForWriting(); + _builtinGlyphs = _settings->EnableBuiltinGlyphs(); _cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str()); _cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str()); _runtimeOpacity = std::nullopt; @@ -1038,6 +1039,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false }; _actualFontFaceName = { fontFace }; + _desiredFont.SetEnableBuiltinGlyphs(_builtinGlyphs); _desiredFont.SetCellSize(_cellWidth, _cellHeight); const auto before = _actualFont.GetSize(); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index dff8ba52bf3..6ab42cdbddb 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -316,6 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation FontInfoDesired _desiredFont; FontInfo _actualFont; winrt::hstring _actualFontFaceName; + bool _builtinGlyphs = true; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index e89b58eeb8c..8a680f1488e 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Control String Padding { get; }; Windows.Foundation.Collections.IMap FontFeatures { get; }; Windows.Foundation.Collections.IMap FontAxes { get; }; + Boolean EnableBuiltinGlyphs { get; }; String CellWidth { get; }; String CellHeight { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index bc34e62978c..4f220ef5e78 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -87,6 +87,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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(), EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_SETTING(_appearance, RetroTerminalEffect); OBSERVABLE_PROJECTED_SETTING(_appearance, CursorShape); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 55ff2be721d..ddab88a6beb 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Text.FontWeight, FontWeight); + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, DarkColorSchemeName); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, LightColorSchemeName); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index e37cdd2e1bf..f1f13d8fbd6 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -287,6 +287,15 @@ + + + + + 1.2 "1.2" is a decimal number. + + Builtin Glyphs + The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones. + + + When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font. This feature only works when GPU Acceleration is available. + A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification. + Font weight Name for a control to select the weight (i.e. bold, thin, etc.) of the text in the app. diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.idl b/src/cascadia/TerminalSettingsModel/FontConfig.idl index 249bb94f8d1..995e7c6a651 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.idl +++ b/src/cascadia/TerminalSettingsModel/FontConfig.idl @@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight); INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontAxes); + INHERITABLE_FONT_SETTING(Boolean, EnableBuiltinGlyphs); INHERITABLE_FONT_SETTING(String, CellWidth); INHERITABLE_FONT_SETTING(String, CellHeight); } diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index f9626e6b6c8..43bb7a12eb4 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -114,6 +114,7 @@ Author(s): X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \ X(IFontAxesMap, FontAxes, "axes") \ X(IFontFeatureMap, FontFeatures, "features") \ + X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \ X(winrt::hstring, CellWidth, "cellWidth") \ X(winrt::hstring, CellHeight, "cellHeight") diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 21d76fd8ed8..cb5633eace4 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -289,6 +289,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _FontWeight = fontInfo.FontWeight(); _FontFeatures = fontInfo.FontFeatures(); _FontAxes = fontInfo.FontAxes(); + _EnableBuiltinGlyphs = fontInfo.EnableBuiltinGlyphs(); _CellWidth = fontInfo.CellWidth(); _CellHeight = fontInfo.CellHeight(); _Padding = profile.Padding(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index b065ee3c9f6..0e7346a5960 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -129,6 +129,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, winrt::Windows::UI::Text::FontWeight, FontWeight); INHERITABLE_SETTING(Model::TerminalSettings, IFontAxesMap, FontAxes); INHERITABLE_SETTING(Model::TerminalSettings, IFontFeatureMap, FontFeatures); + INHERITABLE_SETTING(Model::TerminalSettings, bool, EnableBuiltinGlyphs, true); INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellWidth); INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellHeight); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 02c4ad2427a..8134701d0f6 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -63,6 +63,7 @@ X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ X(IFontFeatureMap, FontFeatures) \ X(IFontAxesMap, FontAxes) \ + X(bool, EnableBuiltinGlyphs, true) \ X(winrt::hstring, CellWidth) \ X(winrt::hstring, CellHeight) \ X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \ diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 2701b4e2072..9b0ff2ca4e4 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -64,6 +64,7 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( { OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; } + _desiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); } // Routine Description: @@ -539,7 +540,10 @@ void SCREEN_INFORMATION::RefreshFontWithRenderer() void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont) { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + FontInfoDesired fiDesiredFont(*pfiNewFont); + fiDesiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); GetDesiredFont() = fiDesiredFont; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 274fadec58f..0798fe847ab 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -776,3 +776,8 @@ bool Settings::GetCopyColor() const noexcept { return _fCopyColor; } + +bool Settings::GetEnableBuiltinGlyphs() const noexcept +{ + return _fEnableBuiltinGlyphs; +} diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 4edeb3ae927..a254589755f 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -171,6 +171,7 @@ class Settings bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; private: RenderSettings _renderSettings; @@ -214,6 +215,7 @@ class Settings DWORD _dwVirtTermLevel; bool _fUseDx; bool _fCopyColor; + bool _fEnableBuiltinGlyphs = true; // this is used for the special STARTF_USESIZE mode. bool _fUseWindowSizePixels; diff --git a/src/inc/til/flat_set.h b/src/inc/til/flat_set.h index 07942abf715..ef7045759ab 100644 --- a/src/inc/til/flat_set.h +++ b/src/inc/til/flat_set.h @@ -4,8 +4,9 @@ #pragma once #pragma warning(push) -#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). #pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique instead (r.11). +#pragma warning(disable : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21). +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). namespace til { @@ -36,7 +37,7 @@ namespace til // * small and cheap T // * >= 50% successful lookups // * <= 50% load factor (LoadFactor >= 2, which is the minimum anyways) - template + template struct linear_flat_set { static_assert(LoadFactor >= 2); @@ -98,27 +99,28 @@ namespace til return nullptr; } - const auto hash = ::std::hash{}(key) >> _shift; + const auto hash = Traits::hash(key) >> _shift; for (auto i = hash;; ++i) { auto& slot = _map[i & _mask]; - if (!slot) + if (!Traits::occupied(slot)) { return nullptr; } - if (slot == key) [[likely]] + if (Traits::equals(slot, key)) [[likely]] { return &slot; } } } + // NOTE: It also does not initialize the returned slot. + // You must do that yourself in way that ensures that Traits::occupied(slot) now returns true. + // Use lookup() to check if the item already exists. template - std::pair insert(U&& key) + std::pair insert(U&& key) { - // Putting this into the lookup path is a little pessimistic, but it - // allows us to default-construct this hashmap with a size of 0. if (_load >= _capacity) [[unlikely]] { _bumpSize(); @@ -129,20 +131,20 @@ namespace til // many times in literature that such a scheme performs the best on average. // As such, we perform the divide here to get the topmost bits down. // See flat_set_hash_integer. - const auto hash = ::std::hash{}(key) >> _shift; + const auto hash = Traits::hash(key) >> _shift; for (auto i = hash;; ++i) { auto& slot = _map[i & _mask]; - if (!slot) + if (!Traits::occupied(slot)) { - slot = std::forward(key); _load += LoadFactor; - return { slot, true }; + Traits::assign(slot, key); + return { &slot, true }; } - if (slot == key) [[likely]] + if (Traits::equals(slot, key)) [[likely]] { - return { slot, false }; + return { &slot, false }; } } } @@ -166,17 +168,17 @@ namespace til // This mirrors the insert() function, but without the lookup part. for (auto& oldSlot : container()) { - if (!oldSlot) + if (!Traits::occupied(oldSlot)) { continue; } - const auto hash = ::std::hash{}(oldSlot) >> newShift; + const auto hash = Traits::hash(oldSlot) >> newShift; for (auto i = hash;; ++i) { auto& slot = newMap[i & newMask]; - if (!slot) + if (!Traits::occupied(slot)) { slot = std::move_if_noexcept(oldSlot); break; diff --git a/src/inc/til/unicode.h b/src/inc/til/unicode.h index 9f703dcb813..99aeeb74769 100644 --- a/src/inc/til/unicode.h +++ b/src/inc/til/unicode.h @@ -10,21 +10,28 @@ namespace til inline constexpr wchar_t UNICODE_REPLACEMENT = 0xFFFD; } - static constexpr bool is_surrogate(const wchar_t wch) noexcept + constexpr bool is_surrogate(const auto wch) noexcept { return (wch & 0xF800) == 0xD800; } - static constexpr bool is_leading_surrogate(const wchar_t wch) noexcept + constexpr bool is_leading_surrogate(const auto wch) noexcept { return (wch & 0xFC00) == 0xD800; } - static constexpr bool is_trailing_surrogate(const wchar_t wch) noexcept + constexpr bool is_trailing_surrogate(const auto wch) noexcept { return (wch & 0xFC00) == 0xDC00; } + constexpr char32_t combine_surrogates(const auto lead, const auto trail) + { + // Ah, I love these bracketed C-style casts. I use them in C all the time. Yep. +#pragma warning(suppress : 26493) // Don't use C-style casts (type.4). + return (char32_t{ lead } << 10) - 0x35FDC00 + char32_t{ trail }; + } + // Verifies the beginning of the given UTF16 string and returns the first UTF16 sequence // or U+FFFD otherwise. It's not really useful and at the time of writing only a // single caller uses this. It's best to delete this if you read this comment. diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index a4441d25a5a..a350700c66f 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -61,7 +61,10 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, - { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) } + { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }, +#if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED + { _RegPropertyType::Boolean, L"EnableBuiltinGlyphs", SET_FIELD_AND_SIZE(_fEnableBuiltinGlyphs) }, +#endif // Special cases that are handled manually in Registry::LoadFromRegistry: // - CONSOLE_REGISTRY_WINDOWPOS diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 0e2f7941688..fa7290371e8 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -295,8 +295,8 @@ CATCH_RETURN() /* fontStyle */ DWRITE_FONT_STYLE_NORMAL, /* fontStretch */ DWRITE_FONT_STRETCH_NORMAL, /* fontSize */ _api.s->font->fontSize, - /* localeName */ L"", - /* textFormat */ textFormat.put())); + /* localeName */ _p.userLocaleName.c_str(), + /* textFormat */ textFormat.addressof())); wil::com_ptr textLayout; RETURN_IF_FAILED(_p.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast(glyph.size()), textFormat.get(), FLT_MAX, FLT_MAX, textLayout.addressof())); @@ -774,5 +774,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 }; fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; + + fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs(); } } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 93821394ce1..01403ef4b38 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -4,7 +4,10 @@ #include "pch.h" #include "AtlasEngine.h" +#include + #include "Backend.h" +#include "BuiltinGlyphs.h" #include "DWriteTextAnalysis.h" #include "../../interactivity/win32/CustomWindowMessages.h" @@ -20,6 +23,7 @@ #pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). #pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...]. #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). +#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1). #pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2). using namespace Microsoft::Console::Render::Atlas; @@ -70,8 +74,9 @@ try _handleSettingsUpdate(); } - if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION) + if (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION || _hackTriggerRedrawAll) { + _hackTriggerRedrawAll = false; _api.invalidatedRows = invalidatedRowsAll; _api.scrollOffset = 0; } @@ -575,7 +580,7 @@ void AtlasEngine::_recreateFontDependentResources() memcpy(&localeName[0], L"en-US", 12); } - _api.userLocaleName = std::wstring{ &localeName[0] }; + _p.userLocaleName = std::wstring{ &localeName[0] }; } if (_p.s->font->fontAxisValues.empty()) @@ -606,6 +611,8 @@ void AtlasEngine::_recreateFontDependentResources() _api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() }; } } + + _hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D; } void AtlasEngine::_recreateCellCountDependentResources() @@ -666,14 +673,65 @@ void AtlasEngine::_flushBufferLine() // This would seriously blow us up otherwise. Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1); + const auto beg = _api.bufferLine.data(); + const auto len = _api.bufferLine.size(); + size_t segmentBeg = 0; + size_t segmentEnd = 0; + bool custom = false; + + if (!_hackWantsBuiltinGlyphs) + { + _mapRegularText(0, len); + return; + } + + while (segmentBeg < len) + { + segmentEnd = segmentBeg; + do + { + auto i = segmentEnd; + char32_t codepoint = beg[i++]; + if (til::is_leading_surrogate(codepoint) && i < len) + { + codepoint = til::combine_surrogates(codepoint, beg[i++]); + } + + const auto c = BuiltinGlyphs::IsBuiltinGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint); + if (custom != c) + { + break; + } + + segmentEnd = i; + } while (segmentEnd < len); + + if (segmentBeg != segmentEnd) + { + if (custom) + { + _mapBuiltinGlyphs(segmentBeg, segmentEnd); + } + else + { + _mapRegularText(segmentBeg, segmentEnd); + } + } + + segmentBeg = segmentEnd; + custom = !custom; + } +} + +void AtlasEngine::_mapRegularText(size_t offBeg, size_t offEnd) +{ auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y]; -#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5). - for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd) + for (u32 idx = gsl::narrow_cast(offBeg), mappedEnd = 0; idx < offEnd; idx = mappedEnd) { u32 mappedLength = 0; wil::com_ptr mappedFontFace; - _mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast(_api.bufferLine.size()) - idx, &mappedLength, mappedFontFace.addressof()); + _mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast(offEnd - idx), &mappedLength, mappedFontFace.addressof()); mappedEnd = idx + mappedLength; if (!mappedFontFace) @@ -697,7 +755,7 @@ void AtlasEngine::_flushBufferLine() _api.glyphProps = Buffer{ size }; } - if (_api.s->font->fontFeatures.empty()) + if (_p.s->font->fontFeatures.empty()) { // We can reuse idx here, as it'll be reset to "idx = mappedEnd" in the outer loop anyways. for (u32 complexityLength = 0; idx < mappedEnd; idx += complexityLength) @@ -712,10 +770,10 @@ void AtlasEngine::_flushBufferLine() for (size_t i = 0; i < complexityLength; ++i) { - const size_t col1 = _api.bufferLineColumn[idx + i + 0]; - const size_t col2 = _api.bufferLineColumn[idx + i + 1]; + const auto col1 = _api.bufferLineColumn[idx + i + 0]; + const auto col2 = _api.bufferLineColumn[idx + i + 1]; const auto glyphAdvance = (col2 - col1) * _p.s->font->cellSize.x; - const auto fg = colors[col1 << shift]; + const auto fg = colors[static_cast(col1) << shift]; row.glyphIndices.emplace_back(_api.glyphIndices[i]); row.glyphAdvances.emplace_back(static_cast(glyphAdvance)); row.glyphOffsets.emplace_back(); @@ -750,9 +808,31 @@ void AtlasEngine::_flushBufferLine() } } +void AtlasEngine::_mapBuiltinGlyphs(size_t offBeg, size_t offEnd) +{ + auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y]; + auto initialIndicesCount = row.glyphIndices.size(); + const auto shift = gsl::narrow_cast(row.lineRendition != LineRendition::SingleWidth); + const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y; + const auto base = reinterpret_cast(_api.bufferLine.data()); + const auto len = offEnd - offBeg; + + row.glyphIndices.insert(row.glyphIndices.end(), base + offBeg, base + offEnd); + row.glyphAdvances.insert(row.glyphAdvances.end(), len, static_cast(_p.s->font->cellSize.x)); + row.glyphOffsets.insert(row.glyphOffsets.end(), len, {}); + + for (size_t i = offBeg; i < offEnd; ++i) + { + const auto col = _api.bufferLineColumn[i]; + row.colors.emplace_back(colors[static_cast(col) << shift]); + } + + row.mappings.emplace_back(nullptr, gsl::narrow_cast(initialIndicesCount), gsl::narrow_cast(row.glyphIndices.size())); +} + void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const { - TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), text, textLength }; + TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), text, textLength }; const auto& textFormatAxis = _api.textFormatAxes[static_cast(_api.attributes)]; // We don't read from scale anyways. @@ -807,7 +887,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len { _api.analysisResults.clear(); - TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow(_api.bufferLine.size()) }; + TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow(_api.bufferLine.size()) }; TextAnalysisSink analysisSink{ _api.analysisResults }; THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink)); @@ -852,7 +932,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len /* isSideways */ false, /* isRightToLeft */ 0, /* scriptAnalysis */ &a.analysis, - /* localeName */ _api.userLocaleName.c_str(), + /* localeName */ _p.userLocaleName.c_str(), /* numberSubstitution */ nullptr, /* features */ &features, /* featureRangeLengths */ &featureRangeLengths, @@ -904,7 +984,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len /* isSideways */ false, /* isRightToLeft */ 0, /* scriptAnalysis */ &a.analysis, - /* localeName */ _api.userLocaleName.c_str(), + /* localeName */ _p.userLocaleName.c_str(), /* features */ &features, /* featureRangeLengths */ &featureRangeLengths, /* featureRanges */ featureRanges, @@ -979,53 +1059,31 @@ void AtlasEngine::_mapReplacementCharacter(u32 from, u32 to, ShapedRow& row) return; } - auto pos1 = from; - auto pos2 = pos1; - size_t col1 = _api.bufferLineColumn[from]; - size_t col2 = col1; + auto pos = from; + auto col1 = _api.bufferLineColumn[from]; auto initialIndicesCount = row.glyphIndices.size(); - const auto softFontAvailable = !_p.s->font->softFontPattern.empty(); - auto currentlyMappingSoftFont = isSoftFontChar(_api.bufferLine[pos1]); const auto shift = gsl::narrow_cast(row.lineRendition != LineRendition::SingleWidth); const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y; - while (pos2 < to) + while (pos < to) { - col2 = _api.bufferLineColumn[++pos2]; + const auto col2 = _api.bufferLineColumn[++pos]; if (col1 == col2) { continue; } - const auto cols = col2 - col1; - const auto ch = static_cast(_api.bufferLine[pos1]); - const auto nowMappingSoftFont = isSoftFontChar(ch); - - row.glyphIndices.emplace_back(nowMappingSoftFont ? ch : _api.replacementCharacterGlyphIndex); - row.glyphAdvances.emplace_back(static_cast(cols * _p.s->font->cellSize.x)); + row.glyphIndices.emplace_back(_api.replacementCharacterGlyphIndex); + row.glyphAdvances.emplace_back(static_cast((col2 - col1) * _p.s->font->cellSize.x)); row.glyphOffsets.emplace_back(); - row.colors.emplace_back(colors[col1 << shift]); - - if (currentlyMappingSoftFont != nowMappingSoftFont) - { - const auto indicesCount = row.glyphIndices.size(); - const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get(); - - if (indicesCount > initialIndicesCount) - { - row.mappings.emplace_back(fontFace, gsl::narrow_cast(initialIndicesCount), gsl::narrow_cast(indicesCount)); - initialIndicesCount = indicesCount; - } - } + row.colors.emplace_back(colors[static_cast(col1) << shift]); - pos1 = pos2; col1 = col2; - currentlyMappingSoftFont = nowMappingSoftFont; } { const auto indicesCount = row.glyphIndices.size(); - const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get(); + const auto fontFace = _api.replacementCharacterFontFace.get(); if (indicesCount > initialIndicesCount) { diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 8d6d76795c0..a32fa4dc2fc 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -86,6 +86,8 @@ namespace Microsoft::Console::Render::Atlas void _recreateFontDependentResources(); void _recreateCellCountDependentResources(); void _flushBufferLine(); + void _mapRegularText(size_t offBeg, size_t offEnd); + void _mapBuiltinGlyphs(size_t offBeg, size_t offEnd); void _mapCharacters(const wchar_t* text, u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const; void _mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row); ATLAS_ATTR_COLD void _mapReplacementCharacter(u32 from, u32 to, ShapedRow& row); @@ -117,6 +119,18 @@ namespace Microsoft::Console::Render::Atlas std::unique_ptr _b; RenderingPayload _p; + // _p.s->font->builtinGlyphs is the setting which decides whether we should map box drawing glyphs to + // our own builtin versions. There's just one problem: BackendD2D doesn't have this functionality. + // But since AtlasEngine shapes the text before it's handed to the backends, it would need to know + // whether BackendD2D is in use, before BackendD2D even exists. These two flags solve the issue + // by triggering a complete, immediate redraw whenever the backend type changes. + // + // The proper solution is to move text shaping into the backends. + // Someone just needs to write a generic "TextBuffer to DWRITE_GLYPH_RUN" function. + bool _hackIsBackendD2D = false; + bool _hackWantsBuiltinGlyphs = true; + bool _hackTriggerRedrawAll = false; + struct ApiState { GenerationalSettings s = DirtyGenerationalSettings(); @@ -133,8 +147,6 @@ namespace Microsoft::Console::Render::Atlas std::vector bufferLine; std::vector bufferLineColumn; - std::wstring userLocaleName; - std::array, 4> textFormatAxes; std::vector analysisResults; Buffer clusterMap; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 92649553245..0c546a8dceb 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -32,7 +32,7 @@ using namespace Microsoft::Console::Render::Atlas; [[nodiscard]] HRESULT AtlasEngine::Present() noexcept try { - if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent()) + if (!_p.dxgi.adapter) { _recreateAdapter(); } @@ -77,7 +77,7 @@ CATCH_RETURN() [[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept { - return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()); + return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()) || _hackTriggerRedrawAll; } void AtlasEngine::WaitUntilCanRender() noexcept @@ -276,12 +276,14 @@ void AtlasEngine::_recreateBackend() _b = std::make_unique(_p); } - // !!! NOTE !!! - // Normally the viewport is indirectly marked as dirty by `AtlasEngine::_handleSettingsUpdate()` whenever - // the settings change, but the `!_p.dxgi.factory->IsCurrent()` check is not part of the settings change - // flow and so we have to manually recreate how AtlasEngine.cpp marks viewports as dirty here. - // This ensures that the backends redraw their entire viewports whenever a new swap chain is created. + // This ensures that the backends redraw their entire viewports whenever a new swap chain is created, + // EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.). _p.MarkAllAsDirty(); + + const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode; + _hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs; + _hackIsBackendD2D = d2dMode; + _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; } void AtlasEngine::_handleSwapChainUpdate() @@ -319,13 +321,15 @@ void AtlasEngine::_createSwapChain() // 3 buffers seems to guarantee a stable framerate at display frequency at all times. .BufferCount = 3, .Scaling = DXGI_SCALING_NONE, - // DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers - // lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive. + // DXGI_SWAP_EFFECT_FLIP_DISCARD is the easiest to use, because it's fast and uses little memory. + // But it's a mode that was created at a time were display drivers lacked support + // for Multiplane Overlays (MPO) and were copying buffers was expensive. // This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content. // With faster GPU memory in general and with support for MPO in particular this isn't // really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a // more "intelligent" composition and display updates to occur like Panel Self Refresh // (PSR) which requires dirty rectangles (Present1 API) to work correctly. + // We were asked by DWM folks to use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL for this reason (PSR). .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index 0ca325a5383..9f2736eceef 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -74,11 +74,6 @@ namespace Microsoft::Console::Render::Atlas return val < min ? min : (max < val ? max : val); } - constexpr bool isSoftFontChar(wchar_t ch) noexcept - { - return ch >= 0xEF20 && ch < 0xEF80; - } - inline constexpr D2D1_RECT_F GlyphRunEmptyBounds{ 1e38f, 1e38f, -1e38f, -1e38f }; void GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 221cf3b5ba3..dd4dcbf0c22 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -4,11 +4,14 @@ #include "pch.h" #include "BackendD3D.h" +#include + #include #include #include #include +#include "BuiltinGlyphs.h" #include "dwrite.h" #include "../../types/inc/ColorFix.hpp" @@ -42,45 +45,8 @@ TIL_FAST_MATH_BEGIN using namespace Microsoft::Console::Render::Atlas; -template<> -struct std::hash -{ - constexpr size_t operator()(u16 key) const noexcept - { - return til::flat_set_hash_integer(key); - } -}; - -template<> -struct std::hash -{ - constexpr size_t operator()(u16 key) const noexcept - { - return til::flat_set_hash_integer(key); - } - - constexpr size_t operator()(const BackendD3D::AtlasGlyphEntry& slot) const noexcept - { - return til::flat_set_hash_integer(slot.glyphIndex); - } -}; - -template<> -struct std::hash -{ - using T = BackendD3D::AtlasFontFaceEntry; - - size_t operator()(const BackendD3D::AtlasFontFaceKey& key) const noexcept - { - return til::flat_set_hash_integer(std::bit_cast(key.fontFace) | static_cast(key.lineRendition)); - } - - size_t operator()(const BackendD3D::AtlasFontFaceEntry& slot) const noexcept - { - const auto& inner = *slot.inner; - return til::flat_set_hash_integer(std::bit_cast(inner.fontFace.get()) | static_cast(inner.lineRendition)); - } -}; +static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 }; +static constexpr D2D1_COLOR_F whiteColor{ 1, 1, 1, 1 }; BackendD3D::BackendD3D(const RenderingPayload& p) { @@ -329,7 +295,11 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) _fontChangedResetGlyphAtlas = true; _textShadingType = font.antialiasingMode == AntialiasingMode::ClearType ? ShadingType::TextClearType : ShadingType::TextGrayscale; + // _ligatureOverhangTriggerLeft/Right are essentially thresholds for a glyph's width at + // which point we consider it wider than allowed and "this looks like a coding ligature". + // See _drawTextOverlapSplit for more information about what this does. { + // No ligatures -> No thresholds. auto ligaturesDisabled = false; for (const auto& feature : font.fontFeatures) { @@ -752,11 +722,15 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p) // It's not great, but it's not terrible. for (auto& slot : _glyphAtlasMap.container()) { - if (slot.inner) + for (auto& glyphs : slot.glyphs) { - slot.inner->glyphs.clear(); + glyphs.clear(); } } + for (auto& glyphs : _builtinGlyphs.glyphs) + { + glyphs.clear(); + } _d2dBeginDrawing(); _d2dRenderTarget->Clear(); @@ -797,9 +771,6 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const _d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof()); _d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS); - // We don't really use D2D for anything except DWrite, but it - // can't hurt to ensure that everything it does is pixel aligned. - _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // Ensure that D2D uses the exact same gamma as our shader uses. _d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get()); @@ -821,9 +792,8 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const } { - static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 }; - THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _emojiBrush.put())); - THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _brush.put())); + THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _emojiBrush.put())); + THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _brush.put())); } ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() }; @@ -955,7 +925,7 @@ void BackendD3D::_drawBackground(const RenderingPayload& p) } _appendQuad() = { - .shadingType = ShadingType::Background, + .shadingType = static_cast(ShadingType::Background), .size = p.s->targetSize, }; } @@ -1013,65 +983,65 @@ void BackendD3D::_drawText(RenderingPayload& p) for (const auto& m : row->mappings) { auto x = m.glyphsFrom; - const AtlasFontFaceKey fontFaceKey{ - .fontFace = m.fontFace.get(), - .lineRendition = row->lineRendition, - }; + const auto glyphsTo = m.glyphsTo; + const auto fontFace = m.fontFace.get(); - // This goto label exists to allow us to retry rendering a glyph if the glyph atlas was full. - // We need to goto here, because a retry will cause the atlas texture as well as the - // _glyphCache hashmap to be cleared, and so we'll have to call insert() again. - drawGlyphRetry: - const auto [fontFaceEntryOuter, fontFaceInserted] = _glyphAtlasMap.insert(fontFaceKey); - auto& fontFaceEntry = *fontFaceEntryOuter.inner; - - if (fontFaceInserted) + // The lack of a fontFace indicates a soft font. + AtlasFontFaceEntry* fontFaceEntry = &_builtinGlyphs; + if (fontFace) [[likely]] { - _initializeFontFaceEntry(fontFaceEntry); + fontFaceEntry = _glyphAtlasMap.insert(fontFace).first; } - while (x < m.glyphsTo) + const auto& glyphs = fontFaceEntry->glyphs[WI_EnumValue(row->lineRendition)]; + + while (x < glyphsTo) { - const auto [glyphEntry, inserted] = fontFaceEntry.glyphs.insert(row->glyphIndices[x]); + size_t dx = 1; + u32 glyphIndex = row->glyphIndices[x]; - if (inserted && !_drawGlyph(p, fontFaceEntry, glyphEntry)) + // Note: !fontFace is only nullptr for builtin glyphs which then use glyphIndices for UTF16 code points. + // In other words, this doesn't accidentally corrupt any actual glyph indices. + if (!fontFace && til::is_leading_surrogate(glyphIndex)) { - // A deadlock in this retry loop is detected in _drawGlyphPrepareRetry. - // - // Yes, I agree, avoid goto. Sometimes. It's not my fault that C++ still doesn't - // have a `continue outerloop;` like other languages had it for decades. :( -#pragma warning(suppress : 26438) // Avoid 'goto' (es.76). -#pragma warning(suppress : 26448) // Consider using gsl::finally if final action is intended (gsl.util). - goto drawGlyphRetry; + glyphIndex = til::combine_surrogates(glyphIndex, row->glyphIndices[x + 1]); + dx = 2; } - if (glyphEntry.data.shadingType != ShadingType::Default) + auto glyphEntry = glyphs.lookup(glyphIndex); + if (!glyphEntry) + { + glyphEntry = _drawGlyph(p, *row, *fontFaceEntry, glyphIndex); + } + + // A shadingType of 0 (ShadingType::Default) indicates a glyph that is whitespace. + if (glyphEntry->shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); - l += glyphEntry.data.offset.x; - t += glyphEntry.data.offset.y; + l += glyphEntry->offset.x; + t += glyphEntry->offset.y; row->dirtyTop = std::min(row->dirtyTop, t); - row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); + row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry->size.y); _appendQuad() = { - .shadingType = glyphEntry.data.shadingType, + .shadingType = static_cast(glyphEntry->shadingType), .position = { static_cast(l), static_cast(t) }, - .size = glyphEntry.data.size, - .texcoord = glyphEntry.data.texcoord, + .size = glyphEntry->size, + .texcoord = glyphEntry->texcoord, .color = row->colors[x], }; - if (glyphEntry.data.overlapSplit) + if (glyphEntry->overlapSplit) { _drawTextOverlapSplit(p, y); } } baselineX += row->glyphAdvances[x]; - ++x; + x += dx; } } @@ -1189,71 +1159,22 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y) } } -void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry) -{ - if (!fontFaceEntry.fontFace) - { - return; - } - - ALLOW_UNINITIALIZED_BEGIN - std::array codepoints; - std::array indices; - ALLOW_UNINITIALIZED_END - - for (u32 i = 0; i < codepoints.size(); ++i) - { - codepoints[i] = 0x2500 + i; - } - - THROW_IF_FAILED(fontFaceEntry.fontFace->GetGlyphIndicesW(codepoints.data(), codepoints.size(), indices.data())); - - for (u32 i = 0; i < indices.size(); ++i) - { - if (const auto idx = indices[i]) - { - fontFaceEntry.boxGlyphs.insert(idx); - } - } -} - -bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { + // The lack of a fontFace indicates a soft font. if (!fontFaceEntry.fontFace) { - return _drawSoftFontGlyph(p, fontFaceEntry, glyphEntry); + return _drawBuiltinGlyph(p, row, fontFaceEntry, glyphIndex); } + const auto glyphIndexU16 = static_cast(glyphIndex); const DWRITE_GLYPH_RUN glyphRun{ .fontFace = fontFaceEntry.fontFace.get(), .fontEmSize = p.s->font->fontSize, .glyphCount = 1, - .glyphIndices = &glyphEntry.glyphIndex, + .glyphIndices = &glyphIndexU16, }; - // To debug issues with this function it may be helpful to know which file - // a given font face corresponds to. This code works for most cases. -#if 0 - wchar_t filePath[MAX_PATH]{}; - { - UINT32 fileCount = 1; - wil::com_ptr file; - if (SUCCEEDED(fontFaceEntry.fontFace->GetFiles(&fileCount, file.addressof()))) - { - wil::com_ptr loader; - THROW_IF_FAILED(file->GetLoader(loader.addressof())); - - if (const auto localLoader = loader.try_query()) - { - void const* fontFileReferenceKey; - UINT32 fontFileReferenceKeySize; - THROW_IF_FAILED(file->GetReferenceKey(&fontFileReferenceKey, &fontFileReferenceKeySize)); - THROW_IF_FAILED(localLoader->GetFilePathFromKey(fontFileReferenceKey, fontFileReferenceKeySize, &filePath[0], MAX_PATH)); - } - } - } -#endif - // It took me a while to figure out how to rasterize glyphs manually with DirectWrite without depending on Direct2D. // The benefits are a reduction in memory usage, an increase in performance under certain circumstances and most // importantly, the ability to debug the renderer more easily, because many graphics debuggers don't support Direct2D. @@ -1322,16 +1243,13 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI // The buffer now contains a grayscale alpha mask. #endif - const auto lineRendition = static_cast(fontFaceEntry.lineRendition); - const auto needsTransform = lineRendition != LineRendition::SingleWidth; - - static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 }; + const int scale = row.lineRendition != LineRendition::SingleWidth; D2D1_MATRIX_3X2_F transform = identityTransform; - if (needsTransform) + if (scale) { transform.m11 = 2.0f; - transform.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f; + transform.m22 = row.lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f; _d2dRenderTarget->SetTransform(&transform); } @@ -1387,26 +1305,10 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI } } - // Overhangs for box glyphs can produce unsightly effects, where the antialiased edges of horizontal - // and vertical lines overlap between neighboring glyphs and produce "boldened" intersections. - // It looks a little something like this: - // ---+---+--- - // This avoids the issue in most cases by simply clipping the glyph to the size of a single cell. - // The downside is that it fails to work well for custom line heights, etc. - const auto isBoxGlyph = fontFaceEntry.boxGlyphs.lookup(glyphEntry.glyphIndex) != nullptr; - if (isBoxGlyph) - { - // NOTE: As mentioned above, the "origin" of a glyph's coordinate system is its baseline. - bounds.left = std::max(bounds.left, 0.0f); - bounds.top = std::max(bounds.top, static_cast(-p.s->font->baseline) * transform.m22); - bounds.right = std::min(bounds.right, static_cast(p.s->font->cellSize.x) * transform.m11); - bounds.bottom = std::min(bounds.bottom, static_cast(p.s->font->descender) * transform.m22); - } - // The bounds may be empty if the glyph is whitespace. if (bounds.left >= bounds.right || bounds.top >= bounds.bottom) { - return true; + return _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); } const auto bl = lrintf(bounds.left); @@ -1418,37 +1320,15 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI .w = br - bl, .h = bb - bt, }; - if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) - { - _drawGlyphPrepareRetry(p); - return false; - } + _drawGlyphAtlasAllocate(p, rect); + _d2dBeginDrawing(); const D2D1_POINT_2F baselineOrigin{ static_cast(rect.x - bl), static_cast(rect.y - bt), }; - _d2dBeginDrawing(); - - if (isBoxGlyph) - { - const D2D1_RECT_F clipRect{ - static_cast(rect.x) / transform.m11, - static_cast(rect.y) / transform.m22, - static_cast(rect.x + rect.w) / transform.m11, - static_cast(rect.y + rect.h) / transform.m22, - }; - _d2dRenderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - } - const auto boxGlyphCleanup = wil::scope_exit([&]() { - if (isBoxGlyph) - { - _d2dRenderTarget->PopAxisAlignedClip(); - } - }); - - if (needsTransform) + if (scale) { transform.dx = (1.0f - transform.m11) * baselineOrigin.x; transform.dy = (1.0f - transform.m22) * baselineOrigin.y; @@ -1474,56 +1354,85 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI // // The former condition makes sure to exclude diacritics and such from being considered a ligature, // while the latter condition-pair makes sure to exclude regular BMP wide glyphs that overlap a little. - const auto horizontalScale = lineRendition != LineRendition::SingleWidth ? 2 : 1; - const auto triggerLeft = _ligatureOverhangTriggerLeft * horizontalScale; - const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; + const auto triggerLeft = _ligatureOverhangTriggerLeft << scale; + const auto triggerRight = _ligatureOverhangTriggerRight << scale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; - glyphEntry.data.overlapSplit = overlapSplit; - glyphEntry.data.offset.x = bl; - glyphEntry.data.offset.y = bt; - glyphEntry.data.size.x = rect.w; - glyphEntry.data.size.y = rect.h; - glyphEntry.data.texcoord.x = rect.x; - glyphEntry.data.texcoord.y = rect.y; + const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); + glyphEntry->shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; + glyphEntry->overlapSplit = overlapSplit; + glyphEntry->offset.x = bl; + glyphEntry->offset.y = bt; + glyphEntry->size.x = rect.w; + glyphEntry->size.y = rect.h; + glyphEntry->texcoord.x = rect.x; + glyphEntry->texcoord.y = rect.y; - if (lineRendition >= LineRendition::DoubleHeightTop) + if (row.lineRendition >= LineRendition::DoubleHeightTop) { - _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry); + _splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry); } - return true; + return glyphEntry; } -bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { + auto baseline = p.s->font->baseline; stbrp_rect rect{ .w = p.s->font->cellSize.x, .h = p.s->font->cellSize.y, }; - - const auto lineRendition = static_cast(fontFaceEntry.lineRendition); - auto baseline = p.s->font->baseline; - - if (lineRendition != LineRendition::SingleWidth) + if (row.lineRendition != LineRendition::SingleWidth) { - const auto heightShift = static_cast(lineRendition >= LineRendition::DoubleHeightTop); + const auto heightShift = static_cast(row.lineRendition >= LineRendition::DoubleHeightTop); rect.w <<= 1; rect.h <<= heightShift; baseline <<= heightShift; } - if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) + _drawGlyphAtlasAllocate(p, rect); + _d2dBeginDrawing(); + + if (BuiltinGlyphs::IsSoftFontChar(glyphIndex)) { - _drawGlyphPrepareRetry(p); - return false; + _drawSoftFontGlyph(p, rect, glyphIndex); + } + else + { + const D2D1_RECT_F r{ + static_cast(rect.x), + static_cast(rect.y), + static_cast(rect.x + rect.w), + static_cast(rect.y + rect.h), + }; + BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); + } + + const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); + glyphEntry->shadingType = ShadingType::TextGrayscale; + glyphEntry->overlapSplit = 0; + glyphEntry->offset.x = 0; + glyphEntry->offset.y = -baseline; + glyphEntry->size.x = rect.w; + glyphEntry->size.y = rect.h; + glyphEntry->texcoord.x = rect.x; + glyphEntry->texcoord.y = rect.y; + + if (row.lineRendition >= LineRendition::DoubleHeightTop) + { + _splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry); } + return glyphEntry; +} + +void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const stbrp_rect& rect, u32 glyphIndex) +{ if (!_softFontBitmap) { // Allocating such a tiny texture is very wasteful (min. texture size on GPUs - // right now is 64kB), but this is a seldom used feature so it's fine... + // right now is 64kB), but this is a seldomly used feature so it's fine... const D2D1_SIZE_U size{ static_cast(p.s->font->softFontCellSize.width), static_cast(p.s->font->softFontCellSize.height), @@ -1536,6 +1445,30 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa THROW_IF_FAILED(_d2dRenderTarget->CreateBitmap(size, nullptr, 0, &bitmapProperties, _softFontBitmap.addressof())); } + { + const auto width = static_cast(p.s->font->softFontCellSize.width); + const auto height = static_cast(p.s->font->softFontCellSize.height); + + auto bitmapData = Buffer{ width * height }; + const auto softFontIndex = glyphIndex - 0xEF20u; + auto src = p.s->font->softFontPattern.begin() + height * softFontIndex; + auto dst = bitmapData.begin(); + + for (size_t y = 0; y < height; y++) + { + auto srcBits = *src++; + for (size_t x = 0; x < width; x++) + { + const auto srcBitIsSet = (srcBits & 0x8000) != 0; + *dst++ = srcBitIsSet ? 0xffffffff : 0x00000000; + srcBits <<= 1; + } + } + + const auto pitch = static_cast(width * sizeof(u32)); + THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch)); + } + const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; const D2D1_RECT_F dest{ static_cast(rect.x), @@ -1545,118 +1478,67 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa }; _d2dBeginDrawing(); - _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - - glyphEntry.data.shadingType = ShadingType::TextGrayscale; - glyphEntry.data.overlapSplit = 0; - glyphEntry.data.offset.x = 0; - glyphEntry.data.offset.y = -baseline; - glyphEntry.data.size.x = rect.w; - glyphEntry.data.size.y = rect.h; - glyphEntry.data.texcoord.x = rect.x; - glyphEntry.data.texcoord.y = rect.y; - - if (lineRendition >= LineRendition::DoubleHeightTop) - { - _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry); - } - - return true; } -void BackendD3D::_drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const +void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect) { - if (!isSoftFontChar(glyphEntry.glyphIndex)) - { - // AtlasEngine::_mapReplacementCharacter should have filtered inputs with isSoftFontChar already. - assert(false); - return; - } - - const auto width = static_cast(p.s->font->softFontCellSize.width); - const auto height = static_cast(p.s->font->softFontCellSize.height); - const auto& softFontPattern = p.s->font->softFontPattern; - - // The isSoftFontChar() range is [0xEF20,0xEF80). - const auto offset = glyphEntry.glyphIndex - 0xEF20u; - const auto offsetBeg = offset * height; - const auto offsetEnd = offsetBeg + height; - - if (offsetEnd > softFontPattern.size()) + if (stbrp_pack_rects(&_rectPacker, &rect, 1)) { - // Out of range values should not occur, but they do and it's unknown why: GH#15553 - assert(false); return; } - Buffer bitmapData{ width * height }; - auto dst = bitmapData.begin(); - auto it = softFontPattern.begin() + offsetBeg; - const auto end = softFontPattern.begin() + offsetEnd; + _d2dEndDrawing(); + _flushQuads(p); + _resetGlyphAtlas(p); - while (it != end) + if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) { - auto srcBits = *it++; - for (size_t x = 0; x < width; x++) - { - const auto srcBitIsSet = (srcBits & 0x8000) != 0; - *dst++ = srcBitIsSet ? 0xffffffff : 0x00000000; - srcBits <<= 1; - } + THROW_HR(HRESULT_FROM_WIN32(ERROR_POSSIBLE_DEADLOCK)); } - - const auto pitch = static_cast(width * sizeof(u32)); - THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch)); } -void BackendD3D::_drawGlyphPrepareRetry(const RenderingPayload& p) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { - THROW_HR_IF_MSG(E_UNEXPECTED, _glyphAtlasMap.empty(), "BackendD3D::_drawGlyph deadlock"); - _d2dEndDrawing(); - _flushQuads(p); - _resetGlyphAtlas(p); + const auto glyphEntry = fontFaceEntry.glyphs[WI_EnumValue(row.lineRendition)].insert(glyphIndex).first; + glyphEntry->shadingType = ShadingType::Default; + return glyphEntry; } // If this is a double-height glyph (DECDHL), we need to split it into 2 glyph entries: // One for the top/bottom half each, because that's how DECDHL works. This will clip the // `glyphEntry` to only contain the one specified by `fontFaceEntry.lineRendition` // and create a second entry in our glyph cache hashmap that contains the other half. -void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry) { // Twice the line height, twice the descender gap. For both. - glyphEntry.data.offset.y -= p.s->font->descender; + glyphEntry->offset.y -= p.s->font->descender; - const auto isTop = fontFaceEntry.lineRendition == LineRendition::DoubleHeightTop; - - const AtlasFontFaceKey key2{ - .fontFace = fontFaceEntry.fontFace.get(), - .lineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop, - }; + const auto isTop = row.lineRendition == LineRendition::DoubleHeightTop; + const auto otherLineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop; + const auto entry2 = fontFaceEntry.glyphs[WI_EnumValue(otherLineRendition)].insert(glyphEntry->glyphIndex).first; - auto& glyphCache = _glyphAtlasMap.insert(key2).first.inner->glyphs; - auto& entry2 = glyphCache.insert(glyphEntry.glyphIndex).first; - entry2.data = glyphEntry.data; + *entry2 = *glyphEntry; - auto& top = isTop ? glyphEntry : entry2; - auto& bottom = isTop ? entry2 : glyphEntry; + const auto top = isTop ? glyphEntry : entry2; + const auto bottom = isTop ? entry2 : glyphEntry; + const auto topSize = clamp(-glyphEntry->offset.y - p.s->font->baseline, 0, static_cast(glyphEntry->size.y)); - const auto topSize = clamp(-glyphEntry.data.offset.y - p.s->font->baseline, 0, static_cast(glyphEntry.data.size.y)); - top.data.offset.y += p.s->font->cellSize.y; - top.data.size.y = topSize; - bottom.data.offset.y += topSize; - bottom.data.size.y = std::max(0, bottom.data.size.y - topSize); - bottom.data.texcoord.y += topSize; + top->offset.y += p.s->font->cellSize.y; + top->size.y = topSize; + bottom->offset.y += topSize; + bottom->size.y = std::max(0, bottom->size.y - topSize); + bottom->texcoord.y += topSize; // Things like diacritics might be so small that they only exist on either half of the // double-height row. This effectively turns the other (unneeded) side into whitespace. - if (!top.data.size.y) + if (!top->size.y) { - top.data.shadingType = ShadingType::Default; + top->shadingType = ShadingType::Default; } - if (!bottom.data.size.y) + if (!bottom->size.y) { - bottom.data.shadingType = ShadingType::Default; + bottom->shadingType = ShadingType::Default; } } @@ -1691,7 +1573,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) for (; posX < end; posX += textCellWidth) { _appendQuad() = { - .shadingType = ShadingType::SolidLine, + .shadingType = static_cast(ShadingType::SolidLine), .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, .color = r.gridlineColor, @@ -1713,7 +1595,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) if (rt < rb) { _appendQuad() = { - .shadingType = shadingType, + .shadingType = static_cast(shadingType), .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, @@ -1803,8 +1685,8 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p) } const i16x2 position{ - p.s->font->cellSize.x * x0, - p.s->font->cellSize.y * p.cursorRect.top, + static_cast(p.s->font->cellSize.x * x0), + static_cast(p.s->font->cellSize.y * p.cursorRect.top), }; const u16x2 size{ static_cast(p.s->font->cellSize.x * (x1 - x0)), @@ -1887,7 +1769,7 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p) for (const auto& c : _cursorRects) { _appendQuad() = { - .shadingType = ShadingType::Cursor, + .shadingType = static_cast(ShadingType::Cursor), .position = c.position, .size = c.size, .color = c.background, @@ -1911,7 +1793,7 @@ void BackendD3D::_drawCursorForeground() // start and end of this "block" here in advance. for (; instancesOffset < instancesCount; ++instancesOffset) { - const auto shadingType = _instances[instancesOffset].shadingType; + const auto shadingType = static_cast(_instances[instancesOffset].shadingType); if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast) { break; @@ -1931,7 +1813,7 @@ void BackendD3D::_drawCursorForeground() // Now do the same thing as above, but backwards from the end. for (; instancesCount > instancesOffset; --instancesCount) { - const auto shadingType = _instances[instancesCount - 1].shadingType; + const auto shadingType = static_cast(_instances[instancesCount - 1].shadingType); if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast) { break; @@ -1990,7 +1872,7 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off // There's one special exception to the rule: Emojis. We currently don't really support inverting // (or reversing) colored glyphs like that, so we can return early here and avoid cutting them up. // It'd be too expensive to check for these rare glyph types inside the _drawCursorForeground() loop. - if (it.shadingType == ShadingType::TextPassthrough) + if (static_cast(it.shadingType) == ShadingType::TextPassthrough) { return 0; } @@ -2121,14 +2003,14 @@ void BackendD3D::_drawSelection(const RenderingPayload& p) else { _appendQuad() = { - .shadingType = ShadingType::Selection, + .shadingType = static_cast(ShadingType::Selection), .position = { - p.s->font->cellSize.x * row->selectionFrom, - p.s->font->cellSize.y * y, + static_cast(p.s->font->cellSize.x * row->selectionFrom), + static_cast(p.s->font->cellSize.y * y), }, .size = { static_cast(p.s->font->cellSize.x * (row->selectionTo - row->selectionFrom)), - p.s->font->cellSize.y, + static_cast(p.s->font->cellSize.y), }, .color = p.s->misc->selectionColor, }; diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index cdf9937cc61..3fddb161d49 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -57,7 +57,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u16 + enum class ShadingType : u8 // u8 so that it packs perfectly into AtlasGlyphEntry { Default = 0, Background = 0, @@ -89,7 +89,7 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u16) ShadingType shadingType; + alignas(u16) u16 shadingType; // not using ShadingType here to avoid struct padding which causes optimization failures with MSVC alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; @@ -97,8 +97,12 @@ namespace Microsoft::Console::Render::Atlas alignas(u32) u32 color; }; - struct alignas(u32) AtlasGlyphEntryData + // NOTE: Don't initialize any members in this struct. This ensures that no + // zero-initialization needs to occur when we allocate large buffers of this object. + struct AtlasGlyphEntry { + u32 glyphIndex; + u8 occupied; ShadingType shadingType; u16 overlapSplit; i16x2 offset; @@ -106,80 +110,71 @@ namespace Microsoft::Console::Render::Atlas u16x2 texcoord; }; - // NOTE: Don't initialize any members in this struct. This ensures that no - // zero-initialization needs to occur when we allocate large buffers of this object. - struct AtlasGlyphEntry + struct AtlasGlyphEntryHashTrait { - u16 glyphIndex; - // All data in QuadInstance is u32-aligned anyways, so this simultaneously serves as padding. - u16 _occupied; - - AtlasGlyphEntryData data; + static constexpr bool occupied(const AtlasGlyphEntry& entry) noexcept + { + return entry.occupied != 0; + } - constexpr bool operator==(u16 key) const noexcept + static constexpr size_t hash(const u16 glyphIndex) noexcept { - return glyphIndex == key; + return til::flat_set_hash_integer(glyphIndex); } - constexpr operator bool() const noexcept + static constexpr size_t hash(const AtlasGlyphEntry& entry) noexcept { - return _occupied != 0; + return til::flat_set_hash_integer(entry.glyphIndex); } - constexpr AtlasGlyphEntry& operator=(u16 key) noexcept + static constexpr bool equals(const AtlasGlyphEntry& entry, u16 glyphIndex) noexcept { - glyphIndex = key; - _occupied = 1; - return *this; + return entry.glyphIndex == glyphIndex; } - }; - // This exists so that we can look up a AtlasFontFaceEntry without AddRef()/Release()ing fontFace first. - struct AtlasFontFaceKey - { - IDWriteFontFace2* fontFace; - LineRendition lineRendition; + static constexpr void assign(AtlasGlyphEntry& entry, u16 glyphIndex) noexcept + { + entry.glyphIndex = glyphIndex; + entry.occupied = 1; + } }; - struct AtlasFontFaceEntryInner + struct AtlasFontFaceEntry { // BODGY: At the time of writing IDWriteFontFallback::MapCharacters returns the same IDWriteFontFace instance // for the same font face variant as long as someone is holding a reference to the instance (see ActiveFaceCache). // This allows us to hash the value of the pointer as if it was uniquely identifying the font face variant. wil::com_ptr fontFace; - LineRendition lineRendition = LineRendition::SingleWidth; - til::linear_flat_set glyphs; - // boxGlyphs gets an increased growth rate of 2^2 = 4x, because presumably fonts either contain very - // few or almost all of the box glyphs. This reduces the cost of _initializeFontFaceEntry quite a bit. - til::linear_flat_set boxGlyphs; + // The 4 entries map to the 4 corresponding LineRendition enum values. + til::linear_flat_set glyphs[4]; }; - struct AtlasFontFaceEntry + struct AtlasFontFaceEntryHashTrait { - // This being a heap allocated allows us to insert into `glyphs` in `_splitDoubleHeightGlyph` - // (which might resize the hashmap!), while the caller `_drawText` is holding onto `glyphs`. - // If it wasn't heap allocated, all pointers into `linear_flat_set` would be invalidated. - std::unique_ptr inner; + static bool occupied(const AtlasFontFaceEntry& entry) noexcept + { + return static_cast(entry.fontFace); + } + + static constexpr size_t hash(const IDWriteFontFace2* fontFace) noexcept + { + return til::flat_set_hash_integer(std::bit_cast(fontFace)); + } - bool operator==(const AtlasFontFaceKey& key) const noexcept + static size_t hash(const AtlasFontFaceEntry& entry) noexcept { - const auto& i = *inner; - return i.fontFace.get() == key.fontFace && i.lineRendition == key.lineRendition; + return hash(entry.fontFace.get()); } - operator bool() const noexcept + static bool equals(const AtlasFontFaceEntry& entry, const IDWriteFontFace2* fontFace) noexcept { - return static_cast(inner); + return entry.fontFace.get() == fontFace; } - AtlasFontFaceEntry& operator=(const AtlasFontFaceKey& key) + static void assign(AtlasFontFaceEntry& entry, IDWriteFontFace2* fontFace) noexcept { - inner = std::make_unique(); - auto& i = *inner; - i.fontFace = key.fontFace; - i.lineRendition = key.lineRendition; - return *this; + entry.fontFace = fontFace; } }; @@ -216,12 +211,12 @@ namespace Microsoft::Console::Render::Atlas void _uploadBackgroundBitmap(const RenderingPayload& p); void _drawText(RenderingPayload& p); ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y); - ATLAS_ATTR_COLD static void _initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry); - [[nodiscard]] ATLAS_ATTR_COLD bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); - bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); - void _drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const; - void _drawGlyphPrepareRetry(const RenderingPayload& p); - void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); + [[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 stbrp_rect& 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); void _drawGridlines(const RenderingPayload& p, u16 y); void _drawCursorBackground(const RenderingPayload& p); ATLAS_ATTR_COLD void _drawCursorForeground(); @@ -258,7 +253,8 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr _glyphAtlas; wil::com_ptr _glyphAtlasView; - til::linear_flat_set _glyphAtlasMap; + til::linear_flat_set _glyphAtlasMap; + AtlasFontFaceEntry _builtinGlyphs; Buffer _rectPackerData; stbrp_context _rectPacker{}; til::CoordType _ligatureOverhangTriggerLeft = 0; diff --git a/src/renderer/atlas/BuiltinGlyphs.cpp b/src/renderer/atlas/BuiltinGlyphs.cpp new file mode 100644 index 00000000000..541d5690324 --- /dev/null +++ b/src/renderer/atlas/BuiltinGlyphs.cpp @@ -0,0 +1,1276 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "BuiltinGlyphs.h" + +// Disable a bunch of warnings which get in the way of writing performant code. +#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23). +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). +#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1). +#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). +#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2). + +using namespace Microsoft::Console::Render::Atlas; +using namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs; + +union Instruction +{ + struct + { + u32 shape : 4; // Shape enum + u32 begX : 5; // Pos enum + u32 begY : 5; // Pos enum + u32 endX : 5; // Pos enum + u32 endY : 5; // Pos enum + }; + u32 value = 0; +}; +static_assert(sizeof(Instruction) == sizeof(u32)); + +static constexpr u32 InstructionsPerGlyph = 4; + +enum Shape : u32 +{ + Shape_Filled025, // axis aligned rectangle, 25% filled + Shape_Filled050, // axis aligned rectangle, 50% filled + Shape_Filled075, // axis aligned rectangle, 75% filled + Shape_Filled100, // axis aligned rectangle, 100% filled + Shape_LightLine, // 1/8th wide line + Shape_HeavyLine, // 1/4th wide line + Shape_EmptyRect, // axis aligned hollow rectangle + Shape_RoundRect, // axis aligned hollow, rounded rectangle + Shape_FilledEllipsis, // axis aligned, filled ellipsis + Shape_EmptyEllipsis, // axis aligned, hollow ellipsis + Shape_ClosedFilledPath, // filled path, the last segment connects to the first; set endX==Pos_Min to ignore + Shape_OpenLinePath, // regular line path; Pos_Min positions are ignored +}; + +// Pos indicates a fraction between 0 and 1 and is used as a UV coordinate with a cell. +// (0,0) is in the top-left corner. Some enum entries also contain a suffix. +// This suffix indicates an offset of that many times the line width, to be added to the position. +// This allows us to store 2 floats in just 5 bits and helps with keeping the Instruction tables compact. +enum Pos : u32 +{ + Pos_Min, + Pos_Max, + + Pos_0_1, + Pos_0_1_Add_0_5, + Pos_1_1, + Pos_1_1_Sub_0_5, + + Pos_1_2, + Pos_1_2_Sub_0_5, + Pos_1_2_Add_0_5, + Pos_1_2_Sub_1, + Pos_1_2_Add_1, + + Pos_1_4, + Pos_3_4, + + Pos_2_6, + Pos_3_6, + Pos_5_6, + + Pos_1_8, + Pos_3_8, + Pos_5_8, + Pos_7_8, + + Pos_2_9, + Pos_3_9, + Pos_5_9, + Pos_6_9, + Pos_8_9, + + Pos_2_12, + Pos_3_12, + Pos_5_12, + Pos_6_12, + Pos_8_12, + Pos_9_12, + Pos_11_12, +}; + +inline constexpr f32 Pos_Lut[][2] = { + /* Pos_Min */ { -0.5f, 0.0f }, + /* Pos_Max */ { 1.5f, 0.0f }, + + /* Pos_0_1 */ { 0.0f, 0.0f }, + /* Pos_0_1_Add_0_5 */ { 0.0f, 0.5f }, + /* Pos_1_1 */ { 1.0f, 0.0f }, + /* Pos_1_1_Sub_0_5 */ { 1.0f, -0.5f }, + + /* Pos_1_2 */ { 1.0f / 2.0f, 0.0f }, + /* Pos_1_2_Sub_0_5 */ { 1.0f / 2.0f, -0.5f }, + /* Pos_1_2_Add_0_5 */ { 1.0f / 2.0f, 0.5f }, + /* Pos_1_2_Sub_1 */ { 1.0f / 2.0f, -1.0f }, + /* Pos_1_2_Add_1 */ { 1.0f / 2.0f, 1.0f }, + + /* Pos_1_4 */ { 1.0f / 4.0f, 0.0f }, + /* Pos_3_4 */ { 3.0f / 4.0f, 0.0f }, + + /* Pos_2_6 */ { 2.0f / 6.0f, 0.0f }, + /* Pos_3_6 */ { 3.0f / 6.0f, 0.0f }, + /* Pos_5_6 */ { 5.0f / 6.0f, 0.0f }, + + /* Pos_1_8 */ { 1.0f / 8.0f, 0.0f }, + /* Pos_3_8 */ { 3.0f / 8.0f, 0.0f }, + /* Pos_5_8 */ { 5.0f / 8.0f, 0.0f }, + /* Pos_7_8 */ { 7.0f / 8.0f, 0.0f }, + + /* Pos_2_9 */ { 2.0f / 9.0f, 0.0f }, + /* Pos_3_9 */ { 3.0f / 9.0f, 0.0f }, + /* Pos_5_9 */ { 5.0f / 9.0f, 0.0f }, + /* Pos_6_9 */ { 6.0f / 9.0f, 0.0f }, + /* Pos_8_9 */ { 8.0f / 9.0f, 0.0f }, + + /* Pos_2_12 */ { 2.0f / 12.0f, 0.0f }, + /* Pos_3_12 */ { 3.0f / 12.0f, 0.0f }, + /* Pos_5_12 */ { 5.0f / 12.0f, 0.0f }, + /* Pos_6_12 */ { 6.0f / 12.0f, 0.0f }, + /* Pos_8_12 */ { 8.0f / 12.0f, 0.0f }, + /* Pos_9_12 */ { 9.0f / 12.0f, 0.0f }, + /* Pos_11_12 */ { 11.0f / 12.0f, 0.0f }, +}; + +static constexpr char32_t BoxDrawing_FirstChar = 0x2500; +static constexpr u32 BoxDrawing_CharCount = 0xA0; +static constexpr Instruction BoxDrawing[BoxDrawing_CharCount][InstructionsPerGlyph] = { + // U+2500 ─ BOX DRAWINGS LIGHT HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2501 ━ BOX DRAWINGS HEAVY HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2502 │ BOX DRAWINGS LIGHT VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2503 ┃ BOX DRAWINGS HEAVY VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2504 ┄ BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_9, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_9, Pos_1_2, Pos_5_9, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_6_9, Pos_1_2, Pos_8_9, Pos_1_2 }, + }, + // U+2505 ┅ BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_9, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_9, Pos_1_2, Pos_5_9, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_6_9, Pos_1_2, Pos_8_9, Pos_1_2 }, + }, + // U+2506 ┆ BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_9 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_9, Pos_1_2, Pos_5_9 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_6_9, Pos_1_2, Pos_8_9 }, + }, + // U+2507 ┇ BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_9 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_9, Pos_1_2, Pos_5_9 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_6_9, Pos_1_2, Pos_8_9 }, + }, + // U+2508 ┈ BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_12, Pos_1_2, Pos_5_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_6_12, Pos_1_2, Pos_8_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_9_12, Pos_1_2, Pos_11_12, Pos_1_2 }, + }, + // U+2509 ┉ BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_12, Pos_1_2, Pos_5_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_6_12, Pos_1_2, Pos_8_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_9_12, Pos_1_2, Pos_11_12, Pos_1_2 }, + }, + // U+250A ┊ BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_12, Pos_1_2, Pos_5_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_6_12, Pos_1_2, Pos_8_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_9_12, Pos_1_2, Pos_11_12 }, + }, + // U+250B ┋ BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_12, Pos_1_2, Pos_5_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_6_12, Pos_1_2, Pos_8_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_9_12, Pos_1_2, Pos_11_12 }, + }, + // U+250C ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250D ┍ BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250E ┎ BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250F ┏ BOX DRAWINGS HEAVY DOWN AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2510 ┐ BOX DRAWINGS LIGHT DOWN AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2511 ┑ BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2512 ┒ BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2513 ┓ BOX DRAWINGS HEAVY DOWN AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2514 └ BOX DRAWINGS LIGHT UP AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2515 ┕ BOX DRAWINGS UP LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2516 ┖ BOX DRAWINGS UP HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2517 ┗ BOX DRAWINGS HEAVY UP AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2518 ┘ BOX DRAWINGS LIGHT UP AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2519 ┙ BOX DRAWINGS UP LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251A ┚ BOX DRAWINGS UP HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251B ┛ BOX DRAWINGS HEAVY UP AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251C ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+251D ┝ BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+251E ┞ BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+251F ┟ BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2520 ┠ BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2521 ┡ BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2522 ┢ BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2523 ┣ BOX DRAWINGS HEAVY VERTICAL AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2524 ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2525 ┥ BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2526 ┦ BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2527 ┧ BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2528 ┨ BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2529 ┩ BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252A ┪ BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252B ┫ BOX DRAWINGS HEAVY VERTICAL AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+252C ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252D ┭ BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252E ┮ BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252F ┯ BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2530 ┰ BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2531 ┱ BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2532 ┲ BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2533 ┳ BOX DRAWINGS HEAVY DOWN AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2534 ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2535 ┵ BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2536 ┶ BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2537 ┷ BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2538 ┸ BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2539 ┹ BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253A ┺ BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253B ┻ BOX DRAWINGS HEAVY UP AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253C ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253D ┽ BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253E ┾ BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253F ┿ BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2540 ╀ BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2541 ╁ BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2542 ╂ BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2543 ╃ BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2544 ╄ BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2545 ╅ BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2546 ╆ BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2547 ╇ BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2548 ╈ BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2549 ╉ BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254A ╊ BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254B ╋ BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254C ╌ BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_6, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_6, Pos_1_2, Pos_5_6, Pos_1_2 }, + }, + // U+254D ╍ BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_6, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_6, Pos_1_2, Pos_5_6, Pos_1_2 }, + }, + // U+254E ╎ BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_6 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_6, Pos_1_2, Pos_5_6 }, + }, + // U+254F ╏ BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_6 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_6, Pos_1_2, Pos_5_6 }, + }, + // U+2550 ═ BOX DRAWINGS DOUBLE HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + }, + // U+2551 ║ BOX DRAWINGS DOUBLE VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + }, + // U+2552 ╒ BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1 }, + }, + // U+2553 ╓ BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2554 ╔ BOX DRAWINGS DOUBLE DOWN AND RIGHT + { + Instruction{ Shape_EmptyRect, Pos_1_2_Sub_1, Pos_1_2_Sub_1, Pos_Max, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2555 ╕ BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1 }, + }, + // U+2556 ╖ BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+2557 ╗ BOX DRAWINGS DOUBLE DOWN AND LEFT + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Add_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + }, + // U+2558 ╘ BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Add_1 }, + }, + // U+2559 ╙ BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+255A ╚ BOX DRAWINGS DOUBLE UP AND RIGHT + { + Instruction{ Shape_EmptyRect, Pos_1_2_Sub_1, Pos_Min, Pos_Max, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + }, + // U+255B ╛ BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Add_1 }, + }, + // U+255C ╜ BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+255D ╝ BOX DRAWINGS DOUBLE UP AND LEFT + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + }, + // U+255E ╞ BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+255F ╟ BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2560 ╠ BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2561 ╡ BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2562 ╢ BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2 }, + }, + // U+2563 ╣ BOX DRAWINGS DOUBLE VERTICAL AND LEFT + { + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + }, + // U+2564 ╤ BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Add_1, Pos_1_2, Pos_1_1 }, + }, + // U+2565 ╥ BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_1 }, + }, + // U+2566 ╦ BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2567 ╧ BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Sub_1 }, + }, + // U+2568 ╨ BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+2569 ╩ BOX DRAWINGS DOUBLE UP AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + }, + // U+256A ╪ BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+256B ╫ BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+256C ╬ BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+256D ╭ BOX DRAWINGS LIGHT ARC DOWN AND RIGHT + { + Instruction{ Shape_RoundRect, Pos_1_2, Pos_1_2, Pos_Max, Pos_Max }, + }, + // U+256E ╮ BOX DRAWINGS LIGHT ARC DOWN AND LEFT + { + Instruction{ Shape_RoundRect, Pos_Min, Pos_1_2, Pos_1_2, Pos_Max }, + }, + // U+256F ╯ BOX DRAWINGS LIGHT ARC UP AND LEFT + { + Instruction{ Shape_RoundRect, Pos_Min, Pos_Min, Pos_1_2, Pos_1_2 }, + }, + // U+2570 ╰ BOX DRAWINGS LIGHT ARC UP AND RIGHT + { + Instruction{ Shape_RoundRect, Pos_1_2, Pos_Min, Pos_Max, Pos_1_2 }, + }, + // U+2571 ╱ BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+2572 ╲ BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2573 ╳ BOX DRAWINGS LIGHT DIAGONAL CROSS + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2574 ╴ BOX DRAWINGS LIGHT LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + }, + // U+2575 ╵ BOX DRAWINGS LIGHT UP + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2576 ╶ BOX DRAWINGS LIGHT RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2577 ╷ BOX DRAWINGS LIGHT DOWN + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2578 ╸ BOX DRAWINGS HEAVY LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + }, + // U+2579 ╹ BOX DRAWINGS HEAVY UP + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+257A ╺ BOX DRAWINGS HEAVY RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257B ╻ BOX DRAWINGS HEAVY DOWN + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+257C ╼ BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257D ╽ BOX DRAWINGS LIGHT UP AND HEAVY DOWN + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+257E ╾ BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257F ╿ BOX DRAWINGS HEAVY UP AND LIGHT DOW + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2580 ▀ UPPER HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+2581 ▁ LOWER ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_7_8, Pos_1_1, Pos_1_1 }, + }, + // U+2582 ▂ LOWER ONE QUARTER BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_3_4, Pos_1_1, Pos_1_1 }, + }, + // U+2583 ▃ LOWER THREE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_5_8, Pos_1_1, Pos_1_1 }, + }, + // U+2584 ▄ LOWER HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+2585 ▅ LOWER FIVE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_3_8, Pos_1_1, Pos_1_1 }, + }, + // U+2586 ▆ LOWER THREE QUARTERS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_4, Pos_1_1, Pos_1_1 }, + }, + // U+2587 ▇ LOWER SEVEN EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_8, Pos_1_1, Pos_1_1 }, + }, + // U+2588 █ FULL BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2589 ▉ LEFT SEVEN EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_7_8, Pos_1_1 }, + }, + // U+258A ▊ LEFT THREE QUARTERS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_3_4, Pos_1_1 }, + }, + // U+258B ▋ LEFT FIVE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_5_8, Pos_1_1 }, + }, + // U+258C ▌ LEFT HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+258D ▍ LEFT THREE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_3_8, Pos_1_1 }, + }, + // U+258E ▎ LEFT ONE QUARTER BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_4, Pos_1_1 }, + }, + // U+258F ▏ LEFT ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_8, Pos_1_1 }, + }, + // U+2590 ▐ RIGHT HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2591 ░ LIGHT SHADE + { + Instruction{ Shape_Filled025, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2592 ▒ MEDIUM SHADE + { + Instruction{ Shape_Filled050, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2593 ▓ DARK SHADE + { + Instruction{ Shape_Filled075, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2594 ▔ UPPER ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_8 }, + }, + // U+2595 ▕ RIGHT ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_7_8, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2596 ▖ QUADRANT LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2597 ▗ QUADRANT LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+2598 ▘ QUADRANT UPPER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2599 ▙ QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+259A ▚ QUADRANT UPPER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+259B ▛ QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259C ▜ QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+259D ▝ QUADRANT UPPER RIGHT + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259E ▞ QUADRANT UPPER RIGHT AND LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259F ▟ QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, +}; + +static constexpr char32_t Powerline_FirstChar = 0xE0B0; +static constexpr u32 Powerline_CharCount = 0x10; +static constexpr Instruction Powerline[Powerline_CharCount][InstructionsPerGlyph] = { + // U+E0B0 Right triangle solid + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B1 Right triangle line + { + Instruction{ Shape_OpenLinePath, Pos_0_1, Pos_0_1, Pos_1_1_Sub_0_5, Pos_1_2 }, + Instruction{ Shape_OpenLinePath, Pos_0_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B2 Left triangle solid + { + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_0_1, Pos_1_2 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B3 Left triangle line + { + Instruction{ Shape_OpenLinePath, Pos_1_1, Pos_0_1, Pos_0_1_Add_0_5, Pos_1_2 }, + Instruction{ Shape_OpenLinePath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B4 Right semi-circle solid + { + Instruction{ Shape_FilledEllipsis, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+E0B5 Right semi-circle line + { + Instruction{ Shape_EmptyEllipsis, Pos_0_1, Pos_1_2, Pos_1_1_Sub_0_5, Pos_1_2_Sub_0_5 }, + }, + // U+E0B6 Left semi-circle solid + { + Instruction{ Shape_FilledEllipsis, Pos_1_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+E0B7 Left semi-circle line + { + Instruction{ Shape_EmptyEllipsis, Pos_1_1, Pos_1_2, Pos_1_1_Sub_0_5, Pos_1_2_Sub_0_5 }, + }, + // U+E0B8 Lower left triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_0_1, Pos_1_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B9 Backslash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+E0BA Lower right triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_1_1, Pos_1_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_Min, Pos_Min }, + }, + // U+E0BB Forward slash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+E0BC Upper left triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_0_1, Pos_0_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_Min, Pos_Min }, + }, + // U+E0BD Forward slash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+E0BE Upper right triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_1_1, Pos_0_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0BF Backslash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, +}; + +constexpr bool BoxDrawing_IsMapped(char32_t codepoint) +{ + return codepoint >= BoxDrawing_FirstChar && codepoint < (BoxDrawing_FirstChar + BoxDrawing_CharCount); +} + +constexpr bool Powerline_IsMapped(char32_t codepoint) +{ + return codepoint >= Powerline_FirstChar && codepoint < (Powerline_FirstChar + Powerline_CharCount); +} + +// How should I make this constexpr == inline, if it's an external symbol? Bad compiler! +#pragma warning(suppress : 26497) // You can attempt to make '...' constexpr unless it contains any undefined behavior (f.4). +bool BuiltinGlyphs::IsBuiltinGlyph(char32_t codepoint) noexcept +{ + return BoxDrawing_IsMapped(codepoint) || Powerline_IsMapped(codepoint); +} + +static const Instruction* GetInstructions(char32_t codepoint) noexcept +{ + if (BoxDrawing_IsMapped(codepoint)) + { + return &BoxDrawing[codepoint - BoxDrawing_FirstChar][0]; + } + if (Powerline_IsMapped(codepoint)) + { + return &Powerline[codepoint - Powerline_FirstChar][0]; + } + return nullptr; +} + +static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext* renderTarget, Shape shape) +{ + static constexpr u32 _ = 0; + static constexpr u32 w = 0xffffffff; + static constexpr u32 size = 4; + // clang-format off + static constexpr u32 shades[3][size * size] = { + { + w, _, _, _, + w, _, _, _, + _, _, w, _, + _, _, w, _, + }, + { + w, _, w, _, + _, w, _, w, + w, _, w, _, + _, w, _, w, + }, + { + _, w, w, w, + _, w, w, w, + w, w, _, w, + w, w, _, w, + }, + }; + // clang-format on + + static constexpr D2D1_SIZE_U bitmapSize{ size, size }; + static constexpr D2D1_BITMAP_PROPERTIES bitmapProps{ + .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, + .dpiX = 96, + .dpiY = 96, + }; + static constexpr D2D1_BITMAP_BRUSH_PROPERTIES bitmapBrushProps{ + .extendModeX = D2D1_EXTEND_MODE_WRAP, + .extendModeY = D2D1_EXTEND_MODE_WRAP, + .interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR + }; + + assert(shape < ARRAYSIZE(shades)); + + wil::com_ptr bitmap; + THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); + + wil::com_ptr bitmapBrush; + THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); + + return bitmapBrush; +} + +void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) +{ + renderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); + const auto restoreD2D = wil::scope_exit([&]() { + renderTarget->PopAxisAlignedClip(); + }); + + const auto instructions = GetInstructions(codepoint); + if (!instructions) + { + assert(false); // If everything in AtlasEngine works correctly, then this function should not get called when !IsBuiltinGlyph(codepoint). + renderTarget->Clear(nullptr); + return; + } + + const auto rectX = rect.left; + const auto rectY = rect.top; + const auto rectW = rect.right - rect.left; + const auto rectH = rect.bottom - rect.top; + const auto lightLineWidth = std::max(1.0f, roundf(rectW / 8.0f)); + D2D1_POINT_2F geometryPoints[2 * InstructionsPerGlyph]; + size_t geometryPointsCount = 0; + + for (size_t i = 0; i < InstructionsPerGlyph; ++i) + { + const auto& instruction = instructions[i]; + if (instruction.value == 0) + { + break; + } + + const auto shape = static_cast(instruction.shape); + auto begX = Pos_Lut[instruction.begX][0] * rectW; + auto begY = Pos_Lut[instruction.begY][0] * rectH; + auto endX = Pos_Lut[instruction.endX][0] * rectW; + auto endY = Pos_Lut[instruction.endY][0] * rectH; + + begX += Pos_Lut[instruction.begX][1] * lightLineWidth; + begY += Pos_Lut[instruction.begY][1] * lightLineWidth; + endX += Pos_Lut[instruction.endX][1] * lightLineWidth; + endY += Pos_Lut[instruction.endY][1] * lightLineWidth; + + const auto lineWidth = shape == Shape_HeavyLine ? lightLineWidth * 2.0f : lightLineWidth; + const auto lineWidthHalf = lineWidth * 0.5f; + const auto isHollowRect = shape == Shape_EmptyRect || shape == Shape_RoundRect; + const auto isLine = shape == Shape_LightLine || shape == Shape_HeavyLine; + const auto isLineX = isLine && begX == endX; + const auto isLineY = isLine && begY == endY; + const auto lineOffsetX = isHollowRect || isLineX ? lineWidthHalf : 0.0f; + const auto lineOffsetY = isHollowRect || isLineY ? lineWidthHalf : 0.0f; + + begX = roundf(begX - lineOffsetX) + lineOffsetX; + begY = roundf(begY - lineOffsetY) + lineOffsetY; + endX = roundf(endX + lineOffsetX) - lineOffsetX; + endY = roundf(endY + lineOffsetY) - lineOffsetY; + + const auto begXabs = begX + rectX; + const auto begYabs = begY + rectY; + const auto endXabs = endX + rectX; + const auto endYabs = endY + rectY; + + switch (shape) + { + case Shape_Filled025: + case Shape_Filled050: + case Shape_Filled075: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + const auto bitmapBrush = createShadedBitmapBrush(renderTarget, shape); + renderTarget->FillRectangle(&r, bitmapBrush.get()); + break; + } + case Shape_Filled100: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + renderTarget->FillRectangle(&r, brush); + break; + } + case Shape_LightLine: + case Shape_HeavyLine: + { + const D2D1_POINT_2F beg{ begXabs, begYabs }; + const D2D1_POINT_2F end{ endXabs, endYabs }; + renderTarget->DrawLine(beg, end, brush, lineWidth, nullptr); + break; + } + case Shape_EmptyRect: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + renderTarget->DrawRectangle(&r, brush, lineWidth, nullptr); + break; + } + case Shape_RoundRect: + { + const D2D1_ROUNDED_RECT rr{ { begXabs, begYabs, endXabs, endYabs }, lightLineWidth * 2, lightLineWidth * 2 }; + renderTarget->DrawRoundedRectangle(&rr, brush, lineWidth, nullptr); + break; + } + case Shape_FilledEllipsis: + { + const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY }; + renderTarget->FillEllipse(&e, brush); + break; + } + case Shape_EmptyEllipsis: + { + const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY }; + renderTarget->DrawEllipse(&e, brush, lineWidth, nullptr); + break; + } + case Shape_ClosedFilledPath: + case Shape_OpenLinePath: + if (instruction.begX) + { + geometryPoints[geometryPointsCount++] = { begXabs, begYabs }; + } + if (instruction.endX) + { + geometryPoints[geometryPointsCount++] = { endXabs, endYabs }; + } + break; + } + } + + if (geometryPointsCount) + { + const auto shape = instructions[0].shape; + const auto beginType = shape == Shape_ClosedFilledPath ? D2D1_FIGURE_BEGIN_FILLED : D2D1_FIGURE_BEGIN_HOLLOW; + const auto endType = shape == Shape_ClosedFilledPath ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN; + + wil::com_ptr geometry; + THROW_IF_FAILED(factory->CreatePathGeometry(geometry.addressof())); + + wil::com_ptr sink; + THROW_IF_FAILED(geometry->Open(sink.addressof())); + + sink->BeginFigure(geometryPoints[0], beginType); + sink->AddLines(&geometryPoints[1], static_cast(geometryPointsCount - 1)); + sink->EndFigure(endType); + + THROW_IF_FAILED(sink->Close()); + + if (beginType == D2D1_FIGURE_BEGIN_FILLED) + { + renderTarget->FillGeometry(geometry.get(), brush, nullptr); + } + else + { + renderTarget->DrawGeometry(geometry.get(), brush, lightLineWidth, nullptr); + } + } +} diff --git a/src/renderer/atlas/BuiltinGlyphs.h b/src/renderer/atlas/BuiltinGlyphs.h new file mode 100644 index 00000000000..f399653893c --- /dev/null +++ b/src/renderer/atlas/BuiltinGlyphs.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "common.h" + +namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs +{ + bool IsBuiltinGlyph(char32_t codepoint) noexcept; + void DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint); + + // This is just an extra. It's not actually implemented as part of BuiltinGlyphs.cpp. + constexpr bool IsSoftFontChar(char32_t ch) noexcept + { + return ch >= 0xEF20 && ch < 0xEF80; + } +} diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj index 689ec6ff8d7..a2faefce901 100644 --- a/src/renderer/atlas/atlas.vcxproj +++ b/src/renderer/atlas/atlas.vcxproj @@ -16,6 +16,7 @@ + @@ -28,6 +29,7 @@ + diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 014f5487118..71010b31f77 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -360,6 +360,7 @@ namespace Microsoft::Console::Render::Atlas u16 dpi = 96; AntialiasingMode antialiasingMode = DefaultAntialiasingMode; + bool builtinGlyphs = false; std::vector softFontPattern; til::size softFontCellSize; @@ -420,8 +421,8 @@ namespace Microsoft::Console::Render::Atlas struct FontMapping { wil::com_ptr fontFace; - u32 glyphsFrom = 0; - u32 glyphsTo = 0; + size_t glyphsFrom = 0; + size_t glyphsTo = 0; }; struct GridLineRange @@ -450,11 +451,18 @@ namespace Microsoft::Console::Render::Atlas dirtyBottom = dirtyTop + cellHeight; } + // Each mappings from/to range indicates the range of indices/advances/offsets/colors this fontFace is to be used for. std::vector mappings; + // Stores glyph indices of the corresponding mappings.fontFace, unless fontFace is nullptr, + // in which case this stores UTF16 because we're dealing with a custom glyph (box glyph, etc.). std::vector glyphIndices; - std::vector glyphAdvances; // same size as glyphIndices - std::vector glyphOffsets; // same size as glyphIndices - std::vector colors; // same size as glyphIndices + // Same size as glyphIndices. + std::vector glyphAdvances; + // Same size as glyphIndices. + std::vector glyphOffsets; + // Same size as glyphIndices. + std::vector colors; + std::vector gridLineRanges; LineRendition lineRendition = LineRendition::SingleWidth; u16 selectionFrom = 0; @@ -499,6 +507,7 @@ namespace Microsoft::Console::Render::Atlas //// Parameters which change seldom. GenerationalSettings s; + std::wstring userLocaleName; //// Parameters which change every frame. // This is the backing buffer for `rows`. diff --git a/src/renderer/base/FontInfoDesired.cpp b/src/renderer/base/FontInfoDesired.cpp index 742883e7c14..b578960c812 100644 --- a/src/renderer/base/FontInfoDesired.cpp +++ b/src/renderer/base/FontInfoDesired.cpp @@ -29,6 +29,11 @@ void FontInfoDesired::SetCellSize(const CSSLengthPercentage& cellWidth, const CS _cellHeight = cellHeight; } +void FontInfoDesired::SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept +{ + _builtinGlyphs = builtinGlyphs; +} + const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept { return _cellWidth; @@ -39,6 +44,11 @@ const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept return _cellHeight; } +bool FontInfoDesired::GetEnableBuiltinGlyphs() const noexcept +{ + return _builtinGlyphs; +} + float FontInfoDesired::GetFontSize() const noexcept { return _fontSize; diff --git a/src/renderer/inc/FontInfoDesired.hpp b/src/renderer/inc/FontInfoDesired.hpp index 84643283183..a58425b4118 100644 --- a/src/renderer/inc/FontInfoDesired.hpp +++ b/src/renderer/inc/FontInfoDesired.hpp @@ -35,9 +35,11 @@ class FontInfoDesired : public FontInfoBase bool operator==(const FontInfoDesired& other) = delete; void SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept; + void SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept; const CSSLengthPercentage& GetCellWidth() const noexcept; const CSSLengthPercentage& GetCellHeight() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; float GetFontSize() const noexcept; til::size GetEngineSize() const noexcept; bool IsDefaultRasterFont() const noexcept; @@ -47,4 +49,5 @@ class FontInfoDesired : public FontInfoBase float _fontSize; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; + bool _builtinGlyphs = false; }; diff --git a/src/til/ut_til/FlatSetTests.cpp b/src/til/ut_til/FlatSetTests.cpp index 43e4c5fb2be..751f7f84059 100644 --- a/src/til/ut_til/FlatSetTests.cpp +++ b/src/til/ut_til/FlatSetTests.cpp @@ -12,37 +12,34 @@ using namespace WEX::TestExecution; struct Data { static constexpr auto emptyMarker = std::numeric_limits::max(); + size_t value = emptyMarker; +}; - constexpr operator bool() const noexcept +struct DataHashTrait +{ + static constexpr bool occupied(const Data& d) noexcept { - return value != emptyMarker; + return d.value != Data::emptyMarker; } - constexpr bool operator==(int key) const noexcept + static constexpr size_t hash(const size_t key) noexcept { - return value == static_cast(key); + return til::flat_set_hash_integer(key); } - constexpr Data& operator=(int key) noexcept + static constexpr size_t hash(const Data& d) noexcept { - value = static_cast(key); - return *this; + return til::flat_set_hash_integer(d.value); } - size_t value = emptyMarker; -}; - -template<> -struct ::std::hash -{ - constexpr size_t operator()(int key) const noexcept + static constexpr bool equals(const Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(static_cast(key)); + return d.value == key; } - constexpr size_t operator()(Data d) const noexcept + static constexpr void assign(Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(d.value); + d.value = key; } }; @@ -52,7 +49,7 @@ class FlatSetTests TEST_METHOD(Basic) { - til::linear_flat_set set; + til::linear_flat_set set; // This simultaneously demonstrates how the class can't just do "heterogeneous lookups" // like STL does, but also insert items with a different type. @@ -62,7 +59,7 @@ class FlatSetTests const auto [entry2, inserted2] = set.insert(123); VERIFY_IS_FALSE(inserted2); - VERIFY_ARE_EQUAL(&entry1, &entry2); - VERIFY_ARE_EQUAL(123u, entry2.value); + VERIFY_ARE_EQUAL(entry1, entry2); + VERIFY_ARE_EQUAL(123u, entry2->value); } }; From 2dfa3da199c4257c527bcb9dd0bd705261181266 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 23 Feb 2024 05:34:02 -0600 Subject: [PATCH 25/50] build: switch to NuGetAuthenticate@1 (#16752) It keeps warning us that v0 has been deprecated. (cherry picked from commit 6b29ef51e304fb1af8df7c798f87d3dbb0408c92) Service-Card-Id: 91903282 Service-Version: 1.19 --- .../pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml | 2 +- build/pipelines/templates-v2/steps-restore-nuget.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml index b61a22c4fc1..e30e23b88c4 100644 --- a/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml +++ b/build/pipelines/templates-v2/job-pgo-build-nuget-and-publish.yml @@ -43,7 +43,7 @@ jobs: - template: steps-ensure-nuget-version.yml - - task: NuGetAuthenticate@0 + - task: NuGetAuthenticate@1 inputs: nuGetServiceConnections: 'Terminal Public Artifact Feed' diff --git a/build/pipelines/templates-v2/steps-restore-nuget.yml b/build/pipelines/templates-v2/steps-restore-nuget.yml index bd0c067531c..37018efc1a9 100644 --- a/build/pipelines/templates-v2/steps-restore-nuget.yml +++ b/build/pipelines/templates-v2/steps-restore-nuget.yml @@ -1,7 +1,7 @@ steps: - template: steps-ensure-nuget-version.yml -- task: NuGetAuthenticate@0 +- task: NuGetAuthenticate@1 - script: |- echo ##vso[task.setvariable variable=NUGET_RESTORE_MSBUILD_ARGS]/p:Platform=$(BuildPlatform) From 7c1edbdb5ff16dd372d7c8f18c5f5a769b6f6ca1 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Sun, 18 Feb 2024 23:22:41 -0600 Subject: [PATCH 26/50] build: add a tsa configuration for asyncSdl (#16728) (cherry picked from commit e3ff44bb82699178c00dd46db70030ea4a777353) Service-Card-Id: 91922822 Service-Version: 1.19 --- .../templates-v2/pipeline-onebranch-full-release-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml index ab0f5e2dc00..991ec1b262b 100644 --- a/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml +++ b/build/pipelines/templates-v2/pipeline-onebranch-full-release-build.yml @@ -75,6 +75,9 @@ extends: cloudvault: # https://aka.ms/obpipelines/cloudvault enabled: false globalSdl: # https://aka.ms/obpipelines/sdl + asyncSdl: + enabled: true + tsaOptionsFile: 'build/config/tsa.json' tsa: enabled: true configFile: '$(Build.SourcesDirectory)\build\config\tsa.json' From e5d7fd023070a0a5ec77fd67bc36607bd99cc6d2 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Feb 2024 12:53:02 +0100 Subject: [PATCH 27/50] AtlasEngine: Improve dotted, dashed and curly underlines (#16719) This changeset makes 3 improvements: * Dotted lines now use a 2:1 ratio between gaps and dots (from 1:1). This makes the dots a lot easier to spot at small font sizes. * Dashed lines use a 1:2 ratio and a cells-size independent stride. By being cell-size independent it works more consistently with a wider variety of fonts with weird cell aspect ratios. * Curly lines are now cell-size independent as well and have a height that equals the double-underline size. This ensures that the curve isn't cut off anymore and just like with dashed lines, that it works under weird aspect ratios. Closes #16712 ## Validation Steps Performed This was tested using RenderingTests using Cascadia Mono, Consolas, Courier New, Lucida Console and MS Gothic. (cherry picked from commit 9c8058c3260d39e37416154a181441839e0570f6) Service-Card-Id: 91922825 Service-Version: 1.19 --- src/renderer/atlas/BackendD3D.cpp | 38 +++++++++++-------------------- src/renderer/atlas/BackendD3D.h | 7 +++--- src/renderer/atlas/shader_ps.hlsl | 25 ++++++++------------ 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 74b64c53571..923747ef94a 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -310,29 +310,18 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) // baseline of curlyline is at the middle of singly underline. When there's // limited space to draw a curlyline, we apply a limit on the peak height. { - // initialize curlyline peak height to a desired value. Clamp it to at - // least 1. - constexpr auto curlyLinePeakHeightEm = 0.075f; - _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); - - // calc the limit we need to apply - const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; - - // if the limit is <= 0 (no height at all), stick with the desired height. - // This is how we force a curlyline even when there's no space, though it - // might be clipped at the bottom. - if (maxDrawableCurlyLinePeakHeight > 0.0f) - { - _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); - } + const auto cellHeight = static_cast(font.cellSize.y); + const auto strokeWidth = static_cast(font.thinLineWidth); + + // This gives it the same position and height as our double-underline. There's no particular reason for that, apart from + // it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc. + // We still need to ensure though that it doesn't clip out of the cellHeight at the bottom. + const auto height = std::max(3.0f, static_cast(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position)); + const auto top = std::min(static_cast(font.doubleUnderline[0].position), floorf(cellHeight - height - strokeWidth)); - const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; - const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); - _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + _curlyLineHalfHeight = height * 0.5f; + _curlyUnderline.position = gsl::narrow_cast(lrintf(top)); + _curlyUnderline.height = gsl::narrow_cast(lrintf(height)); } DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); @@ -573,9 +562,8 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; - data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLinePeakHeight = _curlyLinePeakHeight; - data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; + data.thinLineWidth = p.s->font->thinLineWidth; + data.curlyLineHalfHeight = _curlyLineHalfHeight; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 4c248ed3ff3..cdf9937cc61 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,9 +42,8 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; - alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; - alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; - alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; + alignas(sizeof(f32)) f32 thinLineWidth = 0; + alignas(sizeof(f32)) f32 curlyLineHalfHeight = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -291,7 +290,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLinePeakHeight = 0.0f; + f32 _curlyLineHalfHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index e19ba955fe5..d3c0bfac6c3 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,9 +12,8 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; - float curlyLinePeakHeight; - float curlyLineWaveFreq; - float curlyLineCellOffset; + float thinLineWidth; + float curlyLineHalfHeight; } Texture2D background : register(t0); @@ -76,31 +75,25 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (3.0f * underlineWidth * data.renditionScale.x)) < (1.0f / 3.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (6.0f * underlineWidth * data.renditionScale.x)) < (4.0f / 6.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_CURLY_LINE: { - uint cellRow = floor(data.position.y / backgroundCellSize.y); - // Use the previous cell when drawing 'Double Height' curly line. - cellRow -= data.renditionScale.y - 1; - const float cellTop = cellRow * backgroundCellSize.y; - const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; - const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; - const float amp = curlyLinePeakHeight * data.renditionScale.y; - const float freq = curlyLineWaveFreq / data.renditionScale.x; - - const float s = sin(data.position.x * freq); - const float d = abs(centerY - (s * amp) - data.position.y); + const float strokeWidthHalf = thinLineWidth * data.renditionScale.y * 0.5f; + const float amp = (curlyLineHalfHeight - strokeWidthHalf) * data.renditionScale.y; + const float freq = data.renditionScale.x / curlyLineHalfHeight * 1.57079632679489661923f; + const float s = sin(data.position.x * freq) * amp; + const float d = abs(curlyLineHalfHeight - data.texcoord.y - s); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa; From bb5f56e704ca6f838c78a6ecf44f86a6d2b12fea Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 30 Jan 2024 18:01:12 -0600 Subject: [PATCH 28/50] Upgrade Microsoft.Windows.ImplementationLibrary to 1.0.240122.1 (#16617) This includes a fix for the hang on shutdown due to the folder change reader. WIL now validates format strings in `LOG...` macros (yay!) and so we needed to fix some of our `LOG` macros. Closes #16456 (cherry picked from commit ce30e7c89c54c779f506e54c3f3ff996825044f7) Service-Card-Id: 91923199 Service-Version: 1.19 --- dep/nuget/packages.config | 2 +- src/common.nugetversions.targets | 4 ++-- src/renderer/atlas/BackendD3D.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config index d0c670dcd57..e6fd7ec3a68 100644 --- a/dep/nuget/packages.config +++ b/dep/nuget/packages.config @@ -9,7 +9,7 @@ - + diff --git a/src/common.nugetversions.targets b/src/common.nugetversions.targets index 2a3d5cc062e..f3cb03f6cb8 100644 --- a/src/common.nugetversions.targets +++ b/src/common.nugetversions.targets @@ -62,7 +62,7 @@ - + @@ -93,7 +93,7 @@ - + diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 923747ef94a..be3728d7166 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -457,7 +457,7 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p) { if (error) { - LOG_HR_MSG(hr, "%*hs", error->GetBufferSize(), error->GetBufferPointer()); + LOG_HR_MSG(hr, "%.*hs", static_cast(error->GetBufferSize()), static_cast(error->GetBufferPointer())); } else { From 88def9ddcd1088f5c9431e8ea45820128572c74a Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 26 Feb 2024 12:27:57 -0800 Subject: [PATCH 29/50] Add support for actions in fragments (#16185) Surprisingly easier than I thought this would be. ActionMap already supports layering (from defaults.json), so this basically re-uses a lot of that for fun and profit. The trickiest bits: * In `SettingsLoader::_parseFragment`, I'm constructing a fake, empty JSON object, and taking _only_ the actions out from the fragment, and stuffing them into this temp json. Then, I parse that as a globals object, and set _that_ as the parent to the user settings file. That results in _only_ the actions from the fragment being parsed before the user's actions. * In that same method, I'm also explicitly preventing the ActionMap (et al.) from parsing `keys` from these actions. We don't want fragments to be able to say "ctrl+f is clear buffer" or something like that. This required a bit of annoying plumbing. Closes #16063 Tests added. Docs need to be updated. --- .../DeserializationTests.cpp | 190 ++++++++++++++++++ .../SerializationTests.cpp | 26 ++- .../TerminalSettingsModel/ActionMap.h | 2 +- .../ActionMapSerialization.cpp | 4 +- .../CascadiaSettingsSerialization.cpp | 15 +- .../TerminalSettingsModel/Command.cpp | 30 +-- src/cascadia/TerminalSettingsModel/Command.h | 3 +- .../GlobalAppSettings.cpp | 11 +- .../TerminalSettingsModel/GlobalAppSettings.h | 1 + 9 files changed, 246 insertions(+), 36 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index b5139a5263f..50d649d7a5d 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -74,6 +74,13 @@ namespace SettingsModelLocalTests TEST_METHOD(TestInheritedCommand); TEST_METHOD(LoadFragmentsWithMultipleUpdates); + TEST_METHOD(FragmentActionSimple); + TEST_METHOD(FragmentActionNoKeys); + TEST_METHOD(FragmentActionNested); + TEST_METHOD(FragmentActionNestedNoName); + TEST_METHOD(FragmentActionIterable); + TEST_METHOD(FragmentActionRoundtrip); + TEST_METHOD(MigrateReloadEnvVars); private: @@ -2023,6 +2030,189 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(L"NewName", loader.userSettings.profiles[0]->Name()); } + void DeserializationTests::FragmentActionSimple() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "command": { "action": "addMark" }, + "name": "Test Action" + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto settings = winrt::make_self(std::move(loader)); + + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + const auto actionsByName = actionMap->NameMap(); + VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); + } + + void DeserializationTests::FragmentActionNoKeys() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "command": { "action": "addMark" }, + "keys": "ctrl+f", + "name": "Test Action" + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto settings = winrt::make_self(std::move(loader)); + + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + const auto actionsByName = actionMap->NameMap(); + VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('F'), 0 })); + } + + void DeserializationTests::FragmentActionNested() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "name": "nested command", + "commands": [ + { + "name": "child1", + "command": { "action": "newTab", "commandline": "ssh me@first.com" } + }, + { + "name": "child2", + "command": { "action": "newTab", "commandline": "ssh me@second.com" } + } + ] + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto settings = winrt::make_self(std::move(loader)); + + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + const auto actionsByName = actionMap->NameMap(); + const auto& nested{ actionsByName.TryLookup(L"nested command") }; + VERIFY_IS_NOT_NULL(nested); + VERIFY_IS_TRUE(nested.HasNestedCommands()); + } + + void DeserializationTests::FragmentActionNestedNoName() + { + // Basically the same as TestNestedCommandWithoutName + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "commands": [ + { + "name": "child1", + "command": { "action": "newTab", "commandline": "ssh me@first.com" } + }, + { + "name": "child2", + "command": { "action": "newTab", "commandline": "ssh me@second.com" } + } + ] + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto settings = winrt::make_self(std::move(loader)); + VERIFY_ARE_EQUAL(0u, settings->Warnings().Size()); + } + void DeserializationTests::FragmentActionIterable() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "name": "nested", + "commands": [ + { + "iterateOn": "schemes", + "name": "${scheme.name}", + "command": { "action": "setColorScheme", "colorScheme": "${scheme.name}" } + } + ] + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto settings = winrt::make_self(std::move(loader)); + + const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); + const auto actionsByName = actionMap->NameMap(); + const auto& nested{ actionsByName.TryLookup(L"nested") }; + VERIFY_IS_NOT_NULL(nested); + VERIFY_IS_TRUE(nested.HasNestedCommands()); + VERIFY_ARE_EQUAL(settings->GlobalSettings().ColorSchemes().Size(), nested.NestedCommands().Size()); + } + void DeserializationTests::FragmentActionRoundtrip() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "actions": [ + { + "command": { "action": "addMark" }, + "name": "Test Action" + }, + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + const auto oldSettings = winrt::make_self(std::move(loader)); + + const auto actionMap = winrt::get_self(oldSettings->GlobalSettings().ActionMap()); + const auto actionsByName = actionMap->NameMap(); + VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); + + const auto oldResult{ oldSettings->ToJson() }; + + Log::Comment(L"Now, create a _new_ settings object from the re-serialization of the first"); + implementation::SettingsLoader newLoader{ toString(oldResult), DefaultJson }; + // NOTABLY! Don't load the fragment here. + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + const auto newSettings = winrt::make_self(std::move(newLoader)); + + const auto& newActionMap = winrt::get_self(newSettings->GlobalSettings().ActionMap()); + const auto newActionsByName = newActionMap->NameMap(); + VERIFY_IS_NULL(newActionsByName.TryLookup(L"Test Action")); + } + void DeserializationTests::MigrateReloadEnvVars() { static constexpr std::string_view settings1Json{ R"( diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index 2488f3f812d..463d0dc0478 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -68,6 +68,19 @@ namespace SettingsModelLocalTests // written alphabetically. VERIFY_ARE_EQUAL(toString(json), toString(result)); } + + // Helper to remove the `$schema` property from a json object. We + // populate that based off the local path to the settings file. Of + // course, that's entirely unpredictable in tests. So cut it out before + // we do any sort of roundtrip testing. + static Json::Value removeSchema(Json::Value json) + { + if (json.isMember("$schema")) + { + json.removeMember("$schema"); + } + return json; + } }; void SerializationTests::GlobalSettings() @@ -262,15 +275,6 @@ namespace SettingsModelLocalTests { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" }, { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" } ])" }; - // GH#13323 - these can be fragile. In the past, the order these get - // re-serialized as has been not entirely stable. We don't really care - // about the order they get re-serialized in, but the tests aren't - // clever enough to compare the structure, only the literal string - // itself. Feel free to change as needed. - static constexpr std::string_view actionsString4B{ R"([ - { "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" }, - { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" } - ])" }; // command with name and icon and multiple key chords static constexpr std::string_view actionsString5{ R"([ @@ -384,7 +388,6 @@ namespace SettingsModelLocalTests Log::Comment(L"complex commands with key chords"); RoundtripTest(actionsString4A); - RoundtripTest(actionsString4B); Log::Comment(L"command with name and icon and multiple key chords"); RoundtripTest(actionsString5); @@ -483,7 +486,8 @@ namespace SettingsModelLocalTests const auto settings{ winrt::make_self(settingsString) }; const auto result{ settings->ToJson() }; - VERIFY_ARE_EQUAL(toString(VerifyParseSucceeded(settingsString)), toString(result)); + VERIFY_ARE_EQUAL(toString(removeSchema(VerifyParseSucceeded(settingsString))), + toString(removeSchema(result))); } void SerializationTests::LegacyFontSettings() diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 4270a081118..d59dc0677d8 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -67,7 +67,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // JSON static com_ptr FromJson(const Json::Value& json); - std::vector LayerJson(const Json::Value& json); + std::vector LayerJson(const Json::Value& json, const bool withKeybindings = true); Json::Value ToJson() const; // modification diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index a788724b43f..248a0023d00 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -35,7 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - json: an array of Json::Value's to deserialize into our ActionMap. // Return value: // - a list of warnings encountered while deserializing the json - std::vector ActionMap::LayerJson(const Json::Value& json) + std::vector ActionMap::LayerJson(const Json::Value& json, const bool withKeybindings) { // It's possible that the user provided keybindings have some warnings in // them - problems that we should alert the user to, but we can recover @@ -50,7 +50,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation continue; } - AddAction(*Command::FromJson(cmdJson, warnings)); + AddAction(*Command::FromJson(cmdJson, warnings, withKeybindings)); } return warnings; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 7510bb0ebf7..c22d421975a 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -629,7 +629,7 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source // schemes and profiles. Additionally this function supports profiles which specify an "updates" key. void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings) { - const auto json = _parseJson(content); + auto json = _parseJson(content); settings.clear(); @@ -647,6 +647,11 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str } CATCH_LOG() } + + // Parse out actions from the fragment. Manually opt-out of keybinding + // parsing - fragments shouldn't be allowed to bind actions to keys + // directly. We may want to revisit circa GH#2205 + settings.globals->LayerActionsFrom(json.root, false); } { @@ -688,10 +693,10 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str } } - for (const auto& kv : settings.globals->ColorSchemes()) - { - userSettings.globals->AddColorScheme(kv.Value()); - } + // Add the parsed fragment globals as a parent of the user's settings. + // Later, in FinalizeInheritance, this will result in the action map from + // the fragments being applied before the user's own settings. + userSettings.globals->AddLeastImportantParent(settings.globals); } SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index b211987b677..8ca3ebe95a7 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -258,7 +258,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Return Value: // - the newly constructed Command object. winrt::com_ptr Command::FromJson(const Json::Value& json, - std::vector& warnings) + std::vector& warnings, + const bool parseKeys) { auto result = winrt::make_self(); @@ -313,20 +314,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation result->_ActionAndArgs = make(); } - // GH#4239 - If the user provided more than one key - // chord to a "keys" array, warn the user here. - // TODO: GH#1334 - remove this check. - const auto keysJson{ json[JsonKey(KeysKey)] }; - if (keysJson.isArray() && keysJson.size() > 1) + if (parseKeys) { - warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); - } - else - { - Control::KeyChord keys{ nullptr }; - if (JsonUtils::GetValueForKey(json, KeysKey, keys)) + // GH#4239 - If the user provided more than one key + // chord to a "keys" array, warn the user here. + // TODO: GH#1334 - remove this check. + const auto keysJson{ json[JsonKey(KeysKey)] }; + if (keysJson.isArray() && keysJson.size() > 1) { - result->RegisterKey(keys); + warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); + } + else + { + Control::KeyChord keys{ nullptr }; + if (JsonUtils::GetValueForKey(json, KeysKey, keys)) + { + result->RegisterKey(keys); + } } } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 41037bf1a29..4418716c815 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -39,7 +39,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation com_ptr Copy() const; static winrt::com_ptr FromJson(const Json::Value& json, - std::vector& warnings); + std::vector& warnings, + const bool parseKeys = true); static void ExpandCommands(Windows::Foundation::Collections::IMap& commands, Windows::Foundation::Collections::IVectorView profiles, diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 1caef96b2cf..5cc65d913e4 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -142,12 +142,19 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON) #undef GLOBAL_SETTINGS_LAYER_JSON + LayerActionsFrom(json, true); + + JsonUtils::GetValueForKey(json, LegacyReloadEnvironmentVariablesKey, _legacyReloadEnvironmentVariables); +} + +void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const bool withKeybindings) +{ static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey }; for (const auto& jsonKey : bindingsKeys) { if (auto bindings{ json[JsonKey(jsonKey)] }) { - auto warnings = _actionMap->LayerJson(bindings); + auto warnings = _actionMap->LayerJson(bindings, withKeybindings); // It's possible that the user provided keybindings have some warnings // in them - problems that we should alert the user to, but we can @@ -158,8 +165,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) _keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end()); } } - - JsonUtils::GetValueForKey(json, LegacyReloadEnvironmentVariablesKey, _legacyReloadEnvironmentVariables); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 9c14376d053..1af8479e0c6 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -49,6 +49,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static com_ptr FromJson(const Json::Value& json); void LayerJson(const Json::Value& json); + void LayerActionsFrom(const Json::Value& json, const bool withKeybindings = true); Json::Value ToJson() const; From fefee5075711310295a43c5e5e910639598b16d5 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Mon, 26 Feb 2024 11:31:37 -0600 Subject: [PATCH 30/50] Fix spelling for openconsole/inbox merge --- .github/actions/spelling/allow/apis.txt | 6 ++++++ .github/actions/spelling/expect/expect.txt | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 9f5b92bc337..5ae7e5753df 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -22,7 +22,9 @@ COLORPROPERTY colspan COMDLG commandlinetoargv +commoncontrols comparand +COPYFROMRESOURCE cstdint CXICON CYICON @@ -42,6 +44,7 @@ endfor ENDSESSION enumset environstrings +EXACTSIZEONLY EXPCMDFLAGS EXPCMDSTATE filetime @@ -59,6 +62,7 @@ Hashtable HIGHCONTRASTON HIGHCONTRASTW hinternet +HIGHQUALITYSCALE HINTERNET hotkeys href @@ -75,6 +79,7 @@ IBox IClass IComparable IComparer +ICONINFO IConnection ICustom IDialog @@ -84,6 +89,7 @@ IExplorer IFACEMETHOD IFile IGraphics +IImage IInheritable IMap IMonarch diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 5a1ab96a5b3..02d18ffcb97 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -19,6 +19,7 @@ AFew AFill AFX AHelper +ahicon ahz AImpl AInplace @@ -844,6 +845,9 @@ IGNORELANGUAGE IHosted iid IIo +ILC +ILCo +ILD ime IMPEXP inbox @@ -1341,11 +1345,14 @@ pgomgr PGONu pguid phhook +phico +phicon phwnd pidl PIDLIST pids pii +piml pinvoke pipename pipestr @@ -1677,6 +1684,8 @@ slpit SManifest SMARTQUOTE SMTO +snapcx +snapcy SOLIDBOX Solutiondir somefile From 4ff38c260f02388385ce6b424ef84a26db9f39aa Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 26 Feb 2024 13:32:19 -0800 Subject: [PATCH 31/50] When the profile icon is set to null, fall back to the icon of the commandline (#15843) Basically, title. If you null out the icon, we'll automatically try to use the `commandline` as an icon (because we can now). We'll even be smart about it - `cmd.exe /k echo wassup` will still just use the ico of `cmd.exe`. This doesn't work for `ubuntu.exe` (et. al), because that commandline is technically a reparse point, that doesn't actually have an icon associated with it. Closes #705 `"none"` becomes our sentinel value for "no icon". This will also use the same `NormalizeCommandLine` we use for commandline matching for finding the full path to the exe. --- .../TerminalSettingsTests.cpp | 4 +- src/cascadia/TerminalApp/TabManagement.cpp | 7 +- src/cascadia/TerminalApp/TerminalPage.cpp | 5 +- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../ProfileViewModel.cpp | 26 +++ .../TerminalSettingsEditor/ProfileViewModel.h | 10 ++ .../ProfileViewModel.idl | 3 + .../TerminalSettingsEditor/Profiles_Base.xaml | 11 +- .../Resources/en-US/Resources.resw | 8 + .../CascadiaSettings.cpp | 136 +------------- .../TerminalSettingsModel/CascadiaSettings.h | 1 - .../TerminalSettingsModel/Command.cpp | 2 +- .../TerminalSettingsModel/MTSMSettings.h | 1 - .../TerminalSettingsModel/Profile.cpp | 170 ++++++++++++++++++ src/cascadia/TerminalSettingsModel/Profile.h | 15 ++ .../TerminalSettingsModel/Profile.idl | 4 + 16 files changed, 265 insertions(+), 140 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index 0ae0bcc164e..4dbc7f88db8 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -173,13 +173,13 @@ namespace SettingsModelLocalTests { const auto commandLine = file2.native() + LR"( -foo "bar1 bar2" -baz)"s; const auto expected = file2.native() + L"\0-foo\0bar1 bar2\0-baz"s; - const auto actual = implementation::CascadiaSettings::NormalizeCommandLine(commandLine.c_str()); + const auto actual = implementation::Profile::NormalizeCommandLine(commandLine.c_str()); VERIFY_ARE_EQUAL(expected, actual); } { const auto commandLine = L"C:\\"; const auto expected = L"C:\\"; - const auto actual = implementation::CascadiaSettings::NormalizeCommandLine(commandLine); + const auto actual = implementation::Profile::NormalizeCommandLine(commandLine); VERIFY_ARE_EQUAL(expected, actual); } } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 1717c05b8b7..78a1ead3acd 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -174,11 +174,12 @@ namespace winrt::TerminalApp::implementation // Set this tab's icon to the icon from the user's profile if (const auto profile{ newTabImpl->GetFocusedProfile() }) { - if (!profile.Icon().empty()) + const auto& icon = profile.EvaluatedIcon(); + if (!icon.empty()) { const auto theme = _settings.GlobalSettings().CurrentTheme(); const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default; - newTabImpl->UpdateIcon(profile.Icon(), iconStyle); + newTabImpl->UpdateIcon(icon, iconStyle); } } @@ -245,7 +246,7 @@ namespace winrt::TerminalApp::implementation { const auto theme = _settings.GlobalSettings().CurrentTheme(); const auto iconStyle = (theme && theme.Tab()) ? theme.Tab().IconStyle() : IconStyle::Default; - tab.UpdateIcon(profile.Icon(), iconStyle); + tab.UpdateIcon(profile.EvaluatedIcon(), iconStyle); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 26a80fa7457..568a4d03f7e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1033,9 +1033,10 @@ namespace winrt::TerminalApp::implementation // If there's an icon set for this profile, set it as the icon for // this flyout item - if (!profile.Icon().empty()) + const auto& iconPath = profile.EvaluatedIcon(); + if (!iconPath.empty()) { - const auto icon = _CreateNewTabFlyoutIcon(profile.Icon()); + const auto icon = _CreateNewTabFlyoutIcon(iconPath); profileMenuItem.Icon(icon); } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 797c207ef88..b85b0ad5d11 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -602,7 +602,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation MUX::Controls::NavigationViewItem profileNavItem; profileNavItem.Content(box_value(profile.Name())); profileNavItem.Tag(box_value(profile)); - profileNavItem.Icon(IconPathConverter::IconWUX(profile.Icon())); + profileNavItem.Icon(IconPathConverter::IconWUX(profile.EvaluatedIcon())); // Update the menu item when the icon/name changes auto weakMenuItem{ make_weak(profileNavItem) }; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 5f31f6be579..5f7517068b4 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -26,6 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IObservableVector ProfileViewModel::_MonospaceFontList{ nullptr }; Windows::Foundation::Collections::IObservableVector ProfileViewModel::_FontList{ nullptr }; + static constexpr std::wstring_view HideIconValue{ L"none" }; + ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings) : _profile{ profile }, _defaultAppearanceViewModel{ winrt::make(profile.DefaultAppearance().try_as()) }, @@ -69,6 +71,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"CurrentScrollState"); } + else if (viewModelProperty == L"Icon") + { + _NotifyChanges(L"HideIcon"); + } }); // Do the same for the starting directory @@ -348,6 +354,26 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + bool ProfileViewModel::HideIcon() + { + return Icon() == HideIconValue; + } + void ProfileViewModel::HideIcon(const bool hide) + { + if (hide) + { + // Stash the current value of Icon. If the user + // checks and un-checks the "Hide Icon" checkbox, we want + // the path that we display in the text box to remain unchanged. + _lastIcon = Icon(); + Icon(HideIconValue); + } + else + { + Icon(_lastIcon); + } + } + bool ProfileViewModel::IsBellStyleFlagSet(const uint32_t flag) { return (WI_EnumValue(BellStyle()) & flag) == flag; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 44df681665f..7770c1b2f3a 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -58,11 +58,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Padding(to_hstring(value)); } + winrt::hstring EvaluatedIcon() const + { + return _profile.EvaluatedIcon(); + } + // starting directory bool UseParentProcessDirectory(); void UseParentProcessDirectory(const bool useParent); bool UseCustomStartingDirectory(); + // icon + bool HideIcon(); + void HideIcon(const bool hide); + // general profile knowledge winrt::guid OriginalProfileGuid() const noexcept; bool CanDeleteProfile() const; @@ -119,6 +128,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::guid _originalProfileGuid{}; winrt::hstring _lastBgImagePath; winrt::hstring _lastStartingDirectoryPath; + winrt::hstring _lastIcon; Editor::AppearanceViewModel _defaultAppearanceViewModel; static Windows::Foundation::Collections::IObservableVector _MonospaceFontList; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 5020ecb7945..f9f618c8b80 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -67,6 +67,7 @@ namespace Microsoft.Terminal.Settings.Editor ProfileSubPage CurrentPage; Boolean UseParentProcessDirectory; Boolean UseCustomStartingDirectory { get; }; + Boolean HideIcon; AppearanceViewModel DefaultAppearance { get; }; Guid OriginalProfileGuid { get; }; Boolean HasUnfocusedAppearance { get; }; @@ -75,6 +76,8 @@ namespace Microsoft.Terminal.Settings.Editor AppearanceViewModel UnfocusedAppearance { get; }; Boolean VtPassthroughAvailable { get; }; + String EvaluatedIcon { get; }; + void CreateUnfocusedAppearance(); void DeleteUnfocusedAppearance(); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml index 11885a52fe3..28f2901261f 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Base.xaml @@ -108,13 +108,20 @@ + Text="{x:Bind Profile.Icon, Mode=TwoWay}" + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(Profile.HideIcon), Mode=OneWay}" /> - diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index baa8c0bccff..df9f4ae6f9c 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -135,6 +135,18 @@ Duplicate Button label that duplicates the selected profile and navigates to the duplicate's page. + + This color scheme is part of the built-in settings or an installed extension + + + + To make changes to this color scheme, you must make a copy of it. + + + + Make a copy + This is a call to action; the user can click the button to make a copy of the current color scheme. + Set color scheme as default This is the header for a control that allows the user to set the currently selected color scheme as their default. diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 9f193851508..4180c8d8c38 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -44,6 +44,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::com_ptr baseLayerProfile; std::vector> profiles; std::unordered_map> profilesByGuid; + std::unordered_map> colorSchemes; + std::unordered_map colorSchemeRemappings; + bool fixupsAppliedDuringLoad{ false }; void clear(); }; @@ -60,6 +63,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void MergeFragmentIntoUserSettings(const winrt::hstring& source, const std::string_view& content); void FinalizeLayering(); bool DisableDeletedProfiles(); + bool RemapColorSchemeForProfile(const winrt::com_ptr& profile); bool FixupUserSettings(); ParsedSettings inboxSettings; @@ -87,6 +91,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::com_ptr _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson); void _appendProfile(winrt::com_ptr&& profile, const winrt::guid& guid, ParsedSettings& settings); void _addUserProfileParent(const winrt::com_ptr& profile); + void _addOrMergeUserColorScheme(const winrt::com_ptr& colorScheme); void _executeGenerator(const IDynamicProfileGenerator& generator); std::unordered_set _ignoredNamespaces; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index c22d421975a..e3796a9b7d6 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -113,6 +113,8 @@ void ParsedSettings::clear() baseLayerProfile = {}; profiles.clear(); profilesByGuid.clear(); + colorSchemes.clear(); + fixupsAppliedDuringLoad = false; } // This is a convenience method used by the CascadiaSettings constructor. @@ -123,6 +125,7 @@ SettingsLoader SettingsLoader::Default(const std::string_view& userJSON, const s SettingsLoader loader{ userJSON, inboxJSON }; loader.MergeInboxIntoUserSettings(); loader.FinalizeLayering(); + loader.FixupUserSettings(); return loader; } @@ -210,6 +213,7 @@ void SettingsLoader::ApplyRuntimeInitialSettings() // Adds profiles from .inboxSettings as parents of matching profiles in .userSettings. // That way the user profiles will get appropriate defaults from the generators (like icons and such). // If a matching profile doesn't exist yet in .userSettings, one will be created. +// Additionally, produces a final view of the color schemes from the inbox + user settings void SettingsLoader::MergeInboxIntoUserSettings() { for (const auto& profile : inboxSettings.profiles) @@ -333,6 +337,11 @@ void SettingsLoader::MergeFragmentIntoUserSettings(const winrt::hstring& source, // by MergeInboxIntoUserSettings/FindFragmentsAndMergeIntoUserSettings). void SettingsLoader::FinalizeLayering() { + for (const auto& colorScheme : inboxSettings.colorSchemes) + { + _addOrMergeUserColorScheme(colorScheme.second); + } + // Layer default globals -> user globals userSettings.globals->AddLeastImportantParent(inboxSettings.globals); @@ -403,6 +412,42 @@ bool SettingsLoader::DisableDeletedProfiles() return newGeneratedProfiles; } +bool winrt::Microsoft::Terminal::Settings::Model::implementation::SettingsLoader::RemapColorSchemeForProfile(const winrt::com_ptr& profile) +{ + bool modified{ false }; + + const IAppearanceConfig appearances[] = { + profile->DefaultAppearance(), + profile->UnfocusedAppearance() + }; + + for (auto&& appearance : appearances) + { + if (appearance) + { + if (auto schemeName{ appearance.LightColorSchemeName() }; !schemeName.empty()) + { + if (auto found{ userSettings.colorSchemeRemappings.find(schemeName) }; found != userSettings.colorSchemeRemappings.end()) + { + appearance.LightColorSchemeName(found->second); + modified = true; + } + } + + if (auto schemeName{ appearance.DarkColorSchemeName() }; !schemeName.empty()) + { + if (auto found{ userSettings.colorSchemeRemappings.find(schemeName) }; found != userSettings.colorSchemeRemappings.end()) + { + appearance.DarkColorSchemeName(found->second); + modified = true; + } + } + } + } + + return modified; +} + // Runs migrations and fixups on user settings. // Returns true if something got changed and // the settings need to be saved to disk. @@ -420,10 +465,13 @@ bool SettingsLoader::FixupUserSettings() CommandlinePatch{ DEFAULT_WINDOWS_POWERSHELL_GUID, L"powershell.exe", L"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, }; - auto fixedUp = false; + auto fixedUp = userSettings.fixupsAppliedDuringLoad; + fixedUp = RemapColorSchemeForProfile(userSettings.baseLayerProfile) || fixedUp; for (const auto& profile : userSettings.profiles) { + fixedUp = RemapColorSchemeForProfile(profile) || fixedUp; + if (!profile->HasCommandline()) { continue; @@ -574,7 +622,8 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source { if (const auto scheme = ColorScheme::FromJson(schemeJson)) { - settings.globals->AddColorScheme(*scheme); + scheme->Origin(origin); + settings.colorSchemes.emplace(scheme->Name(), std::move(scheme)); } } } @@ -642,7 +691,11 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str { if (const auto scheme = ColorScheme::FromJson(schemeJson)) { - settings.globals->AddColorScheme(*scheme); + scheme->Origin(OriginTag::Fragment); + // Don't add the color scheme to the Fragment's GlobalSettings; that will + // cause layering issues later. Add them to a staging area for later processing. + // (search for STAGED COLORS to find the next step) + settings.colorSchemes.emplace(scheme->Name(), std::move(scheme)); } } CATCH_LOG() @@ -693,6 +746,14 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str } } + // STAGED COLORS are processed here: we merge them into the partially-loaded + // settings directly so that we can resolve conflicts between user-generated + // color schemes and fragment-originated ones. + for (const auto& fragmentColorScheme : settings.colorSchemes) + { + _addOrMergeUserColorScheme(fragmentColorScheme.second); + } + // Add the parsed fragment globals as a parent of the user's settings. // Later, in FinalizeInheritance, this will result in the action map from // the fragments being applied before the user's own settings. @@ -796,6 +857,37 @@ void SettingsLoader::_addUserProfileParent(const winrt::com_ptr& newScheme) +{ + // On entry, all the user color schemes have been loaded. Therefore, any insertions of inbox or fragment schemes + // will fail; we can leverage this to detect when they are equivalent and delete the user's duplicate copies. + // If the user has changed the otherwise "duplicate" scheme, though, we will move it aside. + if (const auto [it, inserted] = userSettings.colorSchemes.emplace(newScheme->Name(), newScheme); !inserted) + { + // This scheme was not inserted because one already existed. + auto existingScheme{ it->second }; + if (existingScheme->Origin() == OriginTag::User) // we only want to impose ordering on User schemes + { + it->second = newScheme; // Stomp the user's existing scheme with the one we just got (to make sure the right Origin is set) + userSettings.fixupsAppliedDuringLoad = true; // Make sure we save the settings. + if (!existingScheme->IsEquivalentForSettingsMergePurposes(newScheme)) + { + hstring newName{ fmt::format(FMT_COMPILE(L"{} (modified)"), existingScheme->Name()) }; + int differentiator = 2; + while (userSettings.colorSchemes.contains(newName)) + { + newName = hstring{ fmt::format(FMT_COMPILE(L"{} (modified {})"), existingScheme->Name(), differentiator++) }; + } + // Rename the user's scheme. + existingScheme->Name(newName); + userSettings.colorSchemeRemappings.emplace(newScheme->Name(), newName); + // And re-add it to the end. + userSettings.colorSchemes.emplace(newName, std::move(existingScheme)); + } + } + } +} + // As the name implies it executes a generator. // Generated profiles are added to .inboxSettings. Used by GenerateProfiles(). void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator) @@ -1064,6 +1156,16 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) : allProfiles.reserve(loader.userSettings.profiles.size()); activeProfiles.reserve(loader.userSettings.profiles.size()); + for (const auto& colorScheme : loader.userSettings.colorSchemes) + { + loader.userSettings.globals->AddColorScheme(*colorScheme.second); + } + + // SettingsLoader and ParsedSettings are supposed to always + // create these two members. We don't want null-pointer exceptions. + assert(loader.userSettings.globals != nullptr); + assert(loader.userSettings.baseLayerProfile != nullptr); + for (const auto& profile : loader.userSettings.profiles) { // If a generator stops producing a certain profile (e.g. WSL or PowerShell were removed) or @@ -1101,11 +1203,6 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) : warnings.emplace_back(Model::SettingsLoadWarnings::DuplicateProfile); } - // SettingsLoader and ParsedSettings are supposed to always - // create these two members. We don't want null-pointer exceptions. - assert(loader.userSettings.globals != nullptr); - assert(loader.userSettings.baseLayerProfile != nullptr); - _globals = loader.userSettings.globals; _baseLayerProfile = loader.userSettings.baseLayerProfile; _allProfiles = winrt::single_threaded_observable_vector(std::move(allProfiles)); @@ -1271,14 +1368,14 @@ Json::Value CascadiaSettings::ToJson() const profiles[JsonKey(ProfilesListKey)] = profilesList; json[JsonKey(ProfilesKey)] = profiles; - // TODO GH#8100: - // "schemes" will be an accumulation of _all_ the color schemes - // including all of the ones from defaults.json Json::Value schemes{ Json::ValueType::arrayValue }; for (const auto& entry : _globals->ColorSchemes()) { const auto scheme{ winrt::get_self(entry.Value()) }; - schemes.append(scheme->ToJson()); + if (scheme->Origin() == OriginTag::User) + { + schemes.append(scheme->ToJson()); + } } json[JsonKey(SchemesKey)] = schemes; diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.cpp b/src/cascadia/TerminalSettingsModel/ColorScheme.cpp index 62f405e48fb..6b49fed7d65 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.cpp +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.cpp @@ -52,7 +52,8 @@ ColorScheme::ColorScheme() noexcept : } ColorScheme::ColorScheme(const winrt::hstring& name) noexcept : - _Name{ name } + _Name{ name }, + _Origin{ OriginTag::User } { const auto table = Utils::CampbellColorTable(); std::copy_n(table.data(), table.size(), _table.data()); @@ -67,6 +68,7 @@ winrt::com_ptr ColorScheme::Copy() const scheme->_SelectionBackground = _SelectionBackground; scheme->_CursorColor = _CursorColor; scheme->_table = _table; + scheme->_Origin = _Origin; return scheme; } @@ -188,3 +190,11 @@ winrt::Microsoft::Terminal::Core::Scheme ColorScheme::ToCoreScheme() const noexc coreScheme.BrightWhite = Table()[15]; return coreScheme; } + +bool ColorScheme::IsEquivalentForSettingsMergePurposes(const winrt::com_ptr& other) noexcept +{ + // The caller likely only got here if the names were the same, so skip checking that one. + // We do not care about the cursor color or the selection background, as the main reason we are + // doing equivalence merging is to replace old, poorly-specified versions of those two properties. + return _table == other->_table && _Background == other->_Background && _Foreground == other->_Foreground; +} diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.h b/src/cascadia/TerminalSettingsModel/ColorScheme.h index f72b7429fce..82d31cea81f 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.h +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.h @@ -51,7 +51,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation com_array Table() const noexcept; void SetColorTableEntry(uint8_t index, const Core::Color& value) noexcept; + bool IsEquivalentForSettingsMergePurposes(const winrt::com_ptr& other) noexcept; + WINRT_PROPERTY(winrt::hstring, Name); + WINRT_PROPERTY(OriginTag, Origin, OriginTag::None); WINRT_PROPERTY(Core::Color, Foreground, static_cast(DEFAULT_FOREGROUND)); WINRT_PROPERTY(Core::Color, Background, static_cast(DEFAULT_BACKGROUND)); WINRT_PROPERTY(Core::Color, SelectionBackground, static_cast(DEFAULT_FOREGROUND)); diff --git a/src/cascadia/TerminalSettingsModel/ColorScheme.idl b/src/cascadia/TerminalSettingsModel/ColorScheme.idl index 8e758cfa6b2..dd55624a530 100644 --- a/src/cascadia/TerminalSettingsModel/ColorScheme.idl +++ b/src/cascadia/TerminalSettingsModel/ColorScheme.idl @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "ISettingsModelObject.idl"; + namespace Microsoft.Terminal.Settings.Model { - [default_interface] runtimeclass ColorScheme : Windows.Foundation.IStringable { + [default_interface] runtimeclass ColorScheme : Windows.Foundation.IStringable, ISettingsModelObject { ColorScheme(); ColorScheme(String name); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 5cc65d913e4..a720c261b39 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -9,6 +9,8 @@ #include "GlobalAppSettings.g.cpp" +#include + using namespace Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; using namespace winrt::Windows::UI::Xaml; @@ -34,13 +36,6 @@ void GlobalAppSettings::_FinalizeInheritance() { _actionMap->AddLeastImportantParent(parent->_actionMap); _keybindingsWarnings.insert(_keybindingsWarnings.end(), parent->_keybindingsWarnings.begin(), parent->_keybindingsWarnings.end()); - for (const auto& [k, v] : parent->_colorSchemes) - { - if (!_colorSchemes.HasKey(k)) - { - _colorSchemes.Insert(k, v); - } - } for (const auto& [k, v] : parent->_themes) { @@ -183,6 +178,28 @@ void GlobalAppSettings::RemoveColorScheme(hstring schemeName) _colorSchemes.TryRemove(schemeName); } +winrt::Microsoft::Terminal::Settings::Model::ColorScheme GlobalAppSettings::DuplicateColorScheme(const Model::ColorScheme& source) +{ + THROW_HR_IF_NULL(E_INVALIDARG, source); + + auto newName = fmt::format(FMT_COMPILE(L"{} ({})"), source.Name(), RS_(L"CopySuffix")); + auto nextCandidateIndex = 2; + + // Check if this name already exists and if so, append a number + while (_colorSchemes.HasKey(newName)) + { + // There is a theoretical unsigned integer wraparound, which is OK + newName = fmt::format(FMT_COMPILE(L"{} ({} {})"), source.Name(), RS_(L"CopySuffix"), nextCandidateIndex++); + } + + auto duplicated{ winrt::get_self(source)->Copy() }; + duplicated->Name(hstring{ newName }); + duplicated->Origin(OriginTag::User); + + AddColorScheme(*duplicated); + return *duplicated; +} + // Method Description: // - Return the warnings that we've collected during parsing the JSON for the // keybindings. It's possible that the user provided keybindings have some diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 1af8479e0c6..21d73483c02 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -44,6 +44,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IMapView ColorSchemes() noexcept; void AddColorScheme(const Model::ColorScheme& scheme); void RemoveColorScheme(hstring schemeName); + Model::ColorScheme DuplicateColorScheme(const Model::ColorScheme& scheme); Model::ActionMap ActionMap() const noexcept; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 20cb1d90528..612262d4b11 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -106,6 +106,7 @@ namespace Microsoft.Terminal.Settings.Model Windows.Foundation.Collections.IMapView ColorSchemes(); void AddColorScheme(ColorScheme scheme); void RemoveColorScheme(String schemeName); + ColorScheme DuplicateColorScheme(ColorScheme scheme); ActionMap ActionMap { get; }; diff --git a/src/cascadia/TerminalSettingsModel/ISettingsModelObject.idl b/src/cascadia/TerminalSettingsModel/ISettingsModelObject.idl new file mode 100644 index 00000000000..21bc4b9c326 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ISettingsModelObject.idl @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Model +{ + // This tag is used to identify the context in which this object was created + enum OriginTag + { + None = 0, + User, + InBox, + Generated, + Fragment, + ProfilesDefaults + }; + + interface ISettingsModelObject { + OriginTag Origin { get; }; + } +} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 2f77b745661..7c54f9e65a9 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -237,6 +237,7 @@ + diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 14e074bc6db..9023fd16edb 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -2,6 +2,7 @@ // Licensed under the MIT license. import "IAppearanceConfig.idl"; +import "ISettingsModelObject.idl"; import "FontConfig.idl"; #include "IInheritable.idl.h" @@ -13,17 +14,6 @@ import "FontConfig.idl"; namespace Microsoft.Terminal.Settings.Model { - // This tag is used to identify the context in which the Profile was created - enum OriginTag - { - None = 0, - User, - InBox, - Generated, - Fragment, - ProfilesDefaults - }; - enum CloseOnExitMode { Never = 0, @@ -42,7 +32,7 @@ namespace Microsoft.Terminal.Settings.Model All = 0xffffffff }; - [default_interface] runtimeclass Profile : Windows.Foundation.IStringable { + [default_interface] runtimeclass Profile : Windows.Foundation.IStringable, ISettingsModelObject { Profile(); Profile(Guid guid); @@ -51,7 +41,6 @@ namespace Microsoft.Terminal.Settings.Model // True if the user explicitly removed this Profile from settings.json. Boolean Deleted { get; }; - OriginTag Origin { get; }; // Helper for magically using a commandline for an icon for a profile // without an explicit icon. From e7796e7db3662c9edc5a4306e647602f07690166 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Thu, 29 Feb 2024 00:34:40 +0800 Subject: [PATCH 34/50] Fix the hyperlink detection when there are leading wide glyph in the row (#16775) ## Summary of the Pull Request URL detection was broken again in #15858. When the regex matched, we calculate the column(cell) by its offset, we use forward or backward iteration of the column to find the correct column that displays the glyphs of `_chars[offset]`. https://github.com/microsoft/terminal/blob/abf5d9423a23b13e9af4c19ca35858f9aaf0a63f/src/buffer/out/Row.cpp#L95-L104 However, when calculating the `currentOffset` we forget that MSB of `_charOffsets[col]` could be `1`, or col is pointing to another glyph in preceding column. https://github.com/microsoft/terminal/blob/abf5d9423a23b13e9af4c19ca35858f9aaf0a63f/src/buffer/out/Row.hpp#L223-L226 --- src/buffer/out/Row.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 87ab005d3bb..7729cdcdbd4 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -97,7 +97,7 @@ til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept offset = clamp(offset, 0, _lastCharOffset); auto col = _currentColumn; - const auto currentOffset = _charOffsets[col]; + const auto currentOffset = _charOffsets[col] & CharOffsetsMask; // Goal: Move the _currentColumn cursor to a cell which contains the given target offset. // Depending on where the target offset is we have to either search forward or backward. From badc00e83bf8f4109f40e9533e8d9d894abfc531 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 28 Feb 2024 17:57:22 +0100 Subject: [PATCH 35/50] Add new and extend existing til::string helpers (#16772) `wstring_case_insensitive_compare` is not a great name for what it does as it's incorrect to use for regular (human readable) strings. This PR thus renames it to `env_key_sorter`. `compare_string_ordinal` was renamed to `compare_ordinal_insensitive` to make sure callers know that the comparison is insensitive (this may otherwise be incorrect in certain contexts after all). The return value was changed to match `memcmp` so that the API is detached from its underlying implementation (= NLS). `compare_linguistic_insensitive` and `contains_linguistic_insensitive` were added to sort and filter human-readable strings respectively. `prefix_split` was extended to allow for needles that are just a single character. This significantly improves the generated assembly and is also usually what someone would want to actually use. I've left the string-as-needle variant in just in case. This PR is prep-work for #2664 --- .../TerminalConnection/ConptyConnection.cpp | 2 +- .../TerminalSettingsEditor/Appearances.h | 8 -- .../ProfileViewModel.cpp | 11 +- .../TerminalSettingsEditor/ProfileViewModel.h | 1 - .../CascadiaSettings.cpp | 5 +- .../KeyChordSerialization.cpp | 2 +- src/inc/til/env.h | 28 +++- src/inc/til/string.h | 124 +++++++++++++----- src/til/ut_til/string.cpp | 18 +++ 9 files changed, 141 insertions(+), 58 deletions(-) diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 67c3e5699bd..d5789a2b3e5 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -98,7 +98,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation if (_environment) { // Order the environment variable names so that resolution order is consistent - std::set keys{}; + std::set keys{}; for (const auto item : _environment) { keys.insert(item.Key().c_str()); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index 4f220ef5e78..f89d0fa2305 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -25,14 +25,6 @@ Author(s): namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - struct FontComparator - { - bool operator()(const Font& lhs, const Font& rhs) const - { - return lhs.LocalizedName() < rhs.LocalizedName(); - } - }; - struct Font : FontT { public: diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 5f7517068b4..4b1ee724ad9 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -5,6 +5,7 @@ #include "ProfileViewModel.h" #include "ProfileViewModel.g.cpp" #include "EnumEntry.h" +#include "Appearances.h" #include #include "../WinRTUtils/inc/Utils.h" @@ -154,11 +155,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation CATCH_LOG(); } + const auto comparator = [&](const Editor::Font& lhs, const Editor::Font& rhs) { + const auto a = lhs.LocalizedName(); + const auto b = rhs.LocalizedName(); + return til::compare_linguistic_insensitive(a, b) < 0; + }; + // sort and save the lists - std::sort(begin(fontList), end(fontList), FontComparator()); + std::sort(begin(fontList), end(fontList), comparator); _FontList = single_threaded_observable_vector(std::move(fontList)); - std::sort(begin(monospaceFontList), end(monospaceFontList), FontComparator()); + std::sort(begin(monospaceFontList), end(monospaceFontList), comparator); _MonospaceFontList = single_threaded_observable_vector(std::move(monospaceFontList)); } CATCH_LOG(); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 7770c1b2f3a..c7c836f818b 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -8,7 +8,6 @@ #include "ProfileViewModel.g.h" #include "Utils.h" #include "ViewModelHelpers.h" -#include "Appearances.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index ad9ac6df600..5971d1e7b99 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -13,9 +13,8 @@ #include #include -#include #include -#include +#include using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings; @@ -555,7 +554,7 @@ void CascadiaSettings::_validateProfileEnvironmentVariables() { for (const auto& profile : _allProfiles) { - std::set envVarNames{}; + std::set envVarNames{}; if (profile.EnvironmentVariables() == nullptr) { continue; diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index c4a5a0cfec0..b54aa289afc 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -153,7 +153,7 @@ static KeyChord _fromString(std::wstring_view wstr) while (!wstr.empty()) { - const auto part = til::prefix_split(wstr, L"+"); + const auto part = til::prefix_split(wstr, L'+'); if (til::equals_insensitive_ascii(part, CTRL_KEY)) { diff --git a/src/inc/til/env.h b/src/inc/til/env.h index 1546a83afb6..33d7fa70e40 100644 --- a/src/inc/til/env.h +++ b/src/inc/til/env.h @@ -19,6 +19,22 @@ class EnvTests; namespace til // Terminal Implementation Library. Also: "Today I Learned" { + // A case-insensitive wide-character map is used to store environment variables + // due to documented requirements: + // + // "All strings in the environment block must be sorted alphabetically by name. + // The sort is case-insensitive, Unicode order, without regard to locale. + // Because the equal sign is a separator, it must not be used in the name of + // an environment variable." + // https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables + struct env_key_sorter + { + [[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept + { + return compare_ordinal_insensitive(lhs, rhs) < 0; + } + }; + namespace details { @@ -161,7 +177,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" friend class ::EnvTests; #endif - std::map _envMap{}; + std::map _envMap{}; // We make copies of the environment variable names to ensure they are null terminated. void get(wil::zwstring_view variable) @@ -348,8 +364,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { static constexpr std::wstring_view temp{ L"temp" }; static constexpr std::wstring_view tmp{ L"tmp" }; - if (til::compare_string_ordinal(var, temp) == CSTR_EQUAL || - til::compare_string_ordinal(var, tmp) == CSTR_EQUAL) + if (til::compare_ordinal_insensitive(var, temp) == 0 || + til::compare_ordinal_insensitive(var, tmp) == 0) { return til::details::wil_env::GetShortPathNameW(value.data()); } @@ -364,9 +380,9 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" static constexpr std::wstring_view path{ L"Path" }; static constexpr std::wstring_view libPath{ L"LibPath" }; static constexpr std::wstring_view os2LibPath{ L"Os2LibPath" }; - return til::compare_string_ordinal(input, path) == CSTR_EQUAL || - til::compare_string_ordinal(input, libPath) == CSTR_EQUAL || - til::compare_string_ordinal(input, os2LibPath) == CSTR_EQUAL; + return til::compare_ordinal_insensitive(input, path) == 0 || + til::compare_ordinal_insensitive(input, libPath) == 0 || + til::compare_ordinal_insensitive(input, os2LibPath) == 0; } void parse(const wchar_t* lastCh) diff --git a/src/inc/til/string.h b/src/inc/til/string.h index c32de1e0531..e76639000cf 100644 --- a/src/inc/til/string.h +++ b/src/inc/til/string.h @@ -315,65 +315,117 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // * return "foo" // If the needle cannot be found the "str" argument is returned as is. template - std::basic_string_view prefix_split(std::basic_string_view& str, const std::basic_string_view& needle) noexcept + constexpr std::basic_string_view prefix_split(std::basic_string_view& str, const std::basic_string_view& needle) noexcept { using view_type = std::basic_string_view; - const auto idx = str.find(needle); - // > If the needle cannot be found the "str" argument is returned as is. - // ...but if needle is empty, idx will always be npos, forcing us to return str. - if (idx == view_type::npos || needle.empty()) - { - return std::exchange(str, {}); - } + const auto needleLen = needle.size(); + const auto idx = needleLen == 0 ? str.size() : str.find(needle); + const auto prefixIdx = std::min(str.size(), idx); + const auto suffixIdx = std::min(str.size(), prefixIdx + needle.size()); - const auto suffixIdx = idx + needle.size(); - const view_type result{ str.data(), idx }; + const view_type result{ str.data(), prefixIdx }; #pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead str = { str.data() + suffixIdx, str.size() - suffixIdx }; return result; } - inline std::string_view prefix_split(std::string_view& str, const std::string_view& needle) noexcept + constexpr std::string_view prefix_split(std::string_view& str, const std::string_view& needle) noexcept { return prefix_split<>(str, needle); } - inline std::wstring_view prefix_split(std::wstring_view& str, const std::wstring_view& needle) noexcept + constexpr std::wstring_view prefix_split(std::wstring_view& str, const std::wstring_view& needle) noexcept { return prefix_split<>(str, needle); } - // - // A case-insensitive wide-character map is used to store environment variables - // due to documented requirements: - // - // "All strings in the environment block must be sorted alphabetically by name. - // The sort is case-insensitive, Unicode order, without regard to locale. - // Because the equal sign is a separator, it must not be used in the name of - // an environment variable." - // https://docs.microsoft.com/en-us/windows/desktop/ProcThread/changing-environment-variables - // - // - Returns CSTR_LESS_THAN, CSTR_EQUAL or CSTR_GREATER_THAN - [[nodiscard]] inline int compare_string_ordinal(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept - { - const auto result = CompareStringOrdinal( - lhs.data(), - ::base::saturated_cast(lhs.size()), - rhs.data(), - ::base::saturated_cast(rhs.size()), - TRUE); - FAIL_FAST_LAST_ERROR_IF(!result); + // Give the arguments ("foo bar baz", " "), this method will + // * modify the first argument to "bar baz" + // * return "foo" + // If the needle cannot be found the "str" argument is returned as is. + template + constexpr std::basic_string_view prefix_split(std::basic_string_view& str, T ch) noexcept + { + using view_type = std::basic_string_view; + + const auto idx = str.find(ch); + const auto prefixIdx = std::min(str.size(), idx); + const auto suffixIdx = std::min(str.size(), prefixIdx + 1); + + const view_type result{ str.data(), prefixIdx }; +#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead + str = { str.data() + suffixIdx, str.size() - suffixIdx }; return result; } - struct wstring_case_insensitive_compare + template + constexpr std::basic_string_view trim(const std::basic_string_view& str, const T ch) noexcept { - [[nodiscard]] bool operator()(const std::wstring& lhs, const std::wstring& rhs) const noexcept + auto beg = str.data(); + auto end = beg + str.size(); + + for (; beg != end && *beg == ch; ++beg) { - return compare_string_ordinal(lhs, rhs) == CSTR_LESS_THAN; } - }; + + for (; beg != end && end[-1] == ch; --end) + { + } + + return { beg, end }; + } + + // 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 + { + const auto lhsLen = ::base::saturated_cast(lhs.size()); + const auto rhsLen = ::base::saturated_cast(rhs.size()); + // MSDN: + // > To maintain the C runtime convention of comparing strings, + // > the value 2 can be subtracted from a nonzero return value. + // > [...] + // > The function returns 0 if it does not succeed. [...] following error codes: + // > * ERROR_INVALID_PARAMETER. Any of the parameter values was invalid. + // -> We can just subtract 2. + return CompareStringOrdinal(lhs.data(), lhsLen, rhs.data(), rhsLen, TRUE) - 2; + } + + // This function is appropriate for sorting strings primarily used for human consumption, like a list of file names. + // Similar to memcmp, this returns <0, 0 or >0. + inline int compare_linguistic_insensitive(const std::wstring_view& lhs, const std::wstring_view& rhs) noexcept + { + const auto lhsLen = ::base::saturated_cast(lhs.size()); + const auto rhsLen = ::base::saturated_cast(rhs.size()); + // MSDN: + // > To maintain the C runtime convention of comparing strings, + // > the value 2 can be subtracted from a nonzero return value. + // > [...] + // > The function returns 0 if it does not succeed. [...] following error codes: + // > * ERROR_INVALID_FLAGS. The values supplied for flags were invalid. + // > * ERROR_INVALID_PARAMETER. Any of the parameter values was invalid. + // -> We can just subtract 2. +#pragma warning(suppress : 26477) // Use 'nullptr' rather than 0 or NULL (es.47). + return CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE, lhs.data(), lhsLen, rhs.data(), rhsLen, nullptr, nullptr, 0) - 2; + } + + // This function is appropriate for strings primarily used for human consumption, like a list of file names. + inline bool contains_linguistic_insensitive(const std::wstring_view& str, const std::wstring_view& needle) noexcept + { + const auto strLen = ::base::saturated_cast(str.size()); + const auto needleLen = ::base::saturated_cast(needle.size()); + // MSDN: + // > Returns a 0-based index into the source string indicated by lpStringSource if successful. + // > [...] + // > The function returns -1 if it does not succeed. + // > * ERROR_INVALID_FLAGS. The values supplied for flags were not valid. + // > * ERROR_INVALID_PARAMETER. Any of the parameter values was invalid. + // > * ERROR_SUCCESS. The action completed successfully but yielded no results. + // -> We can just check for -1. +#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 diff --git a/src/til/ut_til/string.cpp b/src/til/ut_til/string.cpp index 76bb6198b58..26fbf92ea47 100644 --- a/src/til/ut_til/string.cpp +++ b/src/til/ut_til/string.cpp @@ -170,6 +170,24 @@ class StringTests } } + TEST_METHOD(prefix_split_char) + { + { + std::string_view s{ "" }; + VERIFY_ARE_EQUAL("", til::prefix_split(s, ' ')); + VERIFY_ARE_EQUAL("", s); + } + { + std::string_view s{ "foo bar baz" }; + VERIFY_ARE_EQUAL("foo", til::prefix_split(s, ' ')); + VERIFY_ARE_EQUAL("bar baz", s); + VERIFY_ARE_EQUAL("bar", til::prefix_split(s, ' ')); + VERIFY_ARE_EQUAL("baz", s); + VERIFY_ARE_EQUAL("baz", til::prefix_split(s, ' ')); + VERIFY_ARE_EQUAL("", s); + } + } + TEST_METHOD(CleanPathAndFilename) { VERIFY_ARE_EQUAL(LR"(CUsersGeddyMusicAnalog Man)", til::clean_filename(LR"(C:\Users\Geddy\Music\"Analog Man")")); From 94e74d22c65ecec7fb3baacb837527f254bddf1f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 28 Feb 2024 18:25:58 +0100 Subject: [PATCH 36/50] Make shaded block glyphs look even betterer (#16760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shaded glyphs (U+2591..3, etc.) all have one problem in common: The cell size may not be evenly divisible by the pixel/dot size in the glyph. This either results in blurring, or in moiré-like patterns at the edges of the cells with its neighbors, because they happen to start with a pattern that overlaps with the end of the previous cell. This PR solves the issue by moving the pixel/dot pattern generation into the shader. That way the pixel/dot location can be made dependent on the viewport-position of the actual underlying pixels, which avoids repeating patterns between cells. The PR contains some additional modifications, all of which either extend or improve the existing debug facilities in AtlasEngine. Suppressing whitespaces changes makes the diff way smaller. --- src/renderer/atlas/AtlasEngine.api.cpp | 20 +-- src/renderer/atlas/Backend.h | 12 ++ src/renderer/atlas/BackendD3D.cpp | 193 ++++++++++++++----------- src/renderer/atlas/BackendD3D.h | 24 +-- src/renderer/atlas/BuiltinGlyphs.cpp | 75 +++------- src/renderer/atlas/dwrite.hlsl | 4 +- src/renderer/atlas/shader_common.hlsl | 21 ++- src/renderer/atlas/shader_ps.hlsl | 86 ++++++++++- src/renderer/gdi/gdirenderer.hpp | 2 +- src/renderer/gdi/paint.cpp | 2 +- src/renderer/gdi/state.cpp | 18 +-- 11 files changed, 270 insertions(+), 187 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index fa7290371e8..30cac1da931 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -688,10 +688,11 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness)); const auto strikethroughPos = std::roundf(baseline + strikethroughPosition); const auto strikethroughWidth = std::max(1.0f, std::roundf(strikethroughThickness)); - const auto thinLineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f)); + const auto doubleUnderlineWidth = std::max(1.0f, std::roundf(underlineThickness / 2.0f)); + const auto thinLineWidth = std::max(1.0f, std::roundf(std::max(adjustedWidth / 16.0f, adjustedHeight / 32.0f))); // For double underlines we loosely follow what Word does: - // 1. The lines are half the width of an underline (= thinLineWidth) + // 1. The lines are half the width of an underline (= doubleUnderlineWidth) // 2. Ideally the bottom line is aligned with the bottom of the underline // 3. The top underline is vertically in the middle between baseline and ideal bottom underline // 4. If the top line gets too close to the baseline the underlines are shifted downwards @@ -699,18 +700,18 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo // (Additional notes below.) // 2. - auto doubleUnderlinePosBottom = underlinePos + underlineWidth - thinLineWidth; + auto doubleUnderlinePosBottom = underlinePos + underlineWidth - doubleUnderlineWidth; // 3. Since we don't align the center of our two lines, but rather the top borders // we need to subtract half a line width from our center point. - auto doubleUnderlinePosTop = std::roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f); + auto doubleUnderlinePosTop = std::roundf((baseline + doubleUnderlinePosBottom - doubleUnderlineWidth) / 2.0f); // 4. - doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth); + doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + doubleUnderlineWidth); // 5. The gap is only the distance _between_ the lines, but we need the distance from the // top border of the top and bottom lines, which includes an additional line width. const auto doubleUnderlineGap = std::max(1.0f, std::roundf(1.2f / 72.0f * dpi)); - doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth); + doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + doubleUnderlineWidth); // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. - doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth); + doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - doubleUnderlineWidth); const auto cellWidth = gsl::narrow(lrintf(adjustedWidth)); const auto cellHeight = gsl::narrow(lrintf(adjustedHeight)); @@ -749,6 +750,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo const auto strikethroughWidthU16 = gsl::narrow_cast(lrintf(strikethroughWidth)); const auto doubleUnderlinePosTopU16 = gsl::narrow_cast(lrintf(doubleUnderlinePosTop)); const auto doubleUnderlinePosBottomU16 = gsl::narrow_cast(lrintf(doubleUnderlinePosBottom)); + const auto doubleUnderlineWidthU16 = gsl::narrow_cast(lrintf(doubleUnderlineWidth)); // NOTE: From this point onward no early returns or throwing code should exist, // as we might cause _api to be in an inconsistent state otherwise. @@ -771,8 +773,8 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->underline = { underlinePosU16, underlineWidthU16 }; fontMetrics->strikethrough = { strikethroughPosU16, strikethroughWidthU16 }; - fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 }; - fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; + fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, doubleUnderlineWidthU16 }; + fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, doubleUnderlineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs(); diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index 9f2736eceef..ad22c1b20dd 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -7,6 +7,14 @@ namespace Microsoft::Console::Render::Atlas { + // Don't use this definition in the code elsewhere. + // It only exists to make the definitions below possible. +#ifdef NDEBUG +#define ATLAS_DEBUG__IS_DEBUG 0 +#else +#define ATLAS_DEBUG__IS_DEBUG 1 +#endif + // If set to 1, this will cause the entire viewport to be invalidated at all times. // Helpful for benchmarking our text shaping code based on DirectWrite. #define ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION 0 @@ -14,6 +22,10 @@ namespace Microsoft::Console::Render::Atlas // Redraw at display refresh rate at all times. This helps with shader debugging. #define ATLAS_DEBUG_CONTINUOUS_REDRAW 0 + // Hot reload the builtin .hlsl files whenever they change on disk. + // Enabled by default in debug builds. +#define ATLAS_DEBUG_SHADER_HOT_RELOAD ATLAS_DEBUG__IS_DEBUG + // Disables the use of DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT. // This helps with benchmarking the application as it'll run beyond display refresh rate. #define ATLAS_DEBUG_DISABLE_FRAME_LATENCY_WAITABLE_OBJECT 0 diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index dd4dcbf0c22..308518ec27e 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -25,6 +25,8 @@ TIL_FAST_MATH_BEGIN +#pragma warning(disable : 4100) // '...': unreferenced formal parameter +#pragma warning(disable : 26440) // Function '...' can be declared 'noexcept'(f.6). // This code packs various data into smaller-than-int types to save both CPU and GPU memory. This warning would force // us to add dozens upon dozens of gsl::narrow_cast<>s throughout the file which is more annoying than helpful. #pragma warning(disable : 4242) // '=': conversion from '...' to '...', possible loss of data @@ -158,7 +160,7 @@ BackendD3D::BackendD3D(const RenderingPayload& p) THROW_IF_FAILED(p.device->CreateBlendState(&desc, _blendState.addressof())); } -#ifndef NDEBUG +#if ATLAS_DEBUG_SHADER_HOT_RELOAD _sourceDirectory = std::filesystem::path{ __FILE__ }.parent_path(); _sourceCodeWatcher = wil::make_folder_change_reader_nothrow(_sourceDirectory.c_str(), false, wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime, [this](wil::FolderChangeEvent, PCWSTR path) { if (til::ends_with(path, L".hlsl")) @@ -186,9 +188,7 @@ void BackendD3D::Render(RenderingPayload& p) _handleSettingsUpdate(p); } -#ifndef NDEBUG _debugUpdateShaders(p); -#endif // After a Present() the render target becomes unbound. p.deviceContext->OMSetRenderTargets(1, _customRenderTargetView ? _customRenderTargetView.addressof() : _renderTargetView.addressof(), nullptr); @@ -205,9 +205,7 @@ void BackendD3D::Render(RenderingPayload& p) _drawCursorBackground(p); _drawText(p); _drawSelection(p); -#if ATLAS_DEBUG_SHOW_DIRTY _debugShowDirty(p); -#endif _flushQuads(p); if (_customPixelShader) @@ -215,9 +213,7 @@ void BackendD3D::Render(RenderingPayload& p) _executeCustomShader(p); } -#if ATLAS_DEBUG_DUMP_RENDER_TARGET _debugDumpRenderTarget(p); -#endif } bool BackendD3D::RequiresContinuousRedraw() noexcept @@ -277,13 +273,15 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) // limited space to draw a curlyline, we apply a limit on the peak height. { const auto cellHeight = static_cast(font.cellSize.y); - const auto strokeWidth = static_cast(font.thinLineWidth); + const auto duTop = static_cast(font.doubleUnderline[0].position); + const auto duBottom = static_cast(font.doubleUnderline[1].position); + const auto duHeight = static_cast(font.doubleUnderline[0].height); // This gives it the same position and height as our double-underline. There's no particular reason for that, apart from // it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc. // We still need to ensure though that it doesn't clip out of the cellHeight at the bottom. - const auto height = std::max(3.0f, static_cast(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position)); - const auto top = std::min(static_cast(font.doubleUnderline[0].position), floorf(cellHeight - height - strokeWidth)); + const auto height = std::max(3.0f, duBottom + duHeight - duTop); + const auto top = std::min(duTop, floorf(cellHeight - height - duHeight)); _curlyLineHalfHeight = height * 0.5f; _curlyUnderline.position = gsl::narrow_cast(lrintf(top)); @@ -532,8 +530,9 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; - data.thinLineWidth = p.s->font->thinLineWidth; + data.doubleUnderlineWidth = p.s->font->doubleUnderline[0].height; data.curlyLineHalfHeight = _curlyLineHalfHeight; + data.shadedGlyphDotSize = std::max(1.0f, std::roundf(std::max(p.s->font->cellSize.x / 16.0f, p.s->font->cellSize.y / 32.0f))); p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } } @@ -570,92 +569,101 @@ void BackendD3D::_setupDeviceContextState(const RenderingPayload& p) p.deviceContext->OMSetRenderTargets(1, _customRenderTargetView ? _customRenderTargetView.addressof() : _renderTargetView.addressof(), nullptr); } -#ifndef NDEBUG void BackendD3D::_debugUpdateShaders(const RenderingPayload& p) noexcept -try { - const auto invalidationTime = _sourceCodeInvalidationTime.load(std::memory_order_relaxed); - - if (invalidationTime == INT64_MAX || invalidationTime > std::chrono::steady_clock::now().time_since_epoch().count()) +#if ATLAS_DEBUG_SHADER_HOT_RELOAD + try { - return; - } - - _sourceCodeInvalidationTime.store(INT64_MAX, std::memory_order_relaxed); + const auto invalidationTime = _sourceCodeInvalidationTime.load(std::memory_order_relaxed); - static const auto compile = [](const std::filesystem::path& path, const char* target) { - wil::com_ptr error; - wil::com_ptr blob; - const auto hr = D3DCompileFromFile( - /* pFileName */ path.c_str(), - /* pDefines */ nullptr, - /* pInclude */ D3D_COMPILE_STANDARD_FILE_INCLUDE, - /* pEntrypoint */ "main", - /* pTarget */ target, - /* Flags1 */ D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS, - /* Flags2 */ 0, - /* ppCode */ blob.addressof(), - /* ppErrorMsgs */ error.addressof()); - - if (error) + if (invalidationTime == INT64_MAX || invalidationTime > std::chrono::steady_clock::now().time_since_epoch().count()) { - std::thread t{ [error = std::move(error)]() noexcept { - MessageBoxA(nullptr, static_cast(error->GetBufferPointer()), "Compilation error", MB_ICONERROR | MB_OK); - } }; - t.detach(); + return; } - THROW_IF_FAILED(hr); - return blob; - }; + _sourceCodeInvalidationTime.store(INT64_MAX, std::memory_order_relaxed); - struct FileVS - { - std::wstring_view filename; - wil::com_ptr BackendD3D::*target; - }; - struct FilePS - { - std::wstring_view filename; - wil::com_ptr BackendD3D::*target; - }; + static constexpr auto flags = + D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS +#ifndef NDEBUG + | D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION +#endif + ; + + static const auto compile = [](const std::filesystem::path& path, const char* target) { + wil::com_ptr error; + wil::com_ptr blob; + const auto hr = D3DCompileFromFile( + /* pFileName */ path.c_str(), + /* pDefines */ nullptr, + /* pInclude */ D3D_COMPILE_STANDARD_FILE_INCLUDE, + /* pEntrypoint */ "main", + /* pTarget */ target, + /* Flags1 */ flags, + /* Flags2 */ 0, + /* ppCode */ blob.addressof(), + /* ppErrorMsgs */ error.addressof()); - static constexpr std::array filesVS{ - FileVS{ L"shader_vs.hlsl", &BackendD3D::_vertexShader }, - }; - static constexpr std::array filesPS{ - FilePS{ L"shader_ps.hlsl", &BackendD3D::_pixelShader }, - }; + if (error) + { + std::thread t{ [error = std::move(error)]() noexcept { + MessageBoxA(nullptr, static_cast(error->GetBufferPointer()), "Compilation error", MB_ICONERROR | MB_OK); + } }; + t.detach(); + } - std::array, filesVS.size()> compiledVS; - std::array, filesPS.size()> compiledPS; + THROW_IF_FAILED(hr); + return blob; + }; - // Compile our files before moving them into `this` below to ensure we're - // always in a consistent state where all shaders are seemingly valid. - for (size_t i = 0; i < filesVS.size(); ++i) - { - const auto blob = compile(_sourceDirectory / filesVS[i].filename, "vs_4_0"); - THROW_IF_FAILED(p.device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledVS[i].addressof())); - } - for (size_t i = 0; i < filesPS.size(); ++i) - { - const auto blob = compile(_sourceDirectory / filesPS[i].filename, "ps_4_0"); - THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledPS[i].addressof())); - } + struct FileVS + { + std::wstring_view filename; + wil::com_ptr BackendD3D::*target; + }; + struct FilePS + { + std::wstring_view filename; + wil::com_ptr BackendD3D::*target; + }; - for (size_t i = 0; i < filesVS.size(); ++i) - { - this->*filesVS[i].target = std::move(compiledVS[i]); - } - for (size_t i = 0; i < filesPS.size(); ++i) - { - this->*filesPS[i].target = std::move(compiledPS[i]); - } + static constexpr std::array filesVS{ + FileVS{ L"shader_vs.hlsl", &BackendD3D::_vertexShader }, + }; + static constexpr std::array filesPS{ + FilePS{ L"shader_ps.hlsl", &BackendD3D::_pixelShader }, + }; - _setupDeviceContextState(p); -} -CATCH_LOG() + std::array, filesVS.size()> compiledVS; + std::array, filesPS.size()> compiledPS; + + // Compile our files before moving them into `this` below to ensure we're + // always in a consistent state where all shaders are seemingly valid. + for (size_t i = 0; i < filesVS.size(); ++i) + { + const auto blob = compile(_sourceDirectory / filesVS[i].filename, "vs_4_0"); + THROW_IF_FAILED(p.device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledVS[i].addressof())); + } + for (size_t i = 0; i < filesPS.size(); ++i) + { + const auto blob = compile(_sourceDirectory / filesPS[i].filename, "ps_4_0"); + THROW_IF_FAILED(p.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, compiledPS[i].addressof())); + } + + for (size_t i = 0; i < filesVS.size(); ++i) + { + this->*filesVS[i].target = std::move(compiledVS[i]); + } + for (size_t i = 0; i < filesPS.size(); ++i) + { + this->*filesPS[i].target = std::move(compiledPS[i]); + } + + _setupDeviceContextState(p); + } + CATCH_LOG() #endif +} void BackendD3D::_d2dBeginDrawing() noexcept { @@ -980,6 +988,11 @@ void BackendD3D::_drawText(RenderingPayload& p) } } + const u8x2 renditionScale{ + static_cast(row->lineRendition != LineRendition::SingleWidth ? 2 : 1), + static_cast(row->lineRendition >= LineRendition::DoubleHeightTop ? 2 : 1), + }; + for (const auto& m : row->mappings) { auto x = m.glyphsFrom; @@ -1028,6 +1041,7 @@ void BackendD3D::_drawText(RenderingPayload& p) _appendQuad() = { .shadingType = static_cast(glyphEntry->shadingType), + .renditionScale = renditionScale, .position = { static_cast(l), static_cast(t) }, .size = glyphEntry->size, .texcoord = glyphEntry->texcoord, @@ -1394,6 +1408,8 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa _drawGlyphAtlasAllocate(p, rect); _d2dBeginDrawing(); + auto shadingType = ShadingType::TextGrayscale; + if (BuiltinGlyphs::IsSoftFontChar(glyphIndex)) { _drawSoftFontGlyph(p, rect, glyphIndex); @@ -1407,10 +1423,11 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa static_cast(rect.y + rect.h), }; BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); + shadingType = ShadingType::TextBuiltinGlyph; } const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); - glyphEntry->shadingType = ShadingType::TextGrayscale; + glyphEntry->shadingType = shadingType; glyphEntry->overlapSplit = 0; glyphEntry->offset.x = 0; glyphEntry->offset.y = -baseline; @@ -2023,9 +2040,9 @@ void BackendD3D::_drawSelection(const RenderingPayload& p) } } -#if ATLAS_DEBUG_SHOW_DIRTY void BackendD3D::_debugShowDirty(const RenderingPayload& p) { +#if ATLAS_DEBUG_SHOW_DIRTY _presentRects[_presentRectsPos] = p.dirtyRectInPx; _presentRectsPos = (_presentRectsPos + 1) % std::size(_presentRects); @@ -2048,12 +2065,12 @@ void BackendD3D::_debugShowDirty(const RenderingPayload& p) }; } } -} #endif +} -#if ATLAS_DEBUG_DUMP_RENDER_TARGET void BackendD3D::_debugDumpRenderTarget(const RenderingPayload& p) { +#if ATLAS_DEBUG_DUMP_RENDER_TARGET if (_dumpRenderTargetCounter == 0) { ExpandEnvironmentStringsW(ATLAS_DEBUG_DUMP_RENDER_TARGET_PATH, &_dumpRenderTargetBasePath[0], gsl::narrow_cast(std::size(_dumpRenderTargetBasePath))); @@ -2064,8 +2081,8 @@ void BackendD3D::_debugDumpRenderTarget(const RenderingPayload& p) swprintf_s(path, L"%s\\%u_%08zu.png", &_dumpRenderTargetBasePath[0], GetCurrentProcessId(), _dumpRenderTargetCounter); SaveTextureToPNG(p.deviceContext.get(), _swapChainManager.GetBuffer().get(), p.s->font->dpi, &path[0]); _dumpRenderTargetCounter++; -} #endif +} void BackendD3D::_executeCustomShader(RenderingPayload& p) { diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 3fddb161d49..1ae6b139b4c 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,8 +42,9 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; - alignas(sizeof(f32)) f32 thinLineWidth = 0; + alignas(sizeof(f32)) f32 doubleUnderlineWidth = 0; alignas(sizeof(f32)) f32 curlyLineHalfHeight = 0; + alignas(sizeof(f32)) f32 shadedGlyphDotSize = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -64,17 +65,18 @@ namespace Microsoft::Console::Render::Atlas // This block of values will be used for the TextDrawingFirst/Last range and need to stay together. // This is used to quickly check if an instance is related to a "text drawing primitive". - TextGrayscale = 1, - TextClearType = 2, - TextPassthrough = 3, - DottedLine = 4, - DashedLine = 5, - CurlyLine = 6, + TextGrayscale, + TextClearType, + TextBuiltinGlyph, + TextPassthrough, + DottedLine, + DashedLine, + CurlyLine, // All items starting here will be drawing as a solid RGBA color - SolidLine = 7, + SolidLine, - Cursor = 8, - Selection = 9, + Cursor, + Selection, TextDrawingFirst = TextGrayscale, TextDrawingLast = SolidLine, @@ -305,7 +307,7 @@ namespace Microsoft::Console::Render::Atlas size_t _colorizeGlyphAtlasCounter = 0; #endif -#ifndef NDEBUG +#if ATLAS_DEBUG_SHADER_HOT_RELOAD std::filesystem::path _sourceDirectory; wil::unique_folder_change_reader_nothrow _sourceCodeWatcher; std::atomic _sourceCodeInvalidationTime{ INT64_MAX }; diff --git a/src/renderer/atlas/BuiltinGlyphs.cpp b/src/renderer/atlas/BuiltinGlyphs.cpp index 541d5690324..c46bfc01fc0 100644 --- a/src/renderer/atlas/BuiltinGlyphs.cpp +++ b/src/renderer/atlas/BuiltinGlyphs.cpp @@ -1071,57 +1071,6 @@ static const Instruction* GetInstructions(char32_t codepoint) noexcept return nullptr; } -static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext* renderTarget, Shape shape) -{ - static constexpr u32 _ = 0; - static constexpr u32 w = 0xffffffff; - static constexpr u32 size = 4; - // clang-format off - static constexpr u32 shades[3][size * size] = { - { - w, _, _, _, - w, _, _, _, - _, _, w, _, - _, _, w, _, - }, - { - w, _, w, _, - _, w, _, w, - w, _, w, _, - _, w, _, w, - }, - { - _, w, w, w, - _, w, w, w, - w, w, _, w, - w, w, _, w, - }, - }; - // clang-format on - - static constexpr D2D1_SIZE_U bitmapSize{ size, size }; - static constexpr D2D1_BITMAP_PROPERTIES bitmapProps{ - .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, - .dpiX = 96, - .dpiY = 96, - }; - static constexpr D2D1_BITMAP_BRUSH_PROPERTIES bitmapBrushProps{ - .extendModeX = D2D1_EXTEND_MODE_WRAP, - .extendModeY = D2D1_EXTEND_MODE_WRAP, - .interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR - }; - - assert(shape < ARRAYSIZE(shades)); - - wil::com_ptr bitmap; - THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); - - wil::com_ptr bitmapBrush; - THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); - - return bitmapBrush; -} - void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) { renderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); @@ -1188,16 +1137,28 @@ void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* case Shape_Filled025: case Shape_Filled050: case Shape_Filled075: - { - const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; - const auto bitmapBrush = createShadedBitmapBrush(renderTarget, shape); - renderTarget->FillRectangle(&r, bitmapBrush.get()); - break; - } case Shape_Filled100: { + // This code works in tandem with SHADING_TYPE_TEXT_BUILTIN_GLYPH in our pixel shader. + // Unless someone removed it, it should have a lengthy comment visually explaining + // what each of the 3 RGB components do. The short version is: + // R: stretch the checkerboard pattern (Shape_Filled050) horizontally + // G: invert the pixels + // B: overrides the above and fills it + static constexpr D2D1_COLOR_F colors[] = { + { 1, 0, 0, 1 }, // Shape_Filled025 + { 0, 0, 0, 1 }, // Shape_Filled050 + { 1, 1, 0, 1 }, // Shape_Filled075 + { 1, 1, 1, 1 }, // Shape_Filled100 + }; + + const auto brushColor = brush->GetColor(); + brush->SetColor(&colors[shape]); + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; renderTarget->FillRectangle(&r, brush); + + brush->SetColor(&brushColor); break; } case Shape_LightLine: diff --git a/src/renderer/atlas/dwrite.hlsl b/src/renderer/atlas/dwrite.hlsl index ad7493275b7..955b9f57e5c 100644 --- a/src/renderer/atlas/dwrite.hlsl +++ b/src/renderer/atlas/dwrite.hlsl @@ -37,12 +37,12 @@ float3 DWrite_EnhanceContrast3(float3 alpha, float k) float DWrite_ApplyAlphaCorrection(float a, float f, float4 g) { - return a + a * (1 - a) * ((g.x * f + g.y) * a + (g.z * f + g.w)); + return a + a * (1.0f - a) * ((g.x * f + g.y) * a + (g.z * f + g.w)); } float3 DWrite_ApplyAlphaCorrection3(float3 a, float3 f, float4 g) { - return a + a * (1 - a) * ((g.x * f + g.y) * a + (g.z * f + g.w)); + return a + a * (1.0f - a) * ((g.x * f + g.y) * a + (g.z * f + g.w)); } // Call this function to get the same gamma corrected alpha blending effect diff --git a/src/renderer/atlas/shader_common.hlsl b/src/renderer/atlas/shader_common.hlsl index d712f081f28..51eb524b86b 100644 --- a/src/renderer/atlas/shader_common.hlsl +++ b/src/renderer/atlas/shader_common.hlsl @@ -1,15 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -// clang-format off +// Depends on the background texture #define SHADING_TYPE_TEXT_BACKGROUND 0 + +// Depends on the glyphAtlas texture #define SHADING_TYPE_TEXT_GRAYSCALE 1 #define SHADING_TYPE_TEXT_CLEARTYPE 2 -#define SHADING_TYPE_TEXT_PASSTHROUGH 3 -#define SHADING_TYPE_DOTTED_LINE 4 -#define SHADING_TYPE_DASHED_LINE 5 -#define SHADING_TYPE_CURLY_LINE 6 -// clang-format on +#define SHADING_TYPE_TEXT_BUILTIN_GLYPH 3 +#define SHADING_TYPE_TEXT_PASSTHROUGH 4 + +// Independent of any textures +#define SHADING_TYPE_DOTTED_LINE 5 +#define SHADING_TYPE_DASHED_LINE 6 +#define SHADING_TYPE_CURLY_LINE 7 +#define SHADING_TYPE_SOLID_LINE 8 +#define SHADING_TYPE_CURSOR 9 +#define SHADING_TYPE_SELECTION 10 struct VSData { @@ -27,7 +34,7 @@ struct PSData float4 position : SV_Position; float2 texcoord : texcoord; nointerpolation uint shadingType : shadingType; - nointerpolation uint2 renditionScale : renditionScale; + nointerpolation float2 renditionScale : renditionScale; nointerpolation float4 color : color; }; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index d3c0bfac6c3..d8d894be13e 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,8 +12,9 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; - float thinLineWidth; + float doubleUnderlineWidth; float curlyLineHalfHeight; + float shadedGlyphDotSize; } Texture2D background : register(t0); @@ -67,6 +68,87 @@ Output main(PSData data) : SV_Target color = weights * data.color; break; } + case SHADING_TYPE_TEXT_BUILTIN_GLYPH: + { + // The RGB components of builtin glyphs are used to control the generation of pixel patterns in this shader. + // Below you can see their intended effects where # indicates lit pixels. + // + // .r = stretch + // 0: #_#_#_#_ + // _#_#_#_# + // #_#_#_#_ + // _#_#_#_# + // + // 1: #___#___ + // __#___#_ + // #___#___ + // __#___#_ + // + // .g = invert + // 0: #_#_#_#_ + // _#_#_#_# + // #_#_#_#_ + // _#_#_#_# + // + // 1: _#_#_#_# + // #_#_#_#_ + // _#_#_#_# + // #_#_#_#_ + // + // .r = fill + // 0: #_#_#_#_ + // _#_#_#_# + // #_#_#_#_ + // _#_#_#_# + // + // 1: ######## + // ######## + // ######## + // ######## + // + float4 glyph = glyphAtlas[data.texcoord]; + float2 pos = floor(data.position.xy / (shadedGlyphDotSize * data.renditionScale)); + + // A series of on/off/on/off/on/off pixels can be generated with: + // step(frac(x * 0.5f), 0) + // The inner frac(x * 0.5f) will generate a series of + // 0, 0.5, 0, 0.5, 0, 0.5 + // and the step() will transform that to + // 1, 0, 1, 0, 1, 0 + // + // We can now turn that into a checkerboard pattern quite easily, + // if we imagine the fields of the checkerboard like this: + // +---+---+---+ + // | 0 | 1 | 2 | + // +---+---+---+ + // | 1 | 2 | 3 | + // +---+---+---+ + // | 2 | 3 | 4 | + // +---+---+---+ + // + // Because this means we just need to set + // x = pos.x + pos.y + // and so we end up with + // step(frac(dot(pos, 0.5f)), 0) + // + // Finally, we need to implement the "stretch" explained above, which can + // be easily achieved by simply replacing the factor 0.5 with 0.25 like so + // step(frac(x * 0.25f), 0) + // as this gets us + // 0, 0.25, 0.5, 0.75, 0, 0.25, 0.5, 0.75 + // = 1, 0, 0, 0, 1, 0, 0, 0 + // + // Of course we only want to apply that to the X axis, which means + // below we end up having 2 different multipliers for the dot(). + float stretched = step(frac(dot(pos, float2(glyph.r * -0.25f + 0.5f, 0.5f))), 0) * glyph.a; + // Thankfully the remaining 2 operations are a lot simpler. + float inverted = abs(glyph.g - stretched); + float filled = max(glyph.b, inverted); + + color = premultiplyColor(data.color) * filled; + weights = color.aaaa; + break; + } case SHADING_TYPE_TEXT_PASSTHROUGH: { color = glyphAtlas[data.texcoord]; @@ -89,7 +171,7 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_CURLY_LINE: { - const float strokeWidthHalf = thinLineWidth * data.renditionScale.y * 0.5f; + const float strokeWidthHalf = doubleUnderlineWidth * data.renditionScale.y * 0.5f; const float amp = (curlyLineHalfHeight - strokeWidthHalf) * data.renditionScale.y; const float freq = data.renditionScale.x / curlyLineHalfHeight * 1.57079632679489661923f; const float s = sin(data.position.x * freq) * amp; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 13ecdd25372..601085c35ff 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -117,11 +117,11 @@ namespace Microsoft::Console::Render struct LineMetrics { int gridlineWidth; - int thinLineWidth; int underlineCenter; int underlineWidth; int doubleUnderlinePosTop; int doubleUnderlinePosBottom; + int doubleUnderlineWidth; int strikethroughOffset; int strikethroughWidth; int curlyLineCenter; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 4dbb82d9c6d..29f8dfc43ad 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -632,7 +632,7 @@ try DWORD underlineWidth = _lineMetrics.underlineWidth; if (lines.any(GridLines::DoubleUnderline, GridLines::CurlyUnderline)) { - underlineWidth = _lineMetrics.thinLineWidth; + underlineWidth = _lineMetrics.doubleUnderlineWidth; } const LOGBRUSH brushProp{ .lbStyle = BS_SOLID, .lbColor = underlineColor }; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index f993c6f74e6..7563a68b7df 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -389,20 +389,20 @@ GdiEngine::~GdiEngine() // (Additional notes below.) // 1. - const auto thinLineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); + const auto doubleUnderlineWidth = std::max(1.0f, roundf(idealUnderlineWidth / 2.0f)); // 2. - auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - thinLineWidth; + auto doubleUnderlinePosBottom = underlineCenter + underlineWidth - doubleUnderlineWidth; // 3. Since we don't align the center of our two lines, but rather the top borders // we need to subtract half a line width from our center point. - auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - thinLineWidth) / 2.0f); + auto doubleUnderlinePosTop = roundf((baseline + doubleUnderlinePosBottom - doubleUnderlineWidth) / 2.0f); // 4. - doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + thinLineWidth); + doubleUnderlinePosTop = std::max(doubleUnderlinePosTop, baseline + doubleUnderlineWidth); // 5. The gap is only the distance _between_ the lines, but we need the distance from the // top border of the top and bottom lines, which includes an additional line width. const auto doubleUnderlineGap = std::max(1.0f, roundf(1.2f / 72.0f * _iCurrentDpi)); - doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth); + doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + doubleUnderlineWidth); // Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries. - doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - thinLineWidth); + doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, cellHeight - doubleUnderlineWidth); // The wave line is drawn using a cubic Bézier curve (PolyBezier), because that happens to be cheap with GDI. // We use a Bézier curve where, if the start (a) and end (c) points are at (0,0) and (1,0), the control points are @@ -431,13 +431,13 @@ GdiEngine::~GdiEngine() const auto curlyLineControlPointOffset = roundf(curlyLineIdealAmplitude * (1.0f / 0.140625f) * 0.5f); const auto curlyLinePeriod = curlyLineControlPointOffset * 2.0f; // We can reverse the above to get back the actual amplitude of our Bézier curve. The line - // will be drawn with a width of thinLineWidth in the center of the curve (= 0.5x padding). - const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * thinLineWidth; + // will be drawn with a width of doubleUnderlineWidth in the center of the curve (= 0.5x padding). + const auto curlyLineAmplitude = 0.140625f * curlyLinePeriod + 0.5f * doubleUnderlineWidth; // To make the wavy line with its double-underline amplitude look consistent with the double-underline we position it at its center. const auto curlyLineOffset = std::min(roundf(doubleUnderlineCenter), floorf(cellHeight - curlyLineAmplitude)); _lineMetrics.gridlineWidth = lroundf(idealGridlineWidth); - _lineMetrics.thinLineWidth = lroundf(thinLineWidth); + _lineMetrics.doubleUnderlineWidth = lroundf(doubleUnderlineWidth); _lineMetrics.underlineCenter = lroundf(underlineCenter); _lineMetrics.underlineWidth = lroundf(underlineWidth); _lineMetrics.doubleUnderlinePosTop = lroundf(doubleUnderlinePosTop); From 30dbd3b55416cceaff2b4db51e72a398fb05df9f Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Thu, 29 Feb 2024 11:00:04 -0600 Subject: [PATCH 37/50] Make the Settings Model tests into proper CI tests (#16773) This pull request removes the need for the SettingsModel tests to run in a UAP harness and puts them into the standard CI rotation. This required some changes to `Run-Tests.ps1` to ensure that the right `te.exe` is selected for each test harness. It's a bit annoying, but for things that depend on a `resources.pri`, that file must be in the same directory as the EXE that is hosting the test. Not the DLL, mind you, the EXE. In our case, that's `TE.ProcessHost.exe` The bulk of the change is honestly namespace tidying. Co-authored-by: Mike Griese Co-authored-by: Leonard Hecker --- OpenConsole.sln | 3 +- .../templates-v2/job-run-pgo-tests.yml | 1 + .../templates-v2/job-test-project.yml | 2 + build/scripts/Run-Tests.ps1 | 44 +++++++++++++++---- .../LocalTests_SettingsModel.def | 3 -- .../LocalTests_TerminalApp/SettingsTests.cpp | 2 +- .../TestHostApp/TestHostApp.vcxproj | 1 - .../TerminalSettingsModel/ActionMap.h | 8 ++-- src/cascadia/TerminalSettingsModel/Command.h | 6 +-- .../TerminalSettingsModel/GlobalAppSettings.h | 2 +- .../TerminalSettingsModel/Profile.cpp | 6 ++- src/cascadia/TerminalSettingsModel/Profile.h | 10 ++--- .../TerminalSettingsModel/TerminalSettings.h | 4 +- .../ColorSchemeTests.cpp | 17 +------ .../CommandTests.cpp | 17 +------ .../DeserializationTests.cpp | 17 +------ .../JsonTestClass.h | 0 .../KeyBindingsTests.cpp | 17 +------ .../NewTabMenuTests.cpp | 17 +------ .../ProfileTests.cpp | 17 +------ .../SerializationTests.cpp | 42 +++++++++++------- .../SettingsModel.UnitTests.vcxproj} | 19 ++++++-- .../TerminalSettingsTests.cpp | 19 ++------ .../TestUtils.h | 0 .../ThemeTests.cpp | 17 +------ .../UnitTests_SettingsModel.def | 1 + .../pch.cpp | 0 .../pch.h | 0 tools/OpenConsole.psm1 | 12 +++-- tools/runut.cmd | 14 +++++- tools/tests.xml | 2 +- 31 files changed, 142 insertions(+), 178 deletions(-) delete mode 100644 src/cascadia/LocalTests_SettingsModel/LocalTests_SettingsModel.def rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/ColorSchemeTests.cpp (96%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/CommandTests.cpp (95%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/DeserializationTests.cpp (96%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/JsonTestClass.h (100%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/KeyBindingsTests.cpp (95%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/NewTabMenuTests.cpp (75%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/ProfileTests.cpp (93%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/SerializationTests.cpp (95%) rename src/cascadia/{LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj => UnitTests_SettingsModel/SettingsModel.UnitTests.vcxproj} (87%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/TerminalSettingsTests.cpp (96%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/TestUtils.h (100%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/ThemeTests.cpp (89%) create mode 100644 src/cascadia/UnitTests_SettingsModel/UnitTests_SettingsModel.def rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/pch.cpp (100%) rename src/cascadia/{LocalTests_SettingsModel => UnitTests_SettingsModel}/pch.h (100%) diff --git a/OpenConsole.sln b/OpenConsole.sln index e699c77cece..1a05e24b750 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -270,7 +270,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestHostApp", "src\cascadia\LocalTests_TerminalApp\TestHostApp\TestHostApp.vcxproj", "{A021EDFF-45C8-4DC2-BEF7-36E1B3B8CFE8}" ProjectSection(ProjectDependencies) = postProject {CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076} - {CA5CAD1A-9B68-456A-B13E-C8218070DC42} = {CA5CAD1A-9B68-456A-B13E-C8218070DC42} {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} = {CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506} EndProjectSection EndProject @@ -350,7 +349,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.Settings {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_SettingsModel", "src\cascadia\LocalTests_SettingsModel\SettingsModel.LocalTests.vcxproj", "{CA5CAD1A-9B68-456A-B13E-C8218070DC42}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_SettingsModel", "src\cascadia\UnitTests_SettingsModel\SettingsModel.UnitTests.vcxproj", "{CA5CAD1A-9B68-456A-B13E-C8218070DC42}" ProjectSection(ProjectDependencies) = postProject {CA5CAD1A-082C-4476-9F33-94B339494076} = {CA5CAD1A-082C-4476-9F33-94B339494076} {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} diff --git a/build/pipelines/templates-v2/job-run-pgo-tests.yml b/build/pipelines/templates-v2/job-run-pgo-tests.yml index 817d97ff9ce..f4208bf9553 100644 --- a/build/pipelines/templates-v2/job-run-pgo-tests.yml +++ b/build/pipelines/templates-v2/job-run-pgo-tests.yml @@ -56,6 +56,7 @@ jobs: - task: PowerShell@2 displayName: 'Run PGO Tests' inputs: + pwsh: true targetType: filePath filePath: build\scripts\Run-Tests.ps1 arguments: >- diff --git a/build/pipelines/templates-v2/job-test-project.yml b/build/pipelines/templates-v2/job-test-project.yml index 1cd8e2bef67..5c53d76cfd0 100644 --- a/build/pipelines/templates-v2/job-test-project.yml +++ b/build/pipelines/templates-v2/job-test-project.yml @@ -44,6 +44,7 @@ jobs: - task: PowerShell@2 displayName: 'Run Unit Tests' inputs: + pwsh: true targetType: filePath filePath: build\scripts\Run-Tests.ps1 arguments: -MatchPattern '*unit.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)" @@ -52,6 +53,7 @@ jobs: - task: PowerShell@2 displayName: 'Run Feature Tests' inputs: + pwsh: true targetType: filePath filePath: build\scripts\Run-Tests.ps1 arguments: -MatchPattern '*feature.test*.dll' -Platform '$(OutputBuildPlatform)' -Configuration '$(BuildConfiguration)' -LogPath '${{ parameters.testLogPath }}' -Root "$(Terminal.BinDir)" diff --git a/build/scripts/Run-Tests.ps1 b/build/scripts/Run-Tests.ps1 index bd4cf28fc50..c9a37001c72 100644 --- a/build/scripts/Run-Tests.ps1 +++ b/build/scripts/Run-Tests.ps1 @@ -16,22 +16,48 @@ Param( # Find test DLLs based on the provided root, match pattern, and recursion $testDlls = Get-ChildItem -Path $Root -Recurse -Filter $MatchPattern -$args = @() +$teArgs = @() # Check if the LogPath parameter is provided and enable WTT logging if ($LogPath) { - $args += '/enablewttlogging' - $args += '/appendwttlogging' - $args += "/logFile:$LogPath" + $teArgs += '/enablewttlogging' + $teArgs += '/appendwttlogging' + $teArgs += "/logFile:$LogPath" Write-Host "WTT Logging Enabled" } -# Invoke the te.exe executable with arguments and test DLLs -& "$Root\te.exe" $args $testDlls.FullName $AdditionalTaefArguments +$rootTe = "$Root\te.exe" -# Check the exit code of the te.exe process and exit accordingly -if ($LASTEXITCODE -ne 0) { - Exit $LASTEXITCODE +# Some of our test fixtures depend on resources.pri in the same folder as the .exe hosting them. +# Unfortunately, that means that we need to run the te.exe *next to* each test DLL we discover. +# This code establishes a mapping from te.exe to test DLL (or DLLs) +$testDllTaefGroups = $testDlls | % { + $localTe = Get-Item (Join-Path (Split-Path $_ -Parent) "te.exe") -EA:Ignore + If ($null -eq $localTe) { + $finalTePath = $rootTe + } Else { + $finalTePath = $localTe.FullName + } + [PSCustomObject]@{ + TePath = $finalTePath; + TestDll = $_; + } +} + +# Invoke the te.exe executables with arguments and test DLLs +$anyFailed = $false +$testDllTaefGroups | Group-Object TePath | % { + $te = $_.Group[0].TePath + $dlls = $_.Group.TestDll + Write-Verbose "Running $te (for $($dlls.Name))" + & $te $teArgs $dlls.FullName $AdditionalTaefArguments + if ($LASTEXITCODE -ne 0) { + $anyFailed = $true + } +} + +if ($anyFailed) { + Exit 1 } Exit 0 diff --git a/src/cascadia/LocalTests_SettingsModel/LocalTests_SettingsModel.def b/src/cascadia/LocalTests_SettingsModel/LocalTests_SettingsModel.def deleted file mode 100644 index ba15818ddb1..00000000000 --- a/src/cascadia/LocalTests_SettingsModel/LocalTests_SettingsModel.def +++ /dev/null @@ -1,3 +0,0 @@ -EXPORTS -DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE -DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index a940cc915f3..8116e9e4574 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include "../TerminalApp/TerminalPage.h" -#include "../LocalTests_SettingsModel/TestUtils.h" +#include "../UnitTests_SettingsModel/TestUtils.h" using namespace Microsoft::Console; using namespace WEX::Logging; diff --git a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj index 9e2029c9bcc..8d431bffcc0 100644 --- a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj @@ -133,7 +133,6 @@ - diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index d59dc0677d8..937d79f66cb 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -20,7 +20,7 @@ Author(s): #include "Command.h" // fwdecl unittest classes -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { class KeyBindingsTests; class DeserializationTests; @@ -123,8 +123,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // than is necessary to be serialized. std::unordered_map _MaskingActions; - friend class SettingsModelLocalTests::KeyBindingsTests; - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::TerminalSettingsTests; + friend class SettingsModelUnitTests::KeyBindingsTests; + friend class SettingsModelUnitTests::DeserializationTests; + friend class SettingsModelUnitTests::TerminalSettingsTests; }; } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 4418716c815..a432fe9a370 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -25,7 +25,7 @@ Author(s): #include "SettingsTypes.h" // fwdecl unittest classes -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { class DeserializationTests; class CommandTests; @@ -87,8 +87,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, Windows::Foundation::Collections::IVectorView schemes); - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::CommandTests; + friend class SettingsModelUnitTests::DeserializationTests; + friend class SettingsModelUnitTests::CommandTests; }; } diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 21d73483c02..79f40342254 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -27,7 +27,7 @@ Author(s): #include "RemainingProfilesEntry.h" // fwdecl unittest classes -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { class DeserializationTests; class ColorSchemeTests; diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index f31cd4e85f7..0db012e7bc7 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -317,7 +317,11 @@ Json::Value Profile::ToJson() const JsonUtils::SetValueForKey(json, GuidKey, writeBasicSettings ? Guid() : _Guid); JsonUtils::SetValueForKey(json, HiddenKey, writeBasicSettings ? Hidden() : _Hidden); JsonUtils::SetValueForKey(json, SourceKey, writeBasicSettings ? Source() : _Source); - JsonUtils::SetValueForKey(json, IconKey, writeBasicSettings ? Icon() : _Icon); + + // Recall: Icon isn't actually a setting in the MTSM_PROFILE_SETTINGS. We + // defined it manually in Profile, so make sure we only serialize the Icon + // if the user actually changed it here. + JsonUtils::SetValueForKey(json, IconKey, (writeBasicSettings && HasIcon()) ? Icon() : _Icon); // PermissiveStringConverter is unnecessary for serialization JsonUtils::SetValueForKey(json, PaddingKey, _Padding); diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h index 1414dc0be1d..6fdd5c3ed57 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.h +++ b/src/cascadia/TerminalSettingsModel/Profile.h @@ -54,7 +54,7 @@ Author(s): #include "FontConfig.h" // fwdecl unittest classes -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { class DeserializationTests; class ProfileTests; @@ -147,10 +147,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring _evaluateIcon() const; - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::ProfileTests; - friend class SettingsModelLocalTests::ColorSchemeTests; - friend class SettingsModelLocalTests::KeyBindingsTests; + friend class SettingsModelUnitTests::DeserializationTests; + friend class SettingsModelUnitTests::ProfileTests; + friend class SettingsModelUnitTests::ColorSchemeTests; + friend class SettingsModelUnitTests::KeyBindingsTests; friend class TerminalAppUnitTests::DynamicProfileTests; friend class TerminalAppUnitTests::JsonTests; }; diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 0e7346a5960..22fba938723 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -25,7 +25,7 @@ using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; // fwdecl unittest classes -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { class TerminalSettingsTests; } @@ -182,7 +182,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const Windows::Foundation::Collections::IMapView& schemes, const winrt::Microsoft::Terminal::Settings::Model::Theme currentTheme); - friend class SettingsModelLocalTests::TerminalSettingsTests; + friend class SettingsModelUnitTests::TerminalSettingsTests; }; } diff --git a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp b/src/cascadia/UnitTests_SettingsModel/ColorSchemeTests.cpp similarity index 96% rename from src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp rename to src/cascadia/UnitTests_SettingsModel/ColorSchemeTests.cpp index a537c3dc698..b2198160671 100644 --- a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/ColorSchemeTests.cpp @@ -16,24 +16,11 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class ColorSchemeTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(ColorSchemeTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(ColorSchemeTests); TEST_METHOD(ParseSimpleColorScheme); TEST_METHOD(LayerColorSchemesOnArray); diff --git a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp similarity index 95% rename from src/cascadia/LocalTests_SettingsModel/CommandTests.cpp rename to src/cascadia/UnitTests_SettingsModel/CommandTests.cpp index cf8116033a1..f68b0224a47 100644 --- a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp @@ -15,24 +15,11 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class CommandTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(CommandTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(CommandTests); TEST_METHOD(ManyCommandsSameAction); TEST_METHOD(LayerCommand); diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp similarity index 96% rename from src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp rename to src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp index 50d649d7a5d..e546cf3305e 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp @@ -19,24 +19,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Control; using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class DeserializationTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(DeserializationTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(DeserializationTests); TEST_METHOD(ValidateProfilesExist); TEST_METHOD(ValidateDefaultProfileExists); diff --git a/src/cascadia/LocalTests_SettingsModel/JsonTestClass.h b/src/cascadia/UnitTests_SettingsModel/JsonTestClass.h similarity index 100% rename from src/cascadia/LocalTests_SettingsModel/JsonTestClass.h rename to src/cascadia/UnitTests_SettingsModel/JsonTestClass.h diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp similarity index 95% rename from src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp rename to src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index b2e77f5b4e6..8eed7276f1b 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -17,24 +17,11 @@ using namespace WEX::TestExecution; using namespace WEX::Common; using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class KeyBindingsTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(KeyBindingsTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(KeyBindingsTests); TEST_METHOD(KeyChords); TEST_METHOD(ManyKeysSameAction); diff --git a/src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp b/src/cascadia/UnitTests_SettingsModel/NewTabMenuTests.cpp similarity index 75% rename from src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp rename to src/cascadia/UnitTests_SettingsModel/NewTabMenuTests.cpp index f9e4625e995..d5bc0a75161 100644 --- a/src/cascadia/LocalTests_SettingsModel/NewTabMenuTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/NewTabMenuTests.cpp @@ -18,24 +18,11 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class NewTabMenuTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(NewTabMenuTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(NewTabMenuTests); TEST_METHOD(DefaultsToRemainingProfiles); TEST_METHOD(ParseEmptyFolder); diff --git a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp b/src/cascadia/UnitTests_SettingsModel/ProfileTests.cpp similarity index 93% rename from src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp rename to src/cascadia/UnitTests_SettingsModel/ProfileTests.cpp index a73b2bec3e4..047d3c89cfb 100644 --- a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/ProfileTests.cpp @@ -15,24 +15,11 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class ProfileTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(ProfileTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(ProfileTests); TEST_METHOD(ProfileGeneratesGuid); TEST_METHOD(LayerProfileProperties); diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp similarity index 95% rename from src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp rename to src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index b4778e8b814..c75da633ac2 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -16,24 +16,11 @@ using namespace WEX::Common; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Control; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class SerializationTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(SerializationTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(SerializationTests); TEST_METHOD(GlobalSettings); TEST_METHOD(Profile); @@ -212,9 +199,34 @@ namespace SettingsModelLocalTests "source": "local" })" }; + static constexpr std::string_view profileWithIcon{ R"( + { + "guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}", + "name": "profileWithIcon", + "hidden": false, + "icon": "foo.png" + })" }; + static constexpr std::string_view profileWithNullIcon{ R"( + { + "guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}", + "name": "profileWithNullIcon", + "hidden": false, + "icon": null + })" }; + static constexpr std::string_view profileWithNoIcon{ R"( + { + "guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}", + "name": "profileWithNoIcon", + "hidden": false, + "icon": "none" + })" }; + RoundtripTest(profileString); RoundtripTest(smallProfileString); RoundtripTest(weirdProfileString); + RoundtripTest(profileWithIcon); + RoundtripTest(profileWithNullIcon); + RoundtripTest(profileWithNoIcon); } void SerializationTests::ColorScheme() diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/UnitTests_SettingsModel/SettingsModel.UnitTests.vcxproj similarity index 87% rename from src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj rename to src/cascadia/UnitTests_SettingsModel/SettingsModel.UnitTests.vcxproj index dbd279005be..6210385b26a 100644 --- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj +++ b/src/cascadia/UnitTests_SettingsModel/SettingsModel.UnitTests.vcxproj @@ -14,9 +14,9 @@ {CA5CAD1A-9B68-456A-B13E-C8218070DC42} Win32Proj - SettingsModelLocalTests - LocalTests_SettingsModel - SettingsModel.LocalTests + SettingsModelUnitTests + UnitTests_SettingsModel + SettingsModel.Unit.Tests DynamicLibrary true @@ -64,7 +64,6 @@ _ConsoleGenerateAdditionalWinmdManifests step won't gather the winmd's --> - @@ -98,4 +97,16 @@ + + + + + + + + + diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp similarity index 96% rename from src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp rename to src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp index 4dbc7f88db8..f45fd1624f7 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp @@ -16,24 +16,11 @@ using namespace WEX::Common; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Control; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class TerminalSettingsTests { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(TerminalSettingsTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(TerminalSettingsTests); TEST_METHOD(TryCreateWinRTType); TEST_METHOD(TestTerminalArgsForBinding); @@ -153,7 +140,7 @@ namespace SettingsModelLocalTests g.Data4[7]); } - const auto tmpdir = std::filesystem::temp_directory_path(); + const auto tmpdir = std::filesystem::canonical(std::filesystem::temp_directory_path()); const auto dir1 = tmpdir / guid; const auto dir2 = tmpdir / (guid + L" two"); const auto file1 = dir1 / L"file 1.exe"; diff --git a/src/cascadia/LocalTests_SettingsModel/TestUtils.h b/src/cascadia/UnitTests_SettingsModel/TestUtils.h similarity index 100% rename from src/cascadia/LocalTests_SettingsModel/TestUtils.h rename to src/cascadia/UnitTests_SettingsModel/TestUtils.h diff --git a/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp b/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp similarity index 89% rename from src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp rename to src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp index c7bf11326af..512d9b20aa5 100644 --- a/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/ThemeTests.cpp @@ -17,24 +17,11 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; -namespace SettingsModelLocalTests +namespace SettingsModelUnitTests { - // TODO:microsoft/terminal#3838: - // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for - // an updated TAEF that will let us install framework packages when the test - // package is deployed. Until then, these tests won't deploy in CI. - class ThemeTests : public JsonTestClass { - // Use a custom AppxManifest to ensure that we can activate winrt types - // from our test. This property will tell taef to manually use this as - // the AppxManifest for this test class. - // This does not yet work for anything XAML-y. See TabTests.cpp for more - // details on that. - BEGIN_TEST_CLASS(ThemeTests) - TEST_CLASS_PROPERTY(L"RunAs", L"UAP") - TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") - END_TEST_CLASS() + TEST_CLASS(ThemeTests); TEST_METHOD(ParseSimpleTheme); TEST_METHOD(ParseEmptyTheme); diff --git a/src/cascadia/UnitTests_SettingsModel/UnitTests_SettingsModel.def b/src/cascadia/UnitTests_SettingsModel/UnitTests_SettingsModel.def new file mode 100644 index 00000000000..e80a637aa25 --- /dev/null +++ b/src/cascadia/UnitTests_SettingsModel/UnitTests_SettingsModel.def @@ -0,0 +1 @@ +EXPORTS diff --git a/src/cascadia/LocalTests_SettingsModel/pch.cpp b/src/cascadia/UnitTests_SettingsModel/pch.cpp similarity index 100% rename from src/cascadia/LocalTests_SettingsModel/pch.cpp rename to src/cascadia/UnitTests_SettingsModel/pch.cpp diff --git a/src/cascadia/LocalTests_SettingsModel/pch.h b/src/cascadia/UnitTests_SettingsModel/pch.h similarity index 100% rename from src/cascadia/LocalTests_SettingsModel/pch.h rename to src/cascadia/UnitTests_SettingsModel/pch.h diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index 3dd17957161..0f25a8e93a8 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -169,7 +169,7 @@ function Invoke-OpenConsoleTests() [switch]$FTOnly, [parameter(Mandatory=$false)] - [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'localSettingsModel', 'unitRemoting', 'unitControl')] + [ValidateSet('host', 'interactivityWin32', 'terminal', 'adapter', 'feature', 'uia', 'textbuffer', 'til', 'types', 'terminalCore', 'terminalApp', 'localTerminalApp', 'unitSettingsModel', 'unitRemoting', 'unitControl')] [string]$Test, [parameter(Mandatory=$false)] @@ -232,13 +232,19 @@ function Invoke-OpenConsoleTests() # run selected tests foreach ($t in $TestsToRun) { + $currentTaefExe = $TaefExePath + if ($t.isolatedTaef -eq "true") + { + $currentTaefExe = (Join-Path (Split-Path (Join-Path $BinDir $t.binary)) "te.exe") + } + if ($t.type -eq "unit") { - & $TaefExePath "$BinDir\$($t.binary)" $TaefArgs + & $currentTaefExe "$BinDir\$($t.binary)" $TaefArgs } elseif ($t.type -eq "ft") { - Invoke-TaefInNewWindow -OpenConsolePath $OpenConsolePath -TaefPath $TaefExePath -TestDll "$BinDir\$($t.binary)" -TaefArgs $TaefArgs + Invoke-TaefInNewWindow -OpenConsolePath $OpenConsolePath -TaefPath $currentTaefExe -TestDll "$BinDir\$($t.binary)" -TaefArgs $TaefArgs } else { diff --git a/tools/runut.cmd b/tools/runut.cmd index 02bcc47d192..62614d15d94 100644 --- a/tools/runut.cmd +++ b/tools/runut.cmd @@ -13,7 +13,7 @@ if "%PLATFORM%" == "Win32" ( set _TestHostAppPath=%OPENCON%\bin\%_LAST_BUILD_CONF%\TestHostApp ) -call %TAEF% ^ +%TAEF% ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\Conhost.Unit.Tests.dll ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\TextBuffer.Unit.Tests.dll ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_TerminalCore\Terminal.Core.Unit.Tests.dll ^ @@ -26,6 +26,16 @@ call %TAEF% ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Remoting\Remoting.Unit.Tests.dll ^ %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_Control\Control.Unit.Tests.dll ^ %_TestHostAppPath%\TerminalApp.LocalTests.dll ^ - %_TestHostAppPath%\SettingsModel.LocalTests.dll ^ %* +set _EarlyTestFail=%ERRORLEVEL% + +%OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_SettingsModel\te.exe ^ + %OPENCON%\bin\%PLATFORM%\%_LAST_BUILD_CONF%\UnitTests_SettingsModel\SettingsModel.Unit.Tests.dll ^ + %* + +if %_EarlyTestFail% NEQ 0 ( + exit /b %_EarlyTestFail% +) + +exit /b %ERRORLEVEL% diff --git a/tools/tests.xml b/tools/tests.xml index c6085b0b468..4301c09b2a3 100644 --- a/tools/tests.xml +++ b/tools/tests.xml @@ -5,7 +5,7 @@ - + From 99042d2f0cce6b6ede989aa630d391609c42729b Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Thu, 29 Feb 2024 10:39:26 -0800 Subject: [PATCH 38/50] Allow editing font axes in the Settings UI (#16104) ## Summary of the Pull Request Allow editing of font features and axes in the SUI to get the UI closer towards JSON parity The allowed font axes are obtained directly from the currently selected font, and their display names are presented to the user in the user's current locale (if it exists). Otherwise, we just display the axis tag to the user. ## References and Relevant Issues #10000 ## Validation Steps Performed - [x] Font Axes can be added/changed/removed from the Settings UI ![image](https://github.com/microsoft/terminal/assets/26824113/b1c3ed57-e329-4893-9f15-7b60154b5ea0) ![image](https://github.com/microsoft/terminal/assets/26824113/e1f1ea22-857d-4392-8a15-f81539fe9257) ## PR Checklist - [ ] Closes #xxx - [ ] Tests added/passed - [ ] Documentation updated - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx - [ ] Schema updated (if necessary) --- .github/actions/spelling/allow/allow.txt | 1 + .../TerminalSettingsEditor/Appearances.cpp | 316 ++++++++++++++++++ .../TerminalSettingsEditor/Appearances.h | 42 +++ .../TerminalSettingsEditor/Appearances.idl | 21 ++ .../TerminalSettingsEditor/Appearances.xaml | 63 ++++ .../ProfileViewModel.cpp | 4 + .../Resources/en-US/Resources.resw | 16 + .../SettingContainer.cpp | 11 + .../TerminalSettingsEditor/SettingContainer.h | 2 + .../SettingContainer.idl | 2 + .../TerminalSettingsModel/FontConfig.cpp | 12 + 11 files changed, 490 insertions(+) diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 27466018802..3ece2375e52 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -17,6 +17,7 @@ CMMI copyable Counterintuitively CtrlDToClose +CVS CUI cybersecurity dalet diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 8a3c9f943a2..848cb99349f 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "Appearances.h" #include "Appearances.g.cpp" +#include "AxisKeyValuePair.g.cpp" #include "EnumEntry.h" #include @@ -42,6 +43,154 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _hasPowerlineCharacters.value_or(false); } + Windows::Foundation::Collections::IMap Font::FontAxesTagsAndNames() + { + if (!_fontAxesTagsAndNames) + { + wil::com_ptr font; + THROW_IF_FAILED(_family->GetFont(0, font.put())); + wil::com_ptr fontFace; + THROW_IF_FAILED(font->CreateFontFace(fontFace.put())); + wil::com_ptr fontFace5; + if (fontFace5 = fontFace.try_query()) + { + wil::com_ptr fontResource; + THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.put())); + + const auto axesCount = fontFace5->GetFontAxisValueCount(); + if (axesCount > 0) + { + std::vector axesVector(axesCount); + fontFace5->GetFontAxisValues(axesVector.data(), axesCount); + + uint32_t localeIndex; + BOOL localeExists; + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + const auto localeToTry = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) ? localeName : L"en-US"; + + std::unordered_map fontAxesTagsAndNames; + for (uint32_t i = 0; i < axesCount; ++i) + { + wil::com_ptr names; + THROW_IF_FAILED(fontResource->GetAxisNames(i, names.put())); + + if (!SUCCEEDED(names->FindLocaleName(localeToTry, &localeIndex, &localeExists)) || !localeExists) + { + // default to the first locale in the list + localeIndex = 0; + } + + UINT32 length = 0; + if (SUCCEEDED(names->GetStringLength(localeIndex, &length))) + { + winrt::impl::hstring_builder builder{ length }; + if (SUCCEEDED(names->GetString(localeIndex, builder.data(), length + 1))) + { + fontAxesTagsAndNames.insert(std::pair(_axisTagToString(axesVector[i].axisTag), builder.to_hstring())); + continue; + } + } + // if there was no name found, it means the font does not actually support this axis + // don't insert anything into the vector in this case + } + _fontAxesTagsAndNames = winrt::single_threaded_map(std::move(fontAxesTagsAndNames)); + } + } + } + return _fontAxesTagsAndNames; + } + + winrt::hstring Font::_axisTagToString(DWRITE_FONT_AXIS_TAG tag) + { + std::wstring result; + for (int i = 0; i < 4; ++i) + { + result.push_back((tag >> (i * 8)) & 0xFF); + } + return winrt::hstring{ result }; + } + + AxisKeyValuePair::AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap) : + _AxisKey{ axisKey }, + _AxisValue{ axisValue }, + _baseMap{ baseMap }, + _tagToNameMap{ tagToNameMap } + { + if (_tagToNameMap.HasKey(_AxisKey)) + { + int32_t i{ 0 }; + // IMap guarantees that the iteration order is the same every time + // so this conversion of key to index is safe + for (const auto tagAndName : _tagToNameMap) + { + if (tagAndName.Key() == _AxisKey) + { + _AxisIndex = i; + break; + } + ++i; + } + } + } + + winrt::hstring AxisKeyValuePair::AxisKey() + { + return _AxisKey; + } + + float AxisKeyValuePair::AxisValue() + { + return _AxisValue; + } + + int32_t AxisKeyValuePair::AxisIndex() + { + return _AxisIndex; + } + + void AxisKeyValuePair::AxisValue(float axisValue) + { + if (axisValue != _AxisValue) + { + _baseMap.Remove(_AxisKey); + _AxisValue = axisValue; + _baseMap.Insert(_AxisKey, _AxisValue); + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AxisValue" }); + } + } + + void AxisKeyValuePair::AxisKey(winrt::hstring axisKey) + { + if (axisKey != _AxisKey) + { + _baseMap.Remove(_AxisKey); + _AxisKey = axisKey; + _baseMap.Insert(_AxisKey, _AxisValue); + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"AxisKey" }); + } + } + + void AxisKeyValuePair::AxisIndex(int32_t axisIndex) + { + if (axisIndex != _AxisIndex) + { + _AxisIndex = axisIndex; + + int32_t i{ 0 }; + // same as in the constructor, iterating through IMap + // gives us the same order every time + for (const auto tagAndName : _tagToNameMap) + { + if (i == _AxisIndex) + { + AxisKey(tagAndName.Key()); + break; + } + ++i; + } + } + } + AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) : _appearance{ appearance } { @@ -60,8 +209,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // box, prevent it from ever being changed again. _NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible"); } + else if (viewModelProperty == L"FontAxes") + { + // this is a weird one + // we manually make the observable vector based on the map in the settings model + // (this is due to xaml being unable to bind a list view to a map) + // so when the FontAxes change (say from the reset button), reinitialize the observable vector + InitializeFontAxesVector(); + } }); + InitializeFontAxesVector(); + // Cache the original BG image path. If the user clicks "Use desktop // wallpaper", then un-checks it, this is the string we'll restore to // them. @@ -205,6 +364,119 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation LightColorSchemeName(val.Name()); } + void AppearanceViewModel::AddNewAxisKeyValuePair() + { + if (!_appearance.SourceProfile().FontInfo().FontAxes()) + { + _appearance.SourceProfile().FontInfo().FontAxes(winrt::single_threaded_map()); + } + auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes(); + + // 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(); + for (const auto tagAndName : possibleAxesTagsAndNames) + { + if (!fontAxesMap.HasKey(tagAndName.Key())) + { + fontAxesMap.Insert(tagAndName.Key(), gsl::narrow(0)); + FontAxesVector().Append(_CreateAxisKeyValuePairHelper(tagAndName.Key(), gsl::narrow(0), fontAxesMap, possibleAxesTagsAndNames)); + break; + } + } + _NotifyChanges(L"CanFontAxesBeAdded"); + } + + void AppearanceViewModel::DeleteAxisKeyValuePair(winrt::hstring key) + { + for (uint32_t i = 0; i < _FontAxesVector.Size(); i++) + { + if (_FontAxesVector.GetAt(i).AxisKey() == key) + { + FontAxesVector().RemoveAt(i); + _appearance.SourceProfile().FontInfo().FontAxes().Remove(key); + if (_FontAxesVector.Size() == 0) + { + _appearance.SourceProfile().FontInfo().ClearFontAxes(); + } + break; + } + } + _NotifyChanges(L"CanFontAxesBeAdded"); + } + + void AppearanceViewModel::InitializeFontAxesVector() + { + if (!_FontAxesVector) + { + _FontAxesVector = winrt::single_threaded_observable_vector(); + } + + _FontAxesVector.Clear(); + if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) + { + const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontAxesTagsAndNames(); + for (const auto axis : fontAxesMap) + { + // only show the axes that the font supports + // any axes that the font doesn't support continue to be stored in the json, we just don't show them in the UI + if (fontAxesTagToNameMap.HasKey(axis.Key())) + { + _FontAxesVector.Append(_CreateAxisKeyValuePairHelper(axis.Key(), axis.Value(), fontAxesMap, fontAxesTagToNameMap)); + } + } + } + _NotifyChanges(L"AreFontAxesAvailable", L"CanFontAxesBeAdded"); + } + + // Method Description: + // - Determines whether the currently selected font has any variable font axes + bool AppearanceViewModel::AreFontAxesAvailable() + { + return ProfileViewModel::FindFontWithLocalizedName(FontFace()).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 fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) + { + for (const auto tagAndName : fontAxesTagToNameMap) + { + if (!fontAxesMap.HasKey(tagAndName.Key())) + { + // we found an axis that has not been set + return true; + } + } + // all possible axes have been set already + return false; + } + // the font supports font axes but the profile has none set + return true; + } + // the font does not support any font axes + return false; + } + + // Method Description: + // - Creates an AxisKeyValuePair and sets up an event handler for it + Editor::AxisKeyValuePair AppearanceViewModel::_CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap) + { + const auto axisKeyValuePair = winrt::make(axisKey, axisValue, baseMap, tagToNameMap); + // when either the key or the value changes, send an event for the preview control to catch + axisKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& /*sender*/, auto& /*e*/) { + if (auto appVM{ weakThis.get() }) + { + appVM->_NotifyChanges(L"AxisKeyValuePair"); + } + }); + return axisKeyValuePair; + } + DependencyProperty Appearances::_AppearanceProperty{ nullptr }; Appearances::Appearances() : @@ -271,6 +543,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto backgroundImgCheckboxTooltip{ ToolTipService::GetToolTip(UseDesktopImageCheckBox()) }; Automation::AutomationProperties::SetFullDescription(UseDesktopImageCheckBox(), unbox_value(backgroundImgCheckboxTooltip)); + _FontAxesNames = winrt::single_threaded_observable_vector(); + FontAxesNamesCVS().Source(_FontAxesNames); + INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content"); } @@ -330,6 +605,28 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { ShowProportionalFontWarning(false); } + + _FontAxesNames.Clear(); + const auto axesTagsAndNames = newFontFace.FontAxesTagsAndNames(); + for (const auto tagAndName : axesTagsAndNames) + { + _FontAxesNames.Append(tagAndName.Value()); + } + + // when the font face changes, we have to tell the view model to update the font axes vector + // since the new font may not have the same possible axes as the previous one + Appearance().InitializeFontAxesVector(); + if (!Appearance().AreFontAxesAvailable()) + { + // if the previous font had available font axes and the expander was expanded, + // at this point the expander would be set to disabled so manually collapse it + FontAxesContainer().SetExpanded(false); + FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text")); + } + else + { + FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")); + } } void Appearances::_ViewModelChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/) @@ -348,6 +645,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation biButton.IsChecked(biButton.Tag().as() == biAlignmentVal); } + FontAxesCVS().Source(Appearance().FontAxesVector()); + Appearance().AreFontAxesAvailable() ? FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")) : FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text")); + _ViewModelChangedRevoker = Appearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; if (settingName == L"CursorShape") @@ -470,6 +770,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void Appearances::DeleteAxisKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) + { + if (const auto& button{ sender.try_as() }) + { + if (const auto& tag{ button.Tag().try_as() }) + { + Appearance().DeleteAxisKeyValuePair(tag.value()); + } + } + } + + void Appearances::AddNewAxisKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) + { + Appearance().AddNewAxisKeyValuePair(); + } + bool Appearances::IsVintageCursor() const { return Appearance().CursorShape() == Core::CursorStyle::Vintage; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index f89d0fa2305..53a244ab0d9 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -17,6 +17,7 @@ Author(s): #pragma once #include "Font.g.h" +#include "AxisKeyValuePair.g.h" #include "Appearances.g.h" #include "AppearanceViewModel.g.h" #include "Utils.h" @@ -37,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation hstring ToString() { return _LocalizedName; } bool HasPowerlineCharacters(); + Windows::Foundation::Collections::IMap FontAxesTagsAndNames(); WINRT_PROPERTY(hstring, Name); WINRT_PROPERTY(hstring, LocalizedName); @@ -44,6 +46,31 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: winrt::com_ptr _family; std::optional _hasPowerlineCharacters; + winrt::hstring _axisTagToString(DWRITE_FONT_AXIS_TAG tag); + Windows::Foundation::Collections::IMap _fontAxesTagsAndNames; + }; + + struct AxisKeyValuePair : AxisKeyValuePairT, ViewModelHelper + { + AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); + + winrt::hstring AxisKey(); + void AxisKey(winrt::hstring axisKey); + + float AxisValue(); + void AxisValue(float axisValue); + + int32_t AxisIndex(); + void AxisIndex(int32_t axisIndex); + + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + + private: + winrt::hstring _AxisKey; + float _AxisValue; + int32_t _AxisIndex; + Windows::Foundation::Collections::IMap _baseMap{ nullptr }; + Windows::Foundation::Collections::IMap _tagToNameMap{ nullptr }; }; struct AppearanceViewModel : AppearanceViewModelT, ViewModelHelper @@ -69,6 +96,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::ColorSchemeViewModel CurrentColorScheme(); void CurrentColorScheme(const Editor::ColorSchemeViewModel& val); + void AddNewAxisKeyValuePair(); + void DeleteAxisKeyValuePair(winrt::hstring key); + void InitializeFontAxesVector(); + bool AreFontAxesAvailable(); + bool CanFontAxesBeAdded(); + WINRT_PROPERTY(bool, IsDefault, false); // These settings are not defined in AppearanceConfig, so we grab them @@ -79,6 +112,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_SETTING(_appearance, RetroTerminalEffect); @@ -93,10 +127,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle); OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, SchemesList, _propertyChangedHandlers, nullptr); + WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontAxesVector, _propertyChangedHandlers, nullptr); private: Model::AppearanceConfig _appearance; winrt::hstring _lastBgImagePath; + + Editor::AxisKeyValuePair _CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); }; struct Appearances : AppearancesT @@ -117,6 +154,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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); // manually bind FontWeight Windows::Foundation::IInspectable CurrentFontWeight() const; @@ -144,6 +183,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IMap _FontWeightMap; Editor::EnumEntry _CustomFontWeight{ nullptr }; + Windows::Foundation::Collections::IObservableVector _FontAxesNames; + Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker; static void _ViewModelChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e); void _UpdateWithNewViewModel(); @@ -153,4 +194,5 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation { BASIC_FACTORY(Appearances); + BASIC_FACTORY(AxisKeyValuePair); } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index ddab88a6beb..68bd887d217 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -13,6 +13,8 @@ import "ColorSchemesPageViewModel.idl"; OBSERVABLE_PROJECTED_SETTING(Type, Name); \ Object Name##OverrideSource { get; } +#define COMMA , + namespace Microsoft.Terminal.Settings.Editor { runtimeclass Font : Windows.Foundation.IStringable @@ -20,6 +22,17 @@ namespace Microsoft.Terminal.Settings.Editor String Name { get; }; String LocalizedName { get; }; Boolean HasPowerlineCharacters { get; }; + Windows.Foundation.Collections.IMap FontAxesTagsAndNames { get; }; + } + + // We have to make this because we cannot bind an IObservableMap to a ListView in XAML (in c++) + // So instead we make an IObservableVector of these AxisKeyValuePair objects + runtimeclass AxisKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + AxisKeyValuePair(String axisKey, Single axisValue, Windows.Foundation.Collections.IMap baseMap, Windows.Foundation.Collections.IMap tagToNameMap); + String AxisKey; + Single AxisValue; + Int32 AxisIndex; } runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged @@ -37,6 +50,14 @@ namespace Microsoft.Terminal.Settings.Editor ColorSchemeViewModel CurrentColorScheme; Windows.Foundation.Collections.IObservableVector SchemesList; + void AddNewAxisKeyValuePair(); + void DeleteAxisKeyValuePair(String key); + void InitializeFontAxesVector(); + Boolean AreFontAxesAvailable { get; }; + Boolean CanFontAxesBeAdded { get; }; + Windows.Foundation.Collections.IObservableVector FontAxesVector; + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap, FontAxes); + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, FontFace); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index f1f13d8fbd6..0b5a9c2abfa 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -37,6 +37,8 @@ Background="{x:Bind local:Converters.ColorToBrush(Color)}" CornerRadius="1" /> + @@ -286,6 +288,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Browse... Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window. + + Add new + Button label that adds a new font axis for the current font. + Background image opacity Name for a control to choose the opacity of the image presented on the background of the app. @@ -922,6 +926,18 @@ Sets the weight (lightness or heaviness of the strokes) for the given font. A description for what the "font weight" setting does. Presented near "Profile_FontWeight". + + Variable font axes + Header for a control to allow editing the font axes. + + + Add or remove font axes for the given font. + A description for what the "font axes" setting does. Presented near "Profile_FontAxes". + + + The selected font has no variable font axes. + A description provided when the font axes setting is disabled. Presented near "Profile_FontAxes". + General Header for a sub-page of profile settings focused on more general scenarios. diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainer.cpp b/src/cascadia/TerminalSettingsEditor/SettingContainer.cpp index 539165294c6..9bf3912e3aa 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainer.cpp +++ b/src/cascadia/TerminalSettingsEditor/SettingContainer.cpp @@ -185,6 +185,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + void SettingContainer::SetExpanded(bool expanded) + { + if (const auto& child{ GetTemplateChild(L"Expander") }) + { + if (const auto& expander{ child.try_as() }) + { + expander.IsExpanded(expanded); + } + } + } + // Method Description: // - Updates the override system visibility and text // Arguments: diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainer.h b/src/cascadia/TerminalSettingsEditor/SettingContainer.h index 5b48b438f51..be543681b3c 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainer.h +++ b/src/cascadia/TerminalSettingsEditor/SettingContainer.h @@ -29,6 +29,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnApplyTemplate(); + void SetExpanded(bool expanded); + DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Header); DEPENDENCY_PROPERTY(hstring, HelpText); DEPENDENCY_PROPERTY(hstring, CurrentValue); diff --git a/src/cascadia/TerminalSettingsEditor/SettingContainer.idl b/src/cascadia/TerminalSettingsEditor/SettingContainer.idl index 1148e0eb6d9..8b5fd0eba95 100644 --- a/src/cascadia/TerminalSettingsEditor/SettingContainer.idl +++ b/src/cascadia/TerminalSettingsEditor/SettingContainer.idl @@ -7,6 +7,8 @@ namespace Microsoft.Terminal.Settings.Editor { SettingContainer(); + void SetExpanded(Boolean expanded); + IInspectable Header; static Windows.UI.Xaml.DependencyProperty HeaderProperty { get; }; diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp index fafdf0976bc..b01ec960658 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp @@ -30,6 +30,18 @@ winrt::com_ptr FontConfig::CopyFontInfo(const FontConfig* source, wi MTSM_FONT_SETTINGS(FONT_SETTINGS_COPY) #undef FONT_SETTINGS_COPY + // We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;` + // since that'll just create a reference; we have to manually copy the values. + if (source->_FontAxes) + { + std::map fontAxes; + for (const auto keyValuePair : source->_FontAxes.value()) + { + fontAxes.insert(std::pair(keyValuePair.Key(), keyValuePair.Value())); + } + fontInfo->_FontAxes = winrt::single_threaded_map(std::move(fontAxes)); + } + return fontInfo; } From 6e451a2d4b6574aed846385b8223894472366805 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Thu, 29 Feb 2024 12:08:52 -0800 Subject: [PATCH 39/50] Allow editing font features in the Settings UI (#16678) ## Summary of the Pull Request **Targets #16104** Same as #16104, but for font features ## References and Relevant Issues #10000 ## Validation Steps Performed Font features are detected correctly and can be set in the settings UI ![image](https://github.com/microsoft/terminal/assets/26824113/054c30fa-c584-4b71-872d-d956526c373b) ![image](https://github.com/microsoft/terminal/assets/26824113/484a20eb-abe9-478c-99cf-f63939ab4c5b) ## PR Checklist - [ ] Closes #xxx - [ ] Tests added/passed - [ ] Documentation updated - If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx - [ ] Schema updated (if necessary) --- .github/actions/spelling/allow/apis.txt | 8 + .../TerminalSettingsEditor/Appearances.cpp | 344 +++++++++++++++++- .../TerminalSettingsEditor/Appearances.h | 47 ++- .../TerminalSettingsEditor/Appearances.idl | 17 + .../TerminalSettingsEditor/Appearances.xaml | 63 ++++ .../Resources/en-US/Resources.resw | 120 ++++++ .../TerminalSettingsModel/FontConfig.cpp | 10 + 7 files changed, 605 insertions(+), 4 deletions(-) diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 5ae7e5753df..839fcbe76b2 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -1,3 +1,5 @@ +aalt +abvm ACCEPTFILES ACCESSDENIED acl @@ -36,6 +38,7 @@ delayimp DERR dlldata DNE +dnom DONTADDTORECENT DWMSBT DWMWA @@ -49,6 +52,7 @@ EXPCMDFLAGS EXPCMDSTATE filetime FILTERSPEC +fina FORCEFILESYSTEM FORCEMINIMIZE frac @@ -120,6 +124,7 @@ LSHIFT LTGRAY MAINWINDOW MAXIMIZEBOX +medi memchr memicmp MENUCOMMAND @@ -150,6 +155,7 @@ NOTIFYBYPOS NOTIFYICON NOTIFYICONDATA ntprivapi +numr oaidl ocidl ODR @@ -175,9 +181,11 @@ REGCLS RETURNCMD rfind RLO +rnrn ROOTOWNER roundf RSHIFT +rvrn SACL schandle SEH diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 848cb99349f..0ba4f344e23 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -5,6 +5,7 @@ #include "Appearances.h" #include "Appearances.g.cpp" #include "AxisKeyValuePair.g.cpp" +#include "FeatureKeyValuePair.g.cpp" #include "EnumEntry.h" #include @@ -19,6 +20,20 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Microsoft::Terminal::Settings::Model; +static constexpr std::array DefaultFeatures{ + L"rlig", + L"locl", + L"ccmp", + L"calt", + L"liga", + L"clig", + L"rnrn", + L"kern", + L"mark", + L"mkmk", + L"dist" +}; + namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { bool Font::HasPowerlineCharacters() @@ -86,7 +101,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::impl::hstring_builder builder{ length }; if (SUCCEEDED(names->GetString(localeIndex, builder.data(), length + 1))) { - fontAxesTagsAndNames.insert(std::pair(_axisTagToString(axesVector[i].axisTag), builder.to_hstring())); + fontAxesTagsAndNames.insert(std::pair(_tagToString(axesVector[i].axisTag), builder.to_hstring())); continue; } } @@ -100,7 +115,47 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _fontAxesTagsAndNames; } - winrt::hstring Font::_axisTagToString(DWRITE_FONT_AXIS_TAG tag) + IMap Font::FontFeaturesTagsAndNames() + { + if (!_fontFeaturesTagsAndNames) + { + wil::com_ptr font; + THROW_IF_FAILED(_family->GetFont(0, font.put())); + wil::com_ptr fontFace; + THROW_IF_FAILED(font->CreateFontFace(fontFace.put())); + + wil::com_ptr factory; + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof()))); + wil::com_ptr textAnalyzer; + factory->CreateTextAnalyzer(textAnalyzer.addressof()); + wil::com_ptr textAnalyzer2 = textAnalyzer.query(); + + DWRITE_SCRIPT_ANALYSIS scriptAnalysis{}; + UINT32 tagCount; + // we have to call GetTypographicFeatures twice, first to get the actual count then to get the features + std::ignore = textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", 0, &tagCount, nullptr); + std::vector tags{ tagCount }; + textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", tagCount, &tagCount, tags.data()); + + std::unordered_map fontFeaturesTagsAndNames; + for (auto tag : tags) + { + const auto tagString = _tagToString(tag); + hstring formattedResourceString{ fmt::format(L"Profile_FontFeature_{}", tagString) }; + hstring localizedName{ tagString }; + // we have resource strings for common font features, see if one for this feature exists + if (HasLibraryResourceWithName(formattedResourceString)) + { + localizedName = GetLibraryResourceString(formattedResourceString); + } + fontFeaturesTagsAndNames.insert(std::pair(tagString, localizedName)); + } + _fontFeaturesTagsAndNames = winrt::single_threaded_map(std::move(fontFeaturesTagsAndNames)); + } + return _fontFeaturesTagsAndNames; + } + + winrt::hstring Font::_tagToString(DWRITE_FONT_AXIS_TAG tag) { std::wstring result; for (int i = 0; i < 4; ++i) @@ -110,6 +165,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return winrt::hstring{ result }; } + hstring Font::_tagToString(DWRITE_FONT_FEATURE_TAG tag) + { + std::wstring result; + for (int i = 0; i < 4; ++i) + { + result.push_back((tag >> (i * 8)) & 0xFF); + } + return hstring{ result }; + } + AxisKeyValuePair::AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap) : _AxisKey{ axisKey }, _AxisValue{ axisValue }, @@ -191,6 +256,87 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } + FeatureKeyValuePair::FeatureKeyValuePair(hstring featureKey, uint32_t featureValue, const IMap& baseMap, const IMap& tagToNameMap) : + _FeatureKey{ featureKey }, + _FeatureValue{ featureValue }, + _baseMap{ baseMap }, + _tagToNameMap{ tagToNameMap } + { + if (_tagToNameMap.HasKey(_FeatureKey)) + { + int32_t i{ 0 }; + // this loop assumes that every time we iterate through the map + // we get the same ordering + for (const auto tagAndName : _tagToNameMap) + { + if (tagAndName.Key() == _FeatureKey) + { + _FeatureIndex = i; + break; + } + ++i; + } + } + } + + hstring FeatureKeyValuePair::FeatureKey() + { + return _FeatureKey; + } + + uint32_t FeatureKeyValuePair::FeatureValue() + { + return _FeatureValue; + } + + int32_t FeatureKeyValuePair::FeatureIndex() + { + return _FeatureIndex; + } + + void FeatureKeyValuePair::FeatureValue(uint32_t featureValue) + { + if (featureValue != _FeatureValue) + { + _baseMap.Remove(_FeatureKey); + _FeatureValue = featureValue; + _baseMap.Insert(_FeatureKey, _FeatureValue); + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"FeatureValue" }); + } + } + + void FeatureKeyValuePair::FeatureKey(hstring featureKey) + { + if (featureKey != _FeatureKey) + { + _baseMap.Remove(_FeatureKey); + _FeatureKey = featureKey; + _baseMap.Insert(_FeatureKey, _FeatureValue); + _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"FeatureKey" }); + } + } + + void FeatureKeyValuePair::FeatureIndex(int32_t featureIndex) + { + if (featureIndex != _FeatureIndex) + { + _FeatureIndex = featureIndex; + + int32_t i{ 0 }; + // same as in the constructor, this assumes that iterating through the map + // gives us the same order every time + for (const auto tagAndName : _tagToNameMap) + { + if (i == _FeatureIndex) + { + FeatureKey(tagAndName.Key()); + break; + } + ++i; + } + } + } + AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) : _appearance{ appearance } { @@ -217,9 +363,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // so when the FontAxes change (say from the reset button), reinitialize the observable vector InitializeFontAxesVector(); } + else if (viewModelProperty == L"FontFeatures") + { + // same as the FontAxes one + InitializeFontFeaturesVector(); + } }); InitializeFontAxesVector(); + InitializeFontFeaturesVector(); // Cache the original BG image path. If the user clicks "Use desktop // wallpaper", then un-checks it, this is the string we'll restore to @@ -477,6 +629,150 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return axisKeyValuePair; } + void AppearanceViewModel::AddNewFeatureKeyValuePair() + { + const auto fontInfo = _appearance.SourceProfile().FontInfo(); + auto fontFeaturesMap = fontInfo.FontFeatures(); + if (!fontFeaturesMap) + { + fontFeaturesMap = winrt::single_threaded_map(); + fontInfo.FontFeatures(fontFeaturesMap); + } + + // 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(); + for (const auto tagAndName : possibleFeaturesTagsAndNames) + { + const auto featureKey = tagAndName.Key(); + if (!fontFeaturesMap.HasKey(featureKey)) + { + const auto featureDefaultValue = _IsDefaultFeature(featureKey) ? 1 : 0; + fontFeaturesMap.Insert(featureKey, featureDefaultValue); + FontFeaturesVector().Append(_CreateFeatureKeyValuePairHelper(featureKey, featureDefaultValue, fontFeaturesMap, possibleFeaturesTagsAndNames)); + break; + } + } + _NotifyChanges(L"CanFontFeaturesBeAdded"); + } + + void AppearanceViewModel::DeleteFeatureKeyValuePair(hstring key) + { + for (uint32_t i = 0; i < _FontFeaturesVector.Size(); i++) + { + if (_FontFeaturesVector.GetAt(i).FeatureKey() == key) + { + FontFeaturesVector().RemoveAt(i); + _appearance.SourceProfile().FontInfo().FontFeatures().Remove(key); + if (_FontFeaturesVector.Size() == 0) + { + _appearance.SourceProfile().FontInfo().ClearFontFeatures(); + } + break; + } + } + _NotifyChanges(L"CanFontAxesBeAdded"); + } + + void AppearanceViewModel::InitializeFontFeaturesVector() + { + if (!_FontFeaturesVector) + { + _FontFeaturesVector = single_threaded_observable_vector(); + } + + _FontFeaturesVector.Clear(); + if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) + { + const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(FontFace()).FontFeaturesTagsAndNames(); + for (const auto feature : fontFeaturesMap) + { + const auto featureKey = feature.Key(); + // only show the features that the font supports + // any features that the font doesn't support continue to be stored in the json, we just don't show them in the UI + if (fontFeaturesTagToNameMap.HasKey(featureKey)) + { + _FontFeaturesVector.Append(_CreateFeatureKeyValuePairHelper(featureKey, feature.Value(), fontFeaturesMap, fontFeaturesTagToNameMap)); + } + } + } + _NotifyChanges(L"AreFontFeaturesAvailable", L"CanFontFeaturesBeAdded"); + } + + // Method Description: + // - Determines whether the currently selected font has any font features + bool AppearanceViewModel::AreFontFeaturesAvailable() + { + return ProfileViewModel::FindFontWithLocalizedName(FontFace()).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 fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) + { + for (const auto tagAndName : fontFeaturesTagToNameMap) + { + if (!fontFeaturesMap.HasKey(tagAndName.Key())) + { + // we found a feature that has not been set + return true; + } + } + // all possible features have been set already + return false; + } + // the font supports font features but the profile has none set + return true; + } + // the font does not support any font features + return false; + } + + // Method Description: + // - Creates a FeatureKeyValuePair and sets up an event handler for it + Editor::FeatureKeyValuePair AppearanceViewModel::_CreateFeatureKeyValuePairHelper(hstring featureKey, uint32_t featureValue, const IMap& baseMap, const IMap& tagToNameMap) + { + const auto featureKeyValuePair = winrt::make(featureKey, featureValue, baseMap, tagToNameMap); + // when either the key or the value changes, send an event for the preview control to catch + featureKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& sender, const PropertyChangedEventArgs& args) { + if (auto appVM{ weakThis.get() }) + { + appVM->_NotifyChanges(L"FeatureKeyValuePair"); + const auto settingName{ args.PropertyName() }; + if (settingName == L"FeatureKey") + { + const auto senderPair = sender.as(); + const auto senderKey = senderPair->FeatureKey(); + if (appVM->_IsDefaultFeature(senderKey)) + { + senderPair->FeatureValue(1); + } + else + { + senderPair->FeatureValue(0); + } + } + } + }); + return featureKeyValuePair; + } + + bool AppearanceViewModel::_IsDefaultFeature(winrt::hstring featureKey) + { + for (const auto defaultFeature : DefaultFeatures) + { + if (defaultFeature == featureKey) + { + return true; + } + } + return false; + } + DependencyProperty Appearances::_AppearanceProperty{ nullptr }; Appearances::Appearances() : @@ -546,6 +842,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _FontAxesNames = winrt::single_threaded_observable_vector(); FontAxesNamesCVS().Source(_FontAxesNames); + _FontFeaturesNames = winrt::single_threaded_observable_vector(); + FontFeaturesNamesCVS().Source(_FontFeaturesNames); + INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content"); } @@ -613,7 +912,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _FontAxesNames.Append(tagAndName.Value()); } - // when the font face changes, we have to tell the view model to update the font axes vector + _FontFeaturesNames.Clear(); + const auto featuresTagsAndNames = newFontFace.FontFeaturesTagsAndNames(); + for (const auto tagAndName : featuresTagsAndNames) + { + _FontFeaturesNames.Append(tagAndName.Value()); + } + + // when the font face changes, we have to tell the view model to update the font axes/features vectors // since the new font may not have the same possible axes as the previous one Appearance().InitializeFontAxesVector(); if (!Appearance().AreFontAxesAvailable()) @@ -627,6 +933,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")); } + + Appearance().InitializeFontFeaturesVector(); + if (!Appearance().AreFontFeaturesAvailable()) + { + // if the previous font had available font features and the expander was expanded, + // at this point the expander would be set to disabled so manually collapse it + FontFeaturesContainer().SetExpanded(false); + FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesUnavailable/Text")); + } + else + { + FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesAvailable/Text")); + } } void Appearances::_ViewModelChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*args*/) @@ -648,6 +967,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation FontAxesCVS().Source(Appearance().FontAxesVector()); Appearance().AreFontAxesAvailable() ? FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")) : FontAxesContainer().HelpText(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")); + _ViewModelChangedRevoker = Appearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; if (settingName == L"CursorShape") @@ -786,6 +1108,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Appearance().AddNewAxisKeyValuePair(); } + void Appearances::DeleteFeatureKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) + { + if (const auto& button{ sender.try_as() }) + { + if (const auto& tag{ button.Tag().try_as() }) + { + Appearance().DeleteFeatureKeyValuePair(tag.value()); + } + } + } + + void Appearances::AddNewFeatureKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) + { + Appearance().AddNewFeatureKeyValuePair(); + } + bool Appearances::IsVintageCursor() const { return Appearance().CursorShape() == Core::CursorStyle::Vintage; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index 53a244ab0d9..d72bb418693 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -18,11 +18,13 @@ Author(s): #include "Font.g.h" #include "AxisKeyValuePair.g.h" +#include "FeatureKeyValuePair.g.h" #include "Appearances.g.h" #include "AppearanceViewModel.g.h" #include "Utils.h" #include "ViewModelHelpers.h" #include "SettingContainer.h" +#include namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { @@ -39,6 +41,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation hstring ToString() { return _LocalizedName; } bool HasPowerlineCharacters(); Windows::Foundation::Collections::IMap FontAxesTagsAndNames(); + Windows::Foundation::Collections::IMap FontFeaturesTagsAndNames(); WINRT_PROPERTY(hstring, Name); WINRT_PROPERTY(hstring, LocalizedName); @@ -46,8 +49,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation private: winrt::com_ptr _family; std::optional _hasPowerlineCharacters; - winrt::hstring _axisTagToString(DWRITE_FONT_AXIS_TAG tag); + + winrt::hstring _tagToString(DWRITE_FONT_AXIS_TAG tag); + winrt::hstring _tagToString(DWRITE_FONT_FEATURE_TAG tag); + Windows::Foundation::Collections::IMap _fontAxesTagsAndNames; + Windows::Foundation::Collections::IMap _fontFeaturesTagsAndNames; }; struct AxisKeyValuePair : AxisKeyValuePairT, ViewModelHelper @@ -73,6 +80,29 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IMap _tagToNameMap{ nullptr }; }; + struct FeatureKeyValuePair : FeatureKeyValuePairT, ViewModelHelper + { + FeatureKeyValuePair(winrt::hstring featureKey, uint32_t featureValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); + + winrt::hstring FeatureKey(); + void FeatureKey(winrt::hstring featureKey); + + uint32_t FeatureValue(); + void FeatureValue(uint32_t featureValue); + + int32_t FeatureIndex(); + void FeatureIndex(int32_t featureIndex); + + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + + private: + winrt::hstring _FeatureKey; + uint32_t _FeatureValue; + int32_t _FeatureIndex; + Windows::Foundation::Collections::IMap _baseMap{ nullptr }; + Windows::Foundation::Collections::IMap _tagToNameMap{ nullptr }; + }; + struct AppearanceViewModel : AppearanceViewModelT, ViewModelHelper { public: @@ -102,6 +132,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool AreFontAxesAvailable(); bool CanFontAxesBeAdded(); + void AddNewFeatureKeyValuePair(); + void DeleteFeatureKeyValuePair(winrt::hstring key); + void InitializeFontFeaturesVector(); + bool AreFontFeaturesAvailable(); + bool CanFontFeaturesBeAdded(); + WINRT_PROPERTY(bool, IsDefault, false); // These settings are not defined in AppearanceConfig, so we grab them @@ -113,6 +149,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontAxes); + OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFeatures); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_SETTING(_appearance, RetroTerminalEffect); @@ -128,12 +165,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, SchemesList, _propertyChangedHandlers, nullptr); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontAxesVector, _propertyChangedHandlers, nullptr); + WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontFeaturesVector, _propertyChangedHandlers, nullptr); private: Model::AppearanceConfig _appearance; winrt::hstring _lastBgImagePath; Editor::AxisKeyValuePair _CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); + Editor::FeatureKeyValuePair _CreateFeatureKeyValuePairHelper(winrt::hstring axisKey, uint32_t axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); + + bool _IsDefaultFeature(winrt::hstring featureTag); }; struct Appearances : AppearancesT @@ -156,6 +197,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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); + void AddNewFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); // manually bind FontWeight Windows::Foundation::IInspectable CurrentFontWeight() const; @@ -184,6 +227,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::EnumEntry _CustomFontWeight{ nullptr }; Windows::Foundation::Collections::IObservableVector _FontAxesNames; + Windows::Foundation::Collections::IObservableVector _FontFeaturesNames; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker; static void _ViewModelChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e); @@ -195,4 +239,5 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation { BASIC_FACTORY(Appearances); BASIC_FACTORY(AxisKeyValuePair); + BASIC_FACTORY(FeatureKeyValuePair); } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 68bd887d217..17ef8a1867a 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -23,6 +23,7 @@ namespace Microsoft.Terminal.Settings.Editor String LocalizedName { get; }; Boolean HasPowerlineCharacters { get; }; Windows.Foundation.Collections.IMap FontAxesTagsAndNames { get; }; + Windows.Foundation.Collections.IMap FontFeaturesTagsAndNames { get; }; } // We have to make this because we cannot bind an IObservableMap to a ListView in XAML (in c++) @@ -35,6 +36,14 @@ namespace Microsoft.Terminal.Settings.Editor Int32 AxisIndex; } + runtimeclass FeatureKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + FeatureKeyValuePair(String featureKey, UInt32 featureValue, Windows.Foundation.Collections.IMap baseMap, Windows.Foundation.Collections.IMap tagToNameMap); + String FeatureKey; + UInt32 FeatureValue; + Int32 FeatureIndex; + } + runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { Boolean IsDefault; @@ -58,6 +67,14 @@ namespace Microsoft.Terminal.Settings.Editor Windows.Foundation.Collections.IObservableVector FontAxesVector; OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap, FontAxes); + void AddNewFeatureKeyValuePair(); + void DeleteFeatureKeyValuePair(String key); + void InitializeFontFeaturesVector(); + Boolean AreFontFeaturesAvailable { get; }; + Boolean CanFontFeaturesBeAdded { get; }; + Windows.Foundation.Collections.IObservableVector FontFeaturesVector; + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, FontFace); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 0b5a9c2abfa..47c01df1fc9 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -39,6 +39,8 @@ + @@ -349,6 +351,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Add new Button label that adds a new font axis for the current font. + + Add new + Button label that adds a new font feature for the current font. + Background image opacity Name for a control to choose the opacity of the image presented on the background of the app. @@ -938,6 +942,18 @@ The selected font has no variable font axes. A description provided when the font axes setting is disabled. Presented near "Profile_FontAxes". + + Font features + Header for a control to allow editing the font features. + + + Add or remove font features for the given font. + A description for what the "font features" setting does. Presented near "Profile_FontFeatures". + + + The selected font has no font features. + A description provided when the font features setting is disabled. Presented near "Profile_FontFeatures". + General Header for a sub-page of profile settings focused on more general scenarios. @@ -1254,6 +1270,110 @@ Thin This is the formal name for a font weight. + + Required ligatures + This is the formal name for a font feature. + + + Localized forms + This is the formal name for a font feature. + + + Composition/decomposition + This is the formal name for a font feature. + + + Contextual alternates + This is the formal name for a font feature. + + + Standard ligatures + This is the formal name for a font feature. + + + Contextual ligatures + This is the formal name for a font feature. + + + Required variation alternates + This is the formal name for a font feature. + + + Kerning + This is the formal name for a font feature. + + + Mark positioning + This is the formal name for a font feature. + + + Mark to mark positioning + This is the formal name for a font feature. + + + Distance + This is the formal name for a font feature. + + + Access all alternates + This is the formal name for a font feature. + + + Case sensitive forms + This is the formal name for a font feature. + + + Denominator + This is the formal name for a font feature. + + + Terminal forms + This is the formal name for a font feature. + + + Fractions + This is the formal name for a font feature. + + + Initial forms + This is the formal name for a font feature. + + + Medial forms + This is the formal name for a font feature. + + + Numerator + This is the formal name for a font feature. + + + Ordinals + This is the formal name for a font feature. + + + Required contextual alternates + This is the formal name for a font feature. + + + Scientific inferiors + This is the formal name for a font feature. + + + Subscript + This is the formal name for a font feature. + + + Superscript + This is the formal name for a font feature. + + + Slashed zero + This is the formal name for a font feature. + + + Above-base mark positioning + This is the formal name for a font feature. + Launch size Header for a group of settings that control the size of the app. Presented near "Globals_InitialCols" and "Globals_InitialRows". diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp index b01ec960658..fd9a181ae30 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp @@ -42,6 +42,16 @@ winrt::com_ptr FontConfig::CopyFontInfo(const FontConfig* source, wi fontInfo->_FontAxes = winrt::single_threaded_map(std::move(fontAxes)); } + if (source->_FontFeatures) + { + std::map fontFeatures; + for (const auto keyValuePair : source->_FontFeatures.value()) + { + fontFeatures.insert(std::pair(keyValuePair.Key(), keyValuePair.Value())); + } + fontInfo->_FontFeatures = winrt::single_threaded_map(std::move(fontFeatures)); + } + return fontInfo; } From b780d8ab7e1cdbc7066cd67782126df214872a98 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 29 Feb 2024 21:25:29 +0100 Subject: [PATCH 40/50] A minor cleanup of ProfileViewModel (#16788) This is just a minor cleanup I did as a drive-by while working on customized font fallback. The benefit of this change is that it's a tiny bit less expensive, but also that it's a lot easier to read. The split into "get index" and "get string by index" helps us to more easily handle both, missing locales and locale fallback. The code that ties everything together then ends up being just 7 lines. --- .../TerminalSettingsEditor/Appearances.h | 6 +- .../ProfileViewModel.cpp | 86 ++++++++----------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index d72bb418693..651523b7a69 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -31,9 +31,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation struct Font : FontT { public: - Font(std::wstring name, std::wstring localizedName, IDWriteFontFamily* family) : - _Name{ name }, - _LocalizedName{ localizedName } + Font(winrt::hstring name, winrt::hstring localizedName, IDWriteFontFamily* family) : + _Name{ std::move(name) }, + _LocalizedName{ std::move(localizedName) } { _family.copy_from(family); } diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index db991b9230d..5682ae5cc18 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -22,7 +22,7 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family); + static Editor::Font fontObjectForDWriteFont(IDWriteFontFamily* family, const wchar_t* locale); Windows::Foundation::Collections::IObservableVector ProfileViewModel::_MonospaceFontList{ nullptr }; Windows::Foundation::Collections::IObservableVector ProfileViewModel::_FontList{ nullptr }; @@ -119,6 +119,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation wil::com_ptr fontCollection; THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), TRUE)); + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + if (!GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH)) + { + memcpy(&localeName[0], L"en-US", 12); + } + for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i) { try @@ -128,7 +134,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put())); // construct a font entry for tracking - if (const auto fontEntry{ _FontObjectForDWriteFont(fontFamily.get()) }) + if (const auto fontEntry{ fontObjectForDWriteFont(fontFamily.get(), &localeName[0]) }) { // check if the font is monospaced try @@ -199,65 +205,43 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return fallbackFont; } - static Editor::Font _FontObjectForDWriteFont(IDWriteFontFamily* family) + static winrt::hstring getLocalizedStringByIndex(IDWriteLocalizedStrings* strings, UINT32 index) { - // used for the font's name as an identifier (i.e. text block's font family property) - std::wstring nameID; - UINT32 nameIDIndex; + UINT32 length = 0; + THROW_IF_FAILED(strings->GetStringLength(index, &length)); - // used for the font's localized name - std::wstring localizedName; - UINT32 localizedNameIndex; + winrt::impl::hstring_builder builder{ length }; + THROW_IF_FAILED(strings->GetString(index, builder.data(), length + 1)); - // get the font's localized names - winrt::com_ptr localizedFamilyNames; - THROW_IF_FAILED(family->GetFamilyNames(localizedFamilyNames.put())); + return builder.to_hstring(); + } - // use our current locale to find the localized name - auto exists{ FALSE }; - HRESULT hr; - wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; - if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)) - { - hr = localizedFamilyNames->FindLocaleName(localeName, &localizedNameIndex, &exists); - } - if (SUCCEEDED(hr) && !exists) - { - // if we can't find the font for our locale, fallback to the en-us one - // Source: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename - hr = localizedFamilyNames->FindLocaleName(L"en-us", &localizedNameIndex, &exists); - } - if (!exists) + static UINT32 getLocalizedStringIndex(IDWriteLocalizedStrings* strings, const wchar_t* locale, UINT32 fallback) + { + UINT32 index; + BOOL exists; + if (FAILED(strings->FindLocaleName(locale, &index, &exists)) || !exists) { - // failed to find the correct locale, using the first one - localizedNameIndex = 0; + index = fallback; } + return index; + } - // get the localized name - UINT32 nameLength; - THROW_IF_FAILED(localizedFamilyNames->GetStringLength(localizedNameIndex, &nameLength)); + static Editor::Font fontObjectForDWriteFont(IDWriteFontFamily* family, const wchar_t* locale) + { + wil::com_ptr familyNames; + THROW_IF_FAILED(family->GetFamilyNames(familyNames.addressof())); - localizedName.resize(nameLength); - THROW_IF_FAILED(localizedFamilyNames->GetString(localizedNameIndex, localizedName.data(), nameLength + 1)); + // If en-us is missing we fall back to whatever is at index 0. + const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-us", 0); + // If our locale is missing we fall back to en-us. + const auto li = getLocalizedStringIndex(familyNames.get(), locale, ci); - // now get the nameID - hr = localizedFamilyNames->FindLocaleName(L"en-us", &nameIDIndex, &exists); - if (FAILED(hr) || !exists) - { - // failed to find it, using the first one - nameIDIndex = 0; - } - - // get the nameID - THROW_IF_FAILED(localizedFamilyNames->GetStringLength(nameIDIndex, &nameLength)); - nameID.resize(nameLength); - THROW_IF_FAILED(localizedFamilyNames->GetString(nameIDIndex, nameID.data(), nameLength + 1)); + auto canonical = getLocalizedStringByIndex(familyNames.get(), ci); + // If the canonical/localized indices are the same, there's no need to get the other string. + auto localized = ci == li ? canonical : getLocalizedStringByIndex(familyNames.get(), li); - if (!nameID.empty() && !localizedName.empty()) - { - return make(nameID, localizedName, family); - } - return nullptr; + return make(std::move(canonical), std::move(localized), family); } winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept From 043d5cd4840d6cab1e48bcaf2e6ac1038f6f0333 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 29 Feb 2024 22:59:15 +0100 Subject: [PATCH 41/50] Fix bugs in CharToColumnMapper (#16787) Aside from overall simplifying `CharToColumnMapper` this fixes 2 bugs: * The backward search loop may have iterated 1 column too far, because it didn't stop at `*current <= *target`, but rather at `*(current - 1) <= *target`. This issue was only apparent when surrogate pairs were being used in a row. * When the target offset is that of a trailing surrogate pair the forward search loop may have iterated 1 column too far. It's somewhat unlikely for this to happen since this code is only used through ICU, but you never know. This is a continuation of PR #16775. --- src/buffer/out/Row.cpp | 56 +++++++---------- src/buffer/out/Row.hpp | 2 +- .../TextBuffer.Unit.Tests.vcxproj | 3 +- .../out/ut_textbuffer/UTextAdapterTests.cpp | 63 +++++++++++++++++++ src/buffer/out/ut_textbuffer/sources | 1 + src/inc/til/point.h | 12 ++++ 6 files changed, 101 insertions(+), 36 deletions(-) create mode 100644 src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 7729cdcdbd4..4722dc0dfc4 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -92,47 +92,35 @@ CharToColumnMapper::CharToColumnMapper(const wchar_t* chars, const uint16_t* cha // If given a position (`offset`) inside the ROW's text, this function will return the corresponding column. // This function in particular returns the glyph's first column. -til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t offset) noexcept +til::CoordType CharToColumnMapper::GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept { - offset = clamp(offset, 0, _lastCharOffset); + targetOffset = clamp(targetOffset, 0, _lastCharOffset); + + // This code needs to fulfill two conditions on top of the obvious (a forward/backward search): + // A: We never want to stop on a column that is marked with CharOffsetsTrailer (= "GetLeadingColumn"). + // B: With these parameters we always want to stop at currentOffset=4: + // _charOffsets={4, 6} + // currentOffset=4 *OR* 6 + // targetOffset=5 + // This is because we're being asked for a "LeadingColumn", while the caller gave us the offset of a + // trailing surrogate pair or similar. Returning the column of the leading half is the correct choice. auto col = _currentColumn; - const auto currentOffset = _charOffsets[col] & CharOffsetsMask; + auto currentOffset = _charOffsets[col]; - // Goal: Move the _currentColumn cursor to a cell which contains the given target offset. - // Depending on where the target offset is we have to either search forward or backward. - if (offset < currentOffset) + // A plain forward-search until we find our targetOffset. + // This loop may iterate too far and thus violate our example in condition B, however... + while (targetOffset > (currentOffset & CharOffsetsMask)) { - // Backward search. - // Goal: Find the first preceding column where the offset is <= the target offset. This results in the first - // cell that contains our target offset, even if that offset is in the middle of a long grapheme. - // - // We abuse the fact that the trailing half of wide glyphs is marked with CharOffsetsTrailer to our advantage. - // Since they're >0x8000, the `offset < _charOffsets[col]` check will always be true and ensure we iterate over them. - // - // Since _charOffsets cannot contain negative values and because offset has been - // clamped to be positive we naturally exit when reaching the first column. - for (; offset < _charOffsets[col - 1]; --col) - { - } + currentOffset = _charOffsets[++col]; } - else if (offset > currentOffset) + // This backward-search is not just a counter-part to the above, but simultaneously also handles conditions A and B. + // It abuses the fact that columns marked with CharOffsetsTrailer are >0x8000 and targetOffset is always <0x8000. + // This means we skip all "trailer" columns when iterating backwards, and only stop on a non-trailer (= condition A). + // Condition B is fixed simply because we iterate backwards after the forward-search (in that exact order). + while (targetOffset < currentOffset) { - // Forward search. - // Goal: Find the first subsequent column where the offset is > the target offset. - // We stop 1 column before that however so that the next loop works correctly. - // It's the inverse of the loop above. - // - // Since offset has been clamped to be at most 1 less than the maximum - // _charOffsets value the loop naturally exits before hitting the end. - for (; offset >= (_charOffsets[col + 1] & CharOffsetsMask); ++col) - { - } - // Now that we found the cell that definitely includes this char offset, - // we have to iterate back to the cell's starting column. - for (; WI_IsFlagSet(_charOffsets[col], CharOffsetsTrailer); --col) - { - } + currentOffset = _charOffsets[--col]; } _currentColumn = col; diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index af8088c3ccd..197343df6d8 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -71,7 +71,7 @@ struct CharToColumnMapper { CharToColumnMapper(const wchar_t* chars, const uint16_t* charOffsets, ptrdiff_t lastCharOffset, til::CoordType currentColumn) noexcept; - til::CoordType GetLeadingColumnAt(ptrdiff_t offset) noexcept; + til::CoordType GetLeadingColumnAt(ptrdiff_t targetOffset) noexcept; til::CoordType GetTrailingColumnAt(ptrdiff_t offset) noexcept; til::CoordType GetLeadingColumnAt(const wchar_t* str) noexcept; til::CoordType GetTrailingColumnAt(const wchar_t* str) noexcept; diff --git a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj index ad3b48c7cb5..c52d012baec 100644 --- a/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj +++ b/src/buffer/out/ut_textbuffer/TextBuffer.Unit.Tests.vcxproj @@ -14,6 +14,7 @@ + Create @@ -41,4 +42,4 @@ - \ No newline at end of file + diff --git a/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp b/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp new file mode 100644 index 00000000000..ee879f4f677 --- /dev/null +++ b/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "WexTestClass.h" +#include "../textBuffer.hpp" +#include "../../renderer/inc/DummyRenderer.hpp" + +template<> +class WEX::TestExecution::VerifyOutputTraits> +{ +public: + static WEX::Common::NoThrowString ToString(const std::vector& vec) + { + WEX::Common::NoThrowString str; + str.Append(L"{ "); + for (size_t i = 0; i < vec.size(); ++i) + { + const auto& s = vec[i]; + if (i != 0) + { + str.Append(L", "); + } + str.AppendFormat(L"{(%d, %d), (%d, %d)}", s.start.x, s.start.y, s.end.x, s.end.y); + } + str.Append(L" }"); + return str; + } +}; + +class UTextAdapterTests +{ + TEST_CLASS(UTextAdapterTests); + + TEST_METHOD(Unicode) + { + DummyRenderer renderer; + TextBuffer buffer{ til::size{ 24, 1 }, TextAttribute{}, 0, false, renderer }; + + RowWriteState state{ + .text = L"abc 𝒶𝒷𝒸 abc ネコちゃん", + }; + buffer.Write(0, TextAttribute{}, state); + VERIFY_IS_TRUE(state.text.empty()); + + static constexpr auto s = [](til::CoordType beg, til::CoordType end) -> til::point_span { + return { { beg, 0 }, { end, 0 } }; + }; + + auto expected = std::vector{ s(0, 2), s(8, 10) }; + auto actual = buffer.SearchText(L"abc", false); + VERIFY_ARE_EQUAL(expected, actual); + + expected = std::vector{ s(5, 5) }; + actual = buffer.SearchText(L"𝒷", false); + VERIFY_ARE_EQUAL(expected, actual); + + expected = std::vector{ s(12, 15) }; + actual = buffer.SearchText(L"ネコ", false); + VERIFY_ARE_EQUAL(expected, actual); + } +}; diff --git a/src/buffer/out/ut_textbuffer/sources b/src/buffer/out/ut_textbuffer/sources index 570467fce63..842246aaee6 100644 --- a/src/buffer/out/ut_textbuffer/sources +++ b/src/buffer/out/ut_textbuffer/sources @@ -17,6 +17,7 @@ SOURCES = \ ReflowTests.cpp \ TextColorTests.cpp \ TextAttributeTests.cpp \ + UTextAdapterTests.cpp \ DefaultResource.rc \ TARGETLIBS = \ diff --git a/src/inc/til/point.h b/src/inc/til/point.h index 332b6fd8e0b..4da8d5c6ddb 100644 --- a/src/inc/til/point.h +++ b/src/inc/til/point.h @@ -276,6 +276,18 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { til::point start; til::point end; + + constexpr bool operator==(const point_span& rhs) const noexcept + { + // `__builtin_memcmp` isn't an official standard, but it's the + // only way at the time of writing to get a constexpr `memcmp`. + return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0; + } + + constexpr bool operator!=(const point_span& rhs) const noexcept + { + return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0; + } }; } From ec5d246b35fbb0feb1bc2cbbff1b4b11362a2824 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Fri, 1 Mar 2024 03:43:57 -0800 Subject: [PATCH 42/50] Fix being unable to delete a changed Font Axis or Font Feature (#16790) Make sure the delete button's `Tag` updates when the selected axis/feature changes, so that the correct key value gets propagated when the delete button is clicked. Refs #16678 #16104 ## Validation Steps Performed 1. Add a new feature/axis 2. Change the key 3. Click the delete button 4. Delete button works --- src/cascadia/TerminalSettingsEditor/Appearances.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 47c01df1fc9..5f73b299c11 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -329,7 +329,7 @@ HorizontalAlignment="Right" Click="DeleteAxisKeyValuePair_Click" Style="{StaticResource DeleteButtonStyle}" - Tag="{x:Bind AxisKey}"> + Tag="{x:Bind AxisKey, Mode=OneWay}"> @@ -390,7 +390,7 @@ HorizontalAlignment="Right" Click="DeleteFeatureKeyValuePair_Click" Style="{StaticResource DeleteButtonStyle}" - Tag="{x:Bind FeatureKey}"> + Tag="{x:Bind FeatureKey, Mode=OneWay}"> From ad51b22f440a57ff8381499c84e19b9f1d174cab Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Fri, 1 Mar 2024 11:56:49 -0600 Subject: [PATCH 43/50] Introduce Microsoft.Terminal.UI and consolidate UI helpers (#15107) This pull request introduces the module Microsoft.Terminal.UI.dll, and moves into it the following things: - Any `IDirectKeyListener` - All XAML converter helpers from around the project - ... including `IconPathConverter` from TerminalSettingsModel - ... but not `EmptyStringVisibilityConverter`, which has died It also adds a XAML Markup Extension named `mtu:ResourceString`, which will allow us to refer to string resources directly from XAML. It will allow us to remove all of the places in the code where we manually set resources on XAML controls. --------- Co-authored-by: Leonard Hecker --- .github/actions/spelling/allow/microsoft.txt | 1 + OpenConsole.sln | 26 +++++++ src/cascadia/TerminalApp/CommandPalette.idl | 3 +- src/cascadia/TerminalApp/CommandPalette.xaml | 24 ++---- .../EmptyStringVisibilityConverter.cpp | 38 --------- .../EmptyStringVisibilityConverter.h | 26 ------- .../EmptyStringVisibilityConverter.idl | 19 ----- .../TerminalApp/IDirectKeyListener.idl | 14 ---- src/cascadia/TerminalApp/PaletteItem.cpp | 2 +- src/cascadia/TerminalApp/SettingsTab.cpp | 2 +- .../TerminalApp/SuggestionsControl.idl | 3 +- .../TerminalApp/SuggestionsControl.xaml | 6 +- .../TerminalApp/TerminalAppLib.vcxproj | 11 +-- .../TerminalAppLib.vcxproj.filters | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.idl | 3 +- src/cascadia/TerminalApp/TerminalTab.cpp | 4 +- src/cascadia/TerminalApp/TerminalWindow.cpp | 4 +- src/cascadia/TerminalApp/TerminalWindow.idl | 3 +- .../TerminalApp/dll/TerminalApp.vcxproj | 3 + src/cascadia/TerminalApp/pch.h | 1 + .../TerminalControl/ControlInteractivity.cpp | 1 - .../TerminalControl/IDirectKeyListener.idl | 14 ---- .../ScrollBarVisualStateManager.cpp | 2 + .../ScrollBarVisualStateManager.h | 4 +- src/cascadia/TerminalControl/TermControl.idl | 8 +- .../TermControlAutomationPeer.h | 3 +- .../TerminalControlLib.vcxproj | 5 +- .../dll/TerminalControl.vcxproj | 5 ++ src/cascadia/TerminalControl/pch.h | 2 + .../TerminalSettingsEditor/Actions.xaml | 9 +-- .../TerminalSettingsEditor/AddProfile.xaml | 4 +- .../TerminalSettingsEditor/Appearances.cpp | 4 +- .../TerminalSettingsEditor/Appearances.xaml | 21 ++--- .../TerminalSettingsEditor/ColorSchemes.xaml | 7 +- .../TerminalSettingsEditor/Converters.h | 30 ------- .../EditColorScheme.xaml | 9 ++- .../GlobalAppearance.xaml | 3 +- .../TerminalSettingsEditor/Launch.xaml | 14 ++-- .../TerminalSettingsEditor/MainPage.cpp | 4 +- .../TerminalSettingsEditor/MainPage.xaml | 1 + ...Microsoft.Terminal.Settings.Editor.vcxproj | 12 +-- ...t.Terminal.Settings.Editor.vcxproj.filters | 5 +- .../TerminalSettingsEditor/ProfileViewModel.h | 2 +- .../Profiles_Appearance.xaml | 9 ++- .../TerminalSettingsEditor/Profiles_Base.xaml | 13 ++-- src/cascadia/TerminalSettingsEditor/pch.h | 1 + .../TerminalSettingsModel/IconPathConverter.h | 29 ------- .../IconPathConverter.idl | 22 ------ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 9 +-- ...Terminal.Settings.ModelLib.vcxproj.filters | 3 - .../Microsoft.Terminal.Settings.Model.vcxproj | 3 +- src/cascadia/TerminalSettingsModel/pch.h | 3 - .../Converters.cpp | 22 +++--- src/cascadia/UIHelpers/Converters.h | 33 ++++++++ .../Converters.idl | 22 ++++-- src/cascadia/UIHelpers/IDirectKeyListener.idl | 10 +++ .../IconPathConverter.cpp | 68 +++++----------- src/cascadia/UIHelpers/IconPathConverter.h | 20 +++++ src/cascadia/UIHelpers/IconPathConverter.idl | 13 ++++ .../UIHelpers/Microsoft.Terminal.UI.def | 3 + src/cascadia/UIHelpers/ResourceString.cpp | 17 ++++ src/cascadia/UIHelpers/ResourceString.h | 43 ++++++++++ src/cascadia/UIHelpers/ResourceString.idl | 9 +++ src/cascadia/UIHelpers/UIHelpers.vcxproj | 78 +++++++++++++++++++ .../UIHelpers/UIHelpers.vcxproj.filters | 38 +++++++++ src/cascadia/UIHelpers/init.cpp | 21 +++++ src/cascadia/UIHelpers/pch.cpp | 4 + src/cascadia/UIHelpers/pch.h | 46 +++++++++++ src/cascadia/UnitTests_SettingsModel/pch.h | 1 - .../WindowsTerminal/WindowsTerminal.vcxproj | 1 + src/cascadia/WindowsTerminal/pch.h | 1 + 72 files changed, 524 insertions(+), 388 deletions(-) delete mode 100644 src/cascadia/TerminalApp/EmptyStringVisibilityConverter.cpp delete mode 100644 src/cascadia/TerminalApp/EmptyStringVisibilityConverter.h delete mode 100644 src/cascadia/TerminalApp/EmptyStringVisibilityConverter.idl delete mode 100644 src/cascadia/TerminalApp/IDirectKeyListener.idl delete mode 100644 src/cascadia/TerminalControl/IDirectKeyListener.idl delete mode 100644 src/cascadia/TerminalSettingsEditor/Converters.h delete mode 100644 src/cascadia/TerminalSettingsModel/IconPathConverter.h delete mode 100644 src/cascadia/TerminalSettingsModel/IconPathConverter.idl rename src/cascadia/{TerminalSettingsEditor => UIHelpers}/Converters.cpp (79%) create mode 100644 src/cascadia/UIHelpers/Converters.h rename src/cascadia/{TerminalSettingsEditor => UIHelpers}/Converters.idl (85%) create mode 100644 src/cascadia/UIHelpers/IDirectKeyListener.idl rename src/cascadia/{TerminalSettingsModel => UIHelpers}/IconPathConverter.cpp (78%) create mode 100644 src/cascadia/UIHelpers/IconPathConverter.h create mode 100644 src/cascadia/UIHelpers/IconPathConverter.idl create mode 100644 src/cascadia/UIHelpers/Microsoft.Terminal.UI.def create mode 100644 src/cascadia/UIHelpers/ResourceString.cpp create mode 100644 src/cascadia/UIHelpers/ResourceString.h create mode 100644 src/cascadia/UIHelpers/ResourceString.idl create mode 100644 src/cascadia/UIHelpers/UIHelpers.vcxproj create mode 100644 src/cascadia/UIHelpers/UIHelpers.vcxproj.filters create mode 100644 src/cascadia/UIHelpers/init.cpp create mode 100644 src/cascadia/UIHelpers/pch.cpp create mode 100644 src/cascadia/UIHelpers/pch.h diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index d14b0a75e3a..5e7aa5c06ee 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -46,6 +46,7 @@ MSAA msixbundle MSVC MSVCP +mtu muxc netcore Onefuzz diff --git a/OpenConsole.sln b/OpenConsole.sln index 1a05e24b750..809dd25a346 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -412,6 +412,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalStress", "src\tools EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UIHelpers", "src\cascadia\UIHelpers\UIHelpers.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchcat", "src\tools\benchcat\benchcat.vcxproj", "{2C836962-9543-4CE5-B834-D28E1F124B66}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleMonitor", "src\tools\ConsoleMonitor\ConsoleMonitor.vcxproj", "{328729E9-6723-416E-9C98-951F1473BBE1}" @@ -2356,6 +2358,29 @@ Global {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.ActiveCfg = Release|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.ActiveCfg = Release|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.ActiveCfg = Release|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.AuditMode|Any CPU.ActiveCfg = AuditMode|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.AuditMode|x64.ActiveCfg = AuditMode|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.AuditMode|x64.Build.0 = AuditMode|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.AuditMode|x86.ActiveCfg = AuditMode|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.Build.0 = Debug|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.ActiveCfg = Debug|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.Build.0 = Debug|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x86.ActiveCfg = Debug|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x86.Build.0 = Debug|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|Any CPU.ActiveCfg = Release|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.ActiveCfg = Release|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.Build.0 = Release|ARM64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.ActiveCfg = Release|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.Build.0 = Release|x64 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x86.ActiveCfg = Release|Win32 + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x86.Build.0 = Release|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|x64.ActiveCfg = Release|x64 @@ -2511,6 +2536,7 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8} {613CCB57-5FA9-48EF-80D0-6B1E319E20C4} = {A10C4720-DCA4-4640-9749-67F4314F527C} {37C995E0-2349-4154-8E77-4A52C0C7F46D} = {A10C4720-DCA4-4640-9749-67F4314F527C} + {6515F03F-E56D-4DB4-B23D-AC4FB80DB36F} = {61901E80-E97D-4D61-A9BB-E8F2FDA8B40C} {2C836962-9543-4CE5-B834-D28E1F124B66} = {A10C4720-DCA4-4640-9749-67F4314F527C} {328729E9-6723-416E-9C98-951F1473BBE1} = {A10C4720-DCA4-4640-9749-67F4314F527C} {BE92101C-04F8-48DA-99F0-E1F4F1D2DC48} = {A10C4720-DCA4-4640-9749-67F4314F527C} diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index 4bb72f46dda..e73d935a883 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -2,13 +2,12 @@ // Licensed under the MIT license. import "TabBase.idl"; -import "IDirectKeyListener.idl"; import "HighlightedTextControl.idl"; import "FilteredCommand.idl"; namespace TerminalApp { - [default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, IDirectKeyListener + [default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener { CommandPalette(); diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index c8100e3568c..2e143a5f12c 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -9,7 +9,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:TerminalApp" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:model="using:Microsoft.Terminal.Settings.Model" + xmlns:mtu="using:Microsoft.Terminal.UI" xmlns:mux="using:Microsoft.UI.Xaml.Controls" AllowFocusOnInteraction="True" AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}" @@ -23,12 +23,6 @@ - - - - - - + Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Item.KeyChordText), Mode=OneWay}"> + Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Item.KeyChordText), Mode=OneWay}"> + Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(PrefixCharacter), Mode=OneWay}" /> + Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(ParentCommandName), Mode=OneWay}">