Remove DxEngine (#16278)

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 
This commit is contained in:
Leonard Hecker 2024-02-22 00:50:59 +01:00 committed by GitHub
parent 9654fc6afe
commit bf25595961
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
68 changed files with 78 additions and 8440 deletions

View File

@ -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}

View File

@ -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.",

View File

@ -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:

View File

@ -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.

View File

@ -3,11 +3,18 @@
#include "pch.h"
#include "HwndTerminal.hpp"
#include <windowsx.h>
#include <DefaultSettings.h>
#include "../../types/viewport.cpp"
#include "../../types/inc/GlyphWidth.hpp"
#include <DefaultSettings.h>
#include <windowsx.h>
#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"
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<DispatchTypes::CursorStyle>(theme.CursorStyle));
publicTerminal->_terminal->SetCursorStyle(static_cast<Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle>(theme.CursorStyle));
publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, static_cast<float>(fontSize), CP_UTF8 };
publicTerminal->_UpdateFont(newDpi);

View File

@ -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 @@ private:
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 };

View File

@ -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; };

View File

@ -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

View File

@ -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"

View File

@ -31,7 +31,7 @@
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\common.nugetversions.props" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
@ -166,7 +166,6 @@
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj" />
<ProjectReference Include="..\..\renderer\uia\lib\uia.vcxproj" />
<ProjectReference Include="..\..\terminal\parser\lib\parser.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\terminal\input\lib\terminalinput.vcxproj" />

View File

@ -43,9 +43,6 @@
<ProjectReference Include="$(OpenConsoleDir)src\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="$(OpenConsoleDir)src\audio\midi\lib\midi.vcxproj">
<Project>{3c67784e-1453-49c2-9660-483e2cc7f7ad}</Project>
</ProjectReference>

View File

@ -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);

View File

@ -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);

View File

@ -117,15 +117,6 @@
</StackPanel>
</local:SettingContainer>
<!-- AtlasEngine -->
<local:SettingContainer x:Uid="Profile_UseAtlasEngine"
ClearSettingValue="{x:Bind Profile.ClearUseAtlasEngine}"
HasSettingValue="{x:Bind Profile.HasUseAtlasEngine, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.UseAtlasEngineOverrideSource, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind Profile.UseAtlasEngine, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- VtPassthrough -->
<local:SettingContainer x:Uid="Profile_VtPassthrough"
ClearSettingValue="{x:Bind Profile.ClearVtPassthrough}"

View File

@ -22,12 +22,6 @@
<TextBlock x:Uid="Globals_RenderingDisclaimer"
Style="{StaticResource DisclaimerStyle}" />
<!-- AtlasEngine -->
<local:SettingContainer x:Uid="Profile_UseAtlasEngine">
<ToggleSwitch IsOn="{x:Bind ViewModel.UseAtlasEngine, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Force Full Repaint -->
<local:SettingContainer x:Uid="Globals_ForceFullRepaint">
<ToggleSwitch IsOn="{x:Bind ViewModel.ForceFullRepaintRendering, Mode=TwoWay}"

View File

@ -12,7 +12,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
explicit RenderingViewModel(Model::CascadiaSettings settings) noexcept;
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.ProfileDefaults(), UseAtlasEngine);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), ForceFullRepaintRendering);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), SoftwareRendering);

View File

@ -11,7 +11,6 @@ namespace Microsoft.Terminal.Settings.Editor
{
RenderingViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, UseAtlasEngine);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ForceFullRepaintRendering);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, SoftwareRendering);
}

View File

@ -1138,10 +1138,6 @@
<value>Controls what happens when the application emits a BEL character.</value>
<comment>A description for what the "bell style" setting does. Presented near "Profile_BellStyle".{Locked="BEL"}</comment>
</data>
<data name="Profile_UseAtlasEngine.Header" xml:space="preserve">
<value>Use the new text renderer ("AtlasEngine")</value>
<comment>{Locked="AtlasEngine"}</comment>
</data>
<data name="Profile_ReloadEnvVars.Header" xml:space="preserve">
<value>Launch this application with a new environment block</value>
<comment>"environment variables" are user-definable values that can affect the way running processes will behave on a computer</comment>

View File

@ -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<winrt::hstring>, BellSound, "bellSound", nullptr) \
X(bool, Elevate, "elevate", false) \

View File

@ -85,7 +85,6 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IMap<String COMMA String>, EnvironmentVariables);
INHERITABLE_PROFILE_SETTING(Boolean, UseAtlasEngine);
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
INHERITABLE_PROFILE_SETTING(Boolean, Elevate);

View File

@ -307,7 +307,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_SuppressApplicationTitle = profile.SuppressApplicationTitle();
}
_UseAtlasEngine = profile.UseAtlasEngine();
_ScrollState = profile.ScrollState();
_AntialiasingMode = profile.AntialiasingMode();

View File

@ -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);

View File

@ -40,7 +40,6 @@
<ProjectReference Include="$(OpenConsoleDir)src\buffer\out\lib\bufferout.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\renderer\dx\lib\dx.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\renderer\uia\lib\uia.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\terminal\parser\lib\parser.vcxproj" />
<ProjectReference Include="$(OpenConsoleDir)src\terminal\input\lib\terminalinput.vcxproj" />

View File

@ -2,18 +2,10 @@
// Licensed under the MIT license.
#include "pch.h"
#include <WexTestClass.h>
#include <DefaultSettings.h>
#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;

View File

@ -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"

View File

@ -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) \

View File

@ -26,15 +26,6 @@
</alwaysEnabledBrandingTokens>
</feature>
<feature>
<name>Feature_ConhostDxEngine</name>
<description>Controls whether conhost supports the DX engine and the UseDx registry key</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledBrandingTokens>
<brandingToken>WindowsInbox</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
<feature>
<name>Feature_ConhostAtlasEngine</name>
<description>Controls whether conhost supports the Atlas engine</description>
@ -44,15 +35,6 @@
</alwaysDisabledBrandingTokens>
</feature>
<feature>
<name>Feature_DxEngineShaderSupport</name>
<description>Controls whether the DX engine is built with shader support.</description>
<stage>AlwaysEnabled</stage>
<alwaysDisabledBrandingTokens>
<brandingToken>WindowsInbox</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
<feature>
<name>Feature_UseNumpadEventsForClipboardInput</name>
<description>Controls whether the clipboard converter (and ConPTY InputStateMachine) uses Numpad events instead of UChar</description>

View File

@ -47,9 +47,6 @@
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>

View File

@ -42,9 +42,6 @@
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\gdi\lib\gdi.vcxproj">
<Project>{1c959542-bac2-4e55-9a6d-13251914cbb9}</Project>
</ProjectReference>

View File

@ -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;
}

View File

@ -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 @@ public:
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 @@ private:
std::wstring _LaunchFaceName;
bool _fAllowAltF4Close;
DWORD _dwVirtTermLevel;
UseDx _fUseDx;
bool _fUseDx;
bool _fCopyColor;
// this is used for the special STARTF_USESIZE mode.

View File

@ -47,9 +47,6 @@
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="..\..\renderer\vt\ut_lib\vt.unittest.vcxproj">
<Project>{990F2657-8580-4828-943F-5DD657D11843}</Project>
</ProjectReference>

View File

@ -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<MockRenderData>();
auto thread = std::make_unique<Microsoft::Console::Render::RenderThread>();
auto* pThread = thread.get();
auto pRenderer = std::make_unique<Microsoft::Console::Render::Renderer>(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.");

View File

@ -63,9 +63,6 @@
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>

View File

@ -26,9 +26,6 @@
<ProjectReference Include="..\..\..\renderer\atlas\atlas.vcxproj">
<Project>{8222900C-8B6C-452A-91AC-BE95DB04B95F}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\dx\lib\dx.vcxproj">
<Project>{48d21369-3d7b-4431-9967-24e81292cf62}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\renderer\vt\lib\vt.vcxproj">
<Project>{990f2657-8580-4828-943f-5dd657d11842}</Project>
</ProjectReference>

View File

@ -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);

View File

@ -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

View File

@ -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:

View File

@ -8,7 +8,6 @@ graph TD
Renderer["Renderer (base/renderer.cpp)\n<small>breaks the text buffer down into GDI-oriented graphics\nprimitives (#quot;change brush to color X#quot;, #quot;draw string Y#quot;, ...)</small>"]
RenderEngineBase[/"RenderEngineBase\n(base/RenderEngineBase.cpp)\n<small>abstracts 24 LOC 👻</small>"\]
GdiEngine["GdiEngine (gdi/...)"]
DxEngine["DxEngine (dx/...)"]
subgraph AtlasEngine["AtlasEngine (atlas/...)"]
AtlasEngine.cpp["AtlasEngine.cpp\n<small>Implements IRenderEngine text rendering API\nbreaks GDI graphics primitives down into DWRITE_GLYPH_RUNs</small>"]
@ -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

View File

@ -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;
}

View File

@ -1,30 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <wrl.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#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
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <dwrite.h>
#include <d2d1.h>
#include <wrl.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#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<DxFontRenderData*> fontRenderData);
[[nodiscard]] HRESULT STDMETHODCALLTYPE AppendClusters(const std::span<const ::Microsoft::Console::Render::Cluster> 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<IDWriteFontFace1> fontFace;
FLOAT fontScale;
::Microsoft::WRL::ComPtr<IUnknown> 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<IDWriteFontFace>& fontFace, FLOAT const scale);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(const gsl::not_null<IDWriteTextAnalysisSource*> 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<IDWriteTextRenderer*> 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<UINT16> _textClusterColumns;
size_t _width;
// Properties of the text that might be relevant.
std::wstring _localeName;
::Microsoft::WRL::ComPtr<IDWriteNumberSubstitution> _numberSubstitution;
DWRITE_READING_DIRECTION _readingDirection;
// Text analysis results
std::vector<LinkedRun> _runs;
std::vector<DWRITE_LINE_BREAKPOINT> _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<DWRITE_GLYPH_OFFSET> _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<UINT16> _glyphClusters;
// This appears to be the index of the glyph inside each font.
std::vector<UINT16> _glyphIndices;
// This is for calculating glyph advances when the entire text is simple.
std::vector<INT32> _glyphDesignUnitAdvances;
std::vector<float> _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<ScaleCorrection> _glyphScaleCorrections;
#ifdef UNIT_TESTING
public:
CustomTextLayout() = default;
friend class CustomTextLayoutTests;
#endif
};
}

View File

@ -1,979 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "CustomTextRenderer.h"
#include "../../inc/DefaultSettings.h"
#include <wrl.h>
#include <wrl/client.h>
#include <VersionHelpers.h>
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<DrawingContext*>(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<DrawingContext*>(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<DrawingContext*>(clientDrawingContext);
RETURN_HR_IF_NULL(E_INVALIDARG, drawingContext);
// Get brush
ID2D1Brush* brush = drawingContext->foregroundBrush;
if (clientDrawingEffect != nullptr)
{
brush = static_cast<ID2D1Brush*>(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<ID2D1DeviceContext*> 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<ULONG>(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<ID2D1SolidColorBrush> brush;
Microsoft::WRL::ComPtr<ID2D1Image> originalTarget;
Microsoft::WRL::ComPtr<ID2D1CommandList> 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<DrawingContext*>(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<ID2D1DeviceContext> 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<ID2D1DeviceContext4> d2dContext4;
RETURN_IF_FAILED(d2dContext.As(&d2dContext4));
::Microsoft::WRL::ComPtr<IDWriteFactory4> 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<IDWriteColorGlyphRunEnumerator1> 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<ID2D1SolidColorBrush> 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<DrawingContext*>(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<ID2D1DeviceContext> 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<IBoxDrawingEffect> boxEffect;
if (SUCCEEDED(clientDrawingEffect->QueryInterface<IBoxDrawingEffect>(&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<ID2D1Factory> d2dFactory;
clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1PathGeometry> pathGeometry;
d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1GeometrySink> 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<ID2D1TransformedGeometry> 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<ID2D1Factory> d2dFactory;
clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1PathGeometry> pathGeometry;
d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1GeometrySink> 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<ID2D1TransformedGeometry> transformedGeometry;
d2dFactory->CreateTransformedGeometry(pathGeometry.Get(),
&matrixAlign,
transformedGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> alignedGeometry;
d2dFactory->CreateTransformedGeometry(pathGeometry.Get(),
&matrixAlign,
alignedGeometry.GetAddressOf());
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> brush;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> 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;
}

View File

@ -1,153 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <wrl/implements.h>
#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<CursorOptions>& 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<CursorOptions> 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<ID2D1DeviceContext*> 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<D2D1_RECT_F> _clipRect;
};
}

View File

@ -1,313 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "DxFontInfo.h"
#include <unicode.hpp>
#include <VersionHelpers.h>
#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<IDWriteFontFace1> DxFontInfo::ResolveFontFaceWithFallback(std::wstring& localeName)
{
// First attempt to find exactly what the user asked for.
_didFallback = false;
Microsoft::WRL::ComPtr<IDWriteFontFace1> 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<IDWriteFontFace1> DxFontInfo::_FindFontFace(std::wstring& localeName)
{
Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
UINT32 familyIndex;
BOOL familyExists;
THROW_IF_FAILED(_fontCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists));
if (familyExists)
{
Microsoft::WRL::ComPtr<IDWriteFontFamily> fontFamily;
THROW_IF_FAILED(_fontCollection->GetFontFamily(familyIndex, &fontFamily));
Microsoft::WRL::ComPtr<IDWriteFont> font;
THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(GetWeight(), GetStretch(), GetStyle(), &font));
Microsoft::WRL::ComPtr<IDWriteFontFace> 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<IDWriteFontFamily*> fontFamily,
std::wstring& localeName)
{
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> 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;
}

View File

@ -1,72 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>
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<IDWriteFontFace1> ResolveFontFaceWithFallback(std::wstring& localeName);
private:
[[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(std::wstring& localeName);
[[nodiscard]] std::wstring _GetFontFamilyName(const gsl::not_null<IDWriteFontFamily*> 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<IDWriteFontCollection> _fontCollection;
// Indicates whether we couldn't match the user request and had to choose from a hard-coded default list.
bool _didFallback;
};
}

View File

@ -1,923 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "DxFontRenderData.h"
#include <VersionHelpers.h>
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<IDWriteFactory1> dwriteFactory) :
_dwriteFactory(std::move(dwriteFactory)),
_defaultFontInfo{ _dwriteFactory.Get() },
_lineSpacing{},
_lineMetrics{},
_fontSize{}
{
}
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> DxFontRenderData::Analyzer()
{
if (!_dwriteTextAnalyzer)
{
Microsoft::WRL::ComPtr<IDWriteTextAnalyzer> analyzer;
THROW_IF_FAILED(_dwriteFactory->CreateTextAnalyzer(&analyzer));
THROW_IF_FAILED(analyzer.As(&_dwriteTextAnalyzer));
}
return _dwriteTextAnalyzer;
}
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFallback> DxFontRenderData::SystemFontFallback()
{
if (!_systemFontFallback)
{
::Microsoft::WRL::ComPtr<IDWriteFactory2> factory2;
THROW_IF_FAILED(_dwriteFactory.As(&factory2));
factory2->GetSystemFontFallback(&_systemFontFallback);
}
return _systemFontFallback;
}
[[nodiscard]] std::wstring DxFontRenderData::UserLocaleName()
{
if (_userLocaleName.empty())
{
std::array<wchar_t, LOCALE_NAME_MAX_LENGTH> localeName;
const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow<int>(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<DWRITE_FONT_FEATURE>& DxFontRenderData::DefaultFontFeatures() const noexcept
{
return _featureVector;
}
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextFormat> DxFontRenderData::DefaultTextFormat()
{
return TextFormatWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch());
}
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontRenderData::DefaultFontFace()
{
return FontFaceWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch());
}
[[nodiscard]] Microsoft::WRL::ComPtr<IBoxDrawingEffect> 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<IDWriteTextFormat> 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<IDWriteTextFormat> 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<IDWriteFontFace1> 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<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& 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<DWRITE_FONT_WEIGHT>(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<BoxDrawingEffect>(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<std::wstring_view, uint32_t>& features)
{
// Populate the feature map with the standard list first
std::unordered_map<DWRITE_FONT_FEATURE_TAG, uint32_t> 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<std::wstring_view, float>& axes)
{
// Clear out the old vector and booleans in case this is a hot reload
_axesVector = std::vector<DWRITE_FONT_AXIS_VALUE>{};
_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<DWRITE_FONT_AXIS_VALUE> 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<size_t>(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<size_t>(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<DWRITE_FONT_AXIS_VALUE> 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<DWRITE_FONT_AXIS_VALUE> 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<float>(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<float>(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<float>(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<til::CoordType>(widthExact);
coordSize.height = static_cast<til::CoordType>(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<IDWriteTextFormat> DxFontRenderData::_BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName)
{
Microsoft::WRL::ComPtr<IDWriteTextFormat> 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<IDWriteTextFormat3> format3;
if (!FAILED(format->QueryInterface(IID_PPV_ARGS(&format3))))
{
if (_inhibitUserWeight && !_axesVectorWithoutWeight.empty())
{
format3->SetFontAxisValues(_axesVectorWithoutWeight.data(), gsl::narrow<uint32_t>(_axesVectorWithoutWeight.size()));
}
else if (!_inhibitUserWeight && !_axesVector.empty())
{
format3->SetFontAxisValues(_axesVector.data(), gsl::narrow<uint32_t>(_axesVector.size()));
}
}
return format;
}

View File

@ -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 <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>
#include <wrl.h>
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<IDWriteFactory1> dwriteFactory);
// DirectWrite text analyzer from the factory
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> Analyzer();
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFallback> 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<DWRITE_FONT_FEATURE>& DefaultFontFeatures() const noexcept;
// The DirectWrite format object representing the size and other text properties to be applied (by default)
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextFormat> DefaultTextFormat();
// The DirectWrite font face to use while calculating layout (by default)
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DefaultFontFace();
// Box drawing scaling effects that are cached for the base font across layouts
[[nodiscard]] Microsoft::WRL::ComPtr<IBoxDrawingEffect> DefaultBoxDrawingEffect();
// The attributed variants of the format object representing the size and other text properties
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextFormat> 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<IDWriteFontFace1> 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<std::wstring_view, uint32_t>& features = {}, const std::unordered_map<std::wstring_view, float>& 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<DWRITE_FONT_AXIS_VALUE> 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<DWRITE_FONT_FEATURE> _featureVector;
// The font axes to apply to the text
std::vector<DWRITE_FONT_AXIS_VALUE> _axesVector;
std::span<DWRITE_FONT_AXIS_VALUE> _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<std::wstring_view, uint32_t>& features);
void _SetAxes(const std::unordered_map<std::wstring_view, float>& 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<IDWriteTextFormat> _BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName);
std::unordered_map<FontAttributeMapKey, ::Microsoft::WRL::ComPtr<IDWriteTextFormat>> _textFormatMap;
std::unordered_map<FontAttributeMapKey, ::Microsoft::WRL::ComPtr<IDWriteFontFace1>> _fontFaceMap;
::Microsoft::WRL::ComPtr<IBoxDrawingEffect> _boxDrawingEffect;
::Microsoft::WRL::ComPtr<IDWriteFontFallback> _systemFontFallback;
::Microsoft::WRL::ComPtr<IDWriteFactory1> _dwriteFactory;
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _dwriteTextAnalyzer;
std::wstring _userLocaleName;
DxFontInfo _defaultFontInfo;
til::size _glyphCell;
DWRITE_LINE_SPACING _lineSpacing;
LineMetrics _lineMetrics;
float _fontSize;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,327 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "../../renderer/inc/RenderEngineBase.hpp"
#include <functional>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#include <DirectXMath.h>
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>
#include <wrl.h>
#include <wrl/client.h>
#include "CustomTextLayout.h"
#include "CustomTextRenderer.h"
#include "DxFontRenderData.h"
#include "DxSoftFont.h"
#include "../../types/inc/Viewport.hpp"
#include <TraceLoggingProvider.h>
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<void(const HANDLE)> pfn) noexcept override;
void SetWarningCallback(std::function<void(const HRESULT)> 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<til::rect>& 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<const uint16_t> 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<const Cluster> 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<til::rect>& rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes,
const RenderSettings& renderSettings,
const gsl::not_null<IRenderData*> 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<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& 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<const til::rect>& 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<void(const HANDLE)> _pfn;
std::function<void(const HRESULT)> _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<til::rect> _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
static std::atomic<size_t> _tracelogCount;
wil::unique_handle _swapChainHandle;
// Device-Independent Resources
::Microsoft::WRL::ComPtr<ID2D1Factory1> _d2dFactory;
::Microsoft::WRL::ComPtr<IDWriteFactory1> _dwriteFactory;
::Microsoft::WRL::ComPtr<CustomTextLayout> _customLayout;
::Microsoft::WRL::ComPtr<CustomTextRenderer> _customRenderer;
::Microsoft::WRL::ComPtr<ID2D1StrokeStyle> _strokeStyle;
::Microsoft::WRL::ComPtr<ID2D1StrokeStyle> _dashStrokeStyle;
std::unique_ptr<DxFontRenderData> _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<ID3D11Device> _d3dDevice;
::Microsoft::WRL::ComPtr<ID3D11DeviceContext> _d3dDeviceContext;
::Microsoft::WRL::ComPtr<ID2D1Device> _d2dDevice;
::Microsoft::WRL::ComPtr<ID2D1DeviceContext> _d2dDeviceContext;
::Microsoft::WRL::ComPtr<ID2D1Bitmap1> _d2dBitmap;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushForeground;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushBackground;
::Microsoft::WRL::ComPtr<IDXGIFactory2> _dxgiFactory2;
::Microsoft::WRL::ComPtr<IDXGIFactoryMedia> _dxgiFactoryMedia;
::Microsoft::WRL::ComPtr<IDXGIDevice> _dxgiDevice;
::Microsoft::WRL::ComPtr<IDXGISurface> _dxgiSurface;
DXGI_SWAP_CHAIN_DESC1 _swapChainDesc;
::Microsoft::WRL::ComPtr<IDXGISwapChain1> _dxgiSwapChain;
wil::unique_handle _swapChainFrameLatencyWaitableObject;
std::unique_ptr<DrawingContext> _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<ID3D11RenderTargetView> _renderTargetView;
::Microsoft::WRL::ComPtr<ID3D11VertexShader> _vertexShader;
::Microsoft::WRL::ComPtr<ID3D11PixelShader> _pixelShader;
::Microsoft::WRL::ComPtr<ID3D11InputLayout> _vertexLayout;
::Microsoft::WRL::ComPtr<ID3D11Buffer> _screenQuadVertexBuffer;
::Microsoft::WRL::ComPtr<ID3D11Buffer> _pixelShaderSettingsBuffer;
::Microsoft::WRL::ComPtr<ID3D11SamplerState> _samplerState;
::Microsoft::WRL::ComPtr<ID3D11Texture2D> _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 };
}
};
}

View File

@ -1,245 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "DxSoftFont.h"
#include "CustomTextRenderer.h"
#include <d2d1effects.h>
#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<const uint16_t> 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<size_t>(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<byte>(bitmapWidth * bitmapHeight);
_bitmapSize = { gsl::narrow_cast<UINT32>(bitmapWidth), gsl::narrow_cast<UINT32>(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<size_t>(glyphNumber);
const auto yOffset = _yOffsetForGlyph<size_t>(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<size_t>(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<const Cluster> clusters,
const float originX,
const float originY)
{
ComPtr<ID2D1DeviceContext> 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<float>(glyphNumber);
const auto y = _yOffsetForGlyph<float>(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<ID2D1DeviceContext*> 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<UINT32>(_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<float>(_targetSize.width - targetCenteringHint) / (_sourceSize.width - _centeringHint);
const auto yScale = gsl::narrow_cast<float>(_targetSize.height) / _sourceSize.height;
return D2D1::Vector2F(xScale, yScale);
}
template<typename T>
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<T>(xOffsetInGrid * paddedGlyphWidth + PADDING);
}
template<typename T>
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<T>(yOffsetInGrid * paddedGlyphHeight + PADDING);
}

View File

@ -1,56 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "../inc/Cluster.hpp"
#include <vector>
#include <d2d1_1.h>
#include <wrl.h>
#include <wrl/client.h>
namespace Microsoft::Console::Render
{
struct DrawingContext;
class DxSoftFont
{
public:
void SetFont(const std::span<const uint16_t> 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<const Cluster> 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<ID2D1DeviceContext*> d2dContext);
D2D1_VECTOR_2F _scaleForTargetSize() const noexcept;
template<typename T>
T _xOffsetForGlyph(const size_t glyphNumber) const noexcept;
template<typename T>
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<byte> _bitmapBits;
::Microsoft::WRL::ComPtr<ID2D1Bitmap> _bitmap;
::Microsoft::WRL::ComPtr<ID2D1Effect> _scaleEffect;
::Microsoft::WRL::ComPtr<ID2D1Effect> _colorEffect;
};
}

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -1,2 +0,0 @@
DIRS= \
lib \

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{48D21369-3D7B-4431-9967-24E81292CF62}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>dx</RootNamespace>
<ProjectName>RendererDx</ProjectName>
<TargetName>ConRenderDx</TargetName>
<ConfigurationType>StaticLibrary</ConfigurationType>
</PropertyGroup>
<ItemDefinitionGroup>
<Midl>
<OutputDirectory>$(SolutionDir)src\renderer\dx\</OutputDirectory>
</Midl>
</ItemDefinitionGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="..\BoxDrawingEffect.cpp" />
<ClCompile Include="..\CustomTextLayout.cpp" />
<ClCompile Include="..\CustomTextRenderer.cpp" />
<ClCompile Include="..\DxFontInfo.cpp" />
<ClCompile Include="..\DxFontRenderData.cpp" />
<ClCompile Include="..\DxRenderer.cpp" />
<ClCompile Include="..\DxSoftFont.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\BoxDrawingEffect.h" />
<ClInclude Include="..\CustomTextLayout.h" />
<ClInclude Include="..\CustomTextRenderer.h" />
<ClInclude Include="..\DxFontInfo.h" />
<ClInclude Include="..\DxFontRenderData.h" />
<ClInclude Include="..\DxRenderer.hpp" />
<ClInclude Include="..\DxSoftFont.h" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\ScreenPixelShader.h" />
<ClInclude Include="..\ScreenVertexShader.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\IBoxDrawingEffect.idl" />
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\BoxDrawingEffect.cpp" />
<ClCompile Include="..\CustomTextLayout.cpp" />
<ClCompile Include="..\CustomTextRenderer.cpp" />
<ClCompile Include="..\DxFontInfo.cpp" />
<ClCompile Include="..\DxFontRenderData.cpp" />
<ClCompile Include="..\DxRenderer.cpp" />
<ClCompile Include="..\DxSoftFont.cpp" />
<ClCompile Include="..\precomp.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\BoxDrawingEffect.h" />
<ClInclude Include="..\CustomTextLayout.h" />
<ClInclude Include="..\CustomTextRenderer.h" />
<ClInclude Include="..\DxFontInfo.h" />
<ClInclude Include="..\DxFontRenderData.h" />
<ClInclude Include="..\DxRenderer.hpp" />
<ClInclude Include="..\DxSoftFont.h" />
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\ScreenPixelShader.h" />
<ClInclude Include="..\ScreenVertexShader.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\IBoxDrawingEffect.idl" />
</ItemGroup>
</Project>

View File

@ -1,8 +0,0 @@
!include ..\sources.inc
# -------------------------------------
# Program Information
# -------------------------------------
TARGETNAME = ConRenderDx
TARGETTYPE = LIBRARY

View File

@ -1,4 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"

View File

@ -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 <windows.h>
#include <winmeta.h>
#include "../host/conddkrefs.h"
#include <condrv.h>
#include <cmath>
#include <exception>
#include <typeinfo>
#include <dcomp.h>
#include <dxgi.h>
#include <dxgi1_2.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include <d2d1.h>
#include <d2d1_1.h>
#include <d2d1_2.h>
#include <d2d1_3.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <dwrite_1.h>
#include <dwrite_2.h>
#include <dwrite_3.h>
// Re-include TIL at the bottom to gain DX superpowers.
#include "til.h"
#pragma hdrstop

View File

@ -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

View File

@ -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);
}
};

View File

@ -1,12 +0,0 @@
//Autogenerated file name + version resource file for Device Guard whitelisting effort
#include <windows.h>
#include <ntverp.h>
#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"

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{95B136F9-B238-490C-A7C5-5843C1FECAC4}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>DxUnitTests</RootNamespace>
<ProjectName>Dx.Unit.Tests</ProjectName>
<TargetName>Dx.Unit.Tests</TargetName>
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)\src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="CustomTextLayoutTests.cpp" />
<ClCompile Include="..\precomp.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
</ProjectReference>
<ProjectReference Include="..\..\base\lib\base.vcxproj">
<Project>{af0a096a-8b3a-4949-81ef-7df8f0fee91f}</Project>
</ProjectReference>
<ProjectReference Include="..\lib\dx.vcxproj">
<Project>{48D21369-3D7B-4431-9967-24E81292CF62}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.build.tests.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="ProductBuild" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(NTMAKEENV)\UniversalTest\Microsoft.TestInfrastructure.UniversalTest.props" />
</Project>

View File

@ -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