From a6a0e44088ab768325665308367ac016a2534891 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 23 Feb 2024 22:40:29 +0100 Subject: [PATCH] Add support for custom box drawing and powerline glyphs (#16729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for drawing our own box drawing, block element, and basic Powerline (U+E0Bx) glyphs in AtlasEngine. This PR consists of 4 parts: * AtlasEngine was refactored to simplify `_drawGlyph` because I've wanted to do that for ~1 year now and never got a chance. Well, now I'm doing it and you all will review it muahahaha. The good news is that it removes a goto usage that even for my standards was rather dangerous. Now it's gone and the risk with it. * AtlasEngine was further refactored to properly parse out text that we want to handle different from regular text. Previously, we only did that for soft fonts, but now we want to do that for a lot more, so a refactor was in order. The new code is still extremely disgusting, because I now stuff `wchar_t`s into an array that's intended for glyph indices, but that's the best way to make it fast and not blow up the complexity of the code even further. * Finally this adds a huge LUT for all the aforementioned glyphs. The LUT has 4 "drawing instruction" entries per glyph which describe the shape (rectangle, circle, lines, etc.) and the start/end coord. With a lot of bit packing each entry is only 4 bytes large. * Finally-finally a `builtinGlyphs` setting was added to the font object and it defaults to `true`. Closes #5897 ## Validation Steps Performed * RenderingTests with soft fonts ✅ * All the aforementioned glyphs ✅ * ...with color ✅ * `customGlyphs` setting can be toggled on and off ✅ --- src/cascadia/TerminalControl/ControlCore.cpp | 2 + src/cascadia/TerminalControl/ControlCore.h | 1 + .../TerminalControl/IControlSettings.idl | 1 + .../TerminalSettingsEditor/Appearances.h | 1 + .../TerminalSettingsEditor/Appearances.idl | 1 + .../TerminalSettingsEditor/Appearances.xaml | 9 + .../Resources/en-US/Resources.resw | 8 + .../TerminalSettingsModel/FontConfig.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + src/host/screenInfo.cpp | 4 + src/host/settings.cpp | 5 + src/host/settings.hpp | 2 + src/inc/til/flat_set.h | 36 +- src/inc/til/unicode.h | 13 +- src/propslib/RegistrySerialization.cpp | 5 +- src/renderer/atlas/AtlasEngine.api.cpp | 6 +- src/renderer/atlas/AtlasEngine.cpp | 144 +- src/renderer/atlas/AtlasEngine.h | 16 +- src/renderer/atlas/AtlasEngine.r.cpp | 22 +- src/renderer/atlas/Backend.h | 5 - src/renderer/atlas/BackendD3D.cpp | 480 +++---- src/renderer/atlas/BackendD3D.h | 104 +- src/renderer/atlas/BuiltinGlyphs.cpp | 1276 +++++++++++++++++ src/renderer/atlas/BuiltinGlyphs.h | 18 + src/renderer/atlas/atlas.vcxproj | 2 + src/renderer/atlas/common.h | 19 +- src/renderer/base/FontInfoDesired.cpp | 10 + src/renderer/inc/FontInfoDesired.hpp | 3 + src/til/ut_til/FlatSetTests.cpp | 37 +- 32 files changed, 1775 insertions(+), 460 deletions(-) create mode 100644 src/renderer/atlas/BuiltinGlyphs.cpp create mode 100644 src/renderer/atlas/BuiltinGlyphs.h diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 0dd8df67a83..8d3e692b114 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -860,6 +860,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto lock = _terminal->LockForWriting(); + _builtinGlyphs = _settings->EnableBuiltinGlyphs(); _cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str()); _cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str()); _runtimeOpacity = std::nullopt; @@ -1038,6 +1039,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false }; _actualFontFaceName = { fontFace }; + _desiredFont.SetEnableBuiltinGlyphs(_builtinGlyphs); _desiredFont.SetCellSize(_cellWidth, _cellHeight); const auto before = _actualFont.GetSize(); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index dff8ba52bf3..6ab42cdbddb 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -316,6 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation FontInfoDesired _desiredFont; FontInfo _actualFont; winrt::hstring _actualFontFaceName; + bool _builtinGlyphs = true; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index e89b58eeb8c..8a680f1488e 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Control String Padding { get; }; Windows.Foundation.Collections.IMap FontFeatures { get; }; Windows.Foundation.Collections.IMap FontAxes { get; }; + Boolean EnableBuiltinGlyphs { get; }; String CellWidth { get; }; String CellHeight { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index bc34e62978c..4f220ef5e78 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -87,6 +87,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFace); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight); + OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_SETTING(_appearance, RetroTerminalEffect); OBSERVABLE_PROJECTED_SETTING(_appearance, CursorShape); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 55ff2be721d..ddab88a6beb 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -41,6 +41,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Text.FontWeight, FontWeight); + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, DarkColorSchemeName); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, LightColorSchemeName); diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index e37cdd2e1bf..f1f13d8fbd6 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -287,6 +287,15 @@ + + + + + 1.2 "1.2" is a decimal number. + + Builtin Glyphs + The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones. + + + When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font. This feature only works when GPU Acceleration is available. + A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification. + Font weight Name for a control to select the weight (i.e. bold, thin, etc.) of the text in the app. diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.idl b/src/cascadia/TerminalSettingsModel/FontConfig.idl index 249bb94f8d1..995e7c6a651 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.idl +++ b/src/cascadia/TerminalSettingsModel/FontConfig.idl @@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight); INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontAxes); + INHERITABLE_FONT_SETTING(Boolean, EnableBuiltinGlyphs); INHERITABLE_FONT_SETTING(String, CellWidth); INHERITABLE_FONT_SETTING(String, CellHeight); } diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index f9626e6b6c8..43bb7a12eb4 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -114,6 +114,7 @@ Author(s): X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \ X(IFontAxesMap, FontAxes, "axes") \ X(IFontFeatureMap, FontFeatures, "features") \ + X(bool, EnableBuiltinGlyphs, "builtinGlyphs", true) \ X(winrt::hstring, CellWidth, "cellWidth") \ X(winrt::hstring, CellHeight, "cellHeight") diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 21d76fd8ed8..cb5633eace4 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -289,6 +289,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _FontWeight = fontInfo.FontWeight(); _FontFeatures = fontInfo.FontFeatures(); _FontAxes = fontInfo.FontAxes(); + _EnableBuiltinGlyphs = fontInfo.EnableBuiltinGlyphs(); _CellWidth = fontInfo.CellWidth(); _CellHeight = fontInfo.CellHeight(); _Padding = profile.Padding(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index b065ee3c9f6..0e7346a5960 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -129,6 +129,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, winrt::Windows::UI::Text::FontWeight, FontWeight); INHERITABLE_SETTING(Model::TerminalSettings, IFontAxesMap, FontAxes); INHERITABLE_SETTING(Model::TerminalSettings, IFontFeatureMap, FontFeatures); + INHERITABLE_SETTING(Model::TerminalSettings, bool, EnableBuiltinGlyphs, true); INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellWidth); INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellHeight); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 02c4ad2427a..8134701d0f6 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -63,6 +63,7 @@ X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ X(IFontFeatureMap, FontFeatures) \ X(IFontAxesMap, FontAxes) \ + X(bool, EnableBuiltinGlyphs, true) \ X(winrt::hstring, CellWidth) \ X(winrt::hstring, CellHeight) \ X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \ diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 2701b4e2072..9b0ff2ca4e4 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -64,6 +64,7 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( { OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; } + _desiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); } // Routine Description: @@ -539,7 +540,10 @@ void SCREEN_INFORMATION::RefreshFontWithRenderer() void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont) { + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + FontInfoDesired fiDesiredFont(*pfiNewFont); + fiDesiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); GetDesiredFont() = fiDesiredFont; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 274fadec58f..0798fe847ab 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -776,3 +776,8 @@ bool Settings::GetCopyColor() const noexcept { return _fCopyColor; } + +bool Settings::GetEnableBuiltinGlyphs() const noexcept +{ + return _fEnableBuiltinGlyphs; +} diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 4edeb3ae927..a254589755f 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -171,6 +171,7 @@ class Settings bool GetUseDx() const noexcept; bool GetCopyColor() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; private: RenderSettings _renderSettings; @@ -214,6 +215,7 @@ class Settings DWORD _dwVirtTermLevel; bool _fUseDx; bool _fCopyColor; + bool _fEnableBuiltinGlyphs = true; // this is used for the special STARTF_USESIZE mode. bool _fUseWindowSizePixels; diff --git a/src/inc/til/flat_set.h b/src/inc/til/flat_set.h index 07942abf715..ef7045759ab 100644 --- a/src/inc/til/flat_set.h +++ b/src/inc/til/flat_set.h @@ -4,8 +4,9 @@ #pragma once #pragma warning(push) -#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). #pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique instead (r.11). +#pragma warning(disable : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21). +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). namespace til { @@ -36,7 +37,7 @@ namespace til // * small and cheap T // * >= 50% successful lookups // * <= 50% load factor (LoadFactor >= 2, which is the minimum anyways) - template + template struct linear_flat_set { static_assert(LoadFactor >= 2); @@ -98,27 +99,28 @@ namespace til return nullptr; } - const auto hash = ::std::hash{}(key) >> _shift; + const auto hash = Traits::hash(key) >> _shift; for (auto i = hash;; ++i) { auto& slot = _map[i & _mask]; - if (!slot) + if (!Traits::occupied(slot)) { return nullptr; } - if (slot == key) [[likely]] + if (Traits::equals(slot, key)) [[likely]] { return &slot; } } } + // NOTE: It also does not initialize the returned slot. + // You must do that yourself in way that ensures that Traits::occupied(slot) now returns true. + // Use lookup() to check if the item already exists. template - std::pair insert(U&& key) + std::pair insert(U&& key) { - // Putting this into the lookup path is a little pessimistic, but it - // allows us to default-construct this hashmap with a size of 0. if (_load >= _capacity) [[unlikely]] { _bumpSize(); @@ -129,20 +131,20 @@ namespace til // many times in literature that such a scheme performs the best on average. // As such, we perform the divide here to get the topmost bits down. // See flat_set_hash_integer. - const auto hash = ::std::hash{}(key) >> _shift; + const auto hash = Traits::hash(key) >> _shift; for (auto i = hash;; ++i) { auto& slot = _map[i & _mask]; - if (!slot) + if (!Traits::occupied(slot)) { - slot = std::forward(key); _load += LoadFactor; - return { slot, true }; + Traits::assign(slot, key); + return { &slot, true }; } - if (slot == key) [[likely]] + if (Traits::equals(slot, key)) [[likely]] { - return { slot, false }; + return { &slot, false }; } } } @@ -166,17 +168,17 @@ namespace til // This mirrors the insert() function, but without the lookup part. for (auto& oldSlot : container()) { - if (!oldSlot) + if (!Traits::occupied(oldSlot)) { continue; } - const auto hash = ::std::hash{}(oldSlot) >> newShift; + const auto hash = Traits::hash(oldSlot) >> newShift; for (auto i = hash;; ++i) { auto& slot = newMap[i & newMask]; - if (!slot) + if (!Traits::occupied(slot)) { slot = std::move_if_noexcept(oldSlot); break; diff --git a/src/inc/til/unicode.h b/src/inc/til/unicode.h index 9f703dcb813..99aeeb74769 100644 --- a/src/inc/til/unicode.h +++ b/src/inc/til/unicode.h @@ -10,21 +10,28 @@ namespace til inline constexpr wchar_t UNICODE_REPLACEMENT = 0xFFFD; } - static constexpr bool is_surrogate(const wchar_t wch) noexcept + constexpr bool is_surrogate(const auto wch) noexcept { return (wch & 0xF800) == 0xD800; } - static constexpr bool is_leading_surrogate(const wchar_t wch) noexcept + constexpr bool is_leading_surrogate(const auto wch) noexcept { return (wch & 0xFC00) == 0xD800; } - static constexpr bool is_trailing_surrogate(const wchar_t wch) noexcept + constexpr bool is_trailing_surrogate(const auto wch) noexcept { return (wch & 0xFC00) == 0xDC00; } + constexpr char32_t combine_surrogates(const auto lead, const auto trail) + { + // Ah, I love these bracketed C-style casts. I use them in C all the time. Yep. +#pragma warning(suppress : 26493) // Don't use C-style casts (type.4). + return (char32_t{ lead } << 10) - 0x35FDC00 + char32_t{ trail }; + } + // Verifies the beginning of the given UTF16 string and returns the first UTF16 sequence // or U+FFFD otherwise. It's not really useful and at the time of writing only a // single caller uses this. It's best to delete this if you read this comment. diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index a4441d25a5a..a350700c66f 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -61,7 +61,10 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Boolean, CONSOLE_REGISTRY_INTERCEPTCOPYPASTE, SET_FIELD_AND_SIZE(_fInterceptCopyPaste) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_TERMINALSCROLLING, SET_FIELD_AND_SIZE(_TerminalScrolling) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, - { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) } + { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }, +#if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED + { _RegPropertyType::Boolean, L"EnableBuiltinGlyphs", SET_FIELD_AND_SIZE(_fEnableBuiltinGlyphs) }, +#endif // Special cases that are handled manually in Registry::LoadFromRegistry: // - CONSOLE_REGISTRY_WINDOWPOS diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 0e2f7941688..fa7290371e8 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -295,8 +295,8 @@ CATCH_RETURN() /* fontStyle */ DWRITE_FONT_STYLE_NORMAL, /* fontStretch */ DWRITE_FONT_STRETCH_NORMAL, /* fontSize */ _api.s->font->fontSize, - /* localeName */ L"", - /* textFormat */ textFormat.put())); + /* localeName */ _p.userLocaleName.c_str(), + /* textFormat */ textFormat.addressof())); wil::com_ptr textLayout; RETURN_IF_FAILED(_p.dwriteFactory->CreateTextLayout(glyph.data(), gsl::narrow_cast(glyph.size()), textFormat.get(), FLT_MAX, FLT_MAX, textLayout.addressof())); @@ -774,5 +774,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->doubleUnderline[0] = { doubleUnderlinePosTopU16, thinLineWidthU16 }; fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; + + fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs(); } } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 93821394ce1..01403ef4b38 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -4,7 +4,10 @@ #include "pch.h" #include "AtlasEngine.h" +#include + #include "Backend.h" +#include "BuiltinGlyphs.h" #include "DWriteTextAnalysis.h" #include "../../interactivity/win32/CustomWindowMessages.h" @@ -20,6 +23,7 @@ #pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). #pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...]. #pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). +#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1). #pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2). using namespace Microsoft::Console::Render::Atlas; @@ -70,8 +74,9 @@ try _handleSettingsUpdate(); } - if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION) + if (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION || _hackTriggerRedrawAll) { + _hackTriggerRedrawAll = false; _api.invalidatedRows = invalidatedRowsAll; _api.scrollOffset = 0; } @@ -575,7 +580,7 @@ void AtlasEngine::_recreateFontDependentResources() memcpy(&localeName[0], L"en-US", 12); } - _api.userLocaleName = std::wstring{ &localeName[0] }; + _p.userLocaleName = std::wstring{ &localeName[0] }; } if (_p.s->font->fontAxisValues.empty()) @@ -606,6 +611,8 @@ void AtlasEngine::_recreateFontDependentResources() _api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() }; } } + + _hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D; } void AtlasEngine::_recreateCellCountDependentResources() @@ -666,14 +673,65 @@ void AtlasEngine::_flushBufferLine() // This would seriously blow us up otherwise. Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1); + const auto beg = _api.bufferLine.data(); + const auto len = _api.bufferLine.size(); + size_t segmentBeg = 0; + size_t segmentEnd = 0; + bool custom = false; + + if (!_hackWantsBuiltinGlyphs) + { + _mapRegularText(0, len); + return; + } + + while (segmentBeg < len) + { + segmentEnd = segmentBeg; + do + { + auto i = segmentEnd; + char32_t codepoint = beg[i++]; + if (til::is_leading_surrogate(codepoint) && i < len) + { + codepoint = til::combine_surrogates(codepoint, beg[i++]); + } + + const auto c = BuiltinGlyphs::IsBuiltinGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint); + if (custom != c) + { + break; + } + + segmentEnd = i; + } while (segmentEnd < len); + + if (segmentBeg != segmentEnd) + { + if (custom) + { + _mapBuiltinGlyphs(segmentBeg, segmentEnd); + } + else + { + _mapRegularText(segmentBeg, segmentEnd); + } + } + + segmentBeg = segmentEnd; + custom = !custom; + } +} + +void AtlasEngine::_mapRegularText(size_t offBeg, size_t offEnd) +{ auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y]; -#pragma warning(suppress : 26494) // Variable 'mappedEnd' is uninitialized. Always initialize an object (type.5). - for (u32 idx = 0, mappedEnd; idx < _api.bufferLine.size(); idx = mappedEnd) + for (u32 idx = gsl::narrow_cast(offBeg), mappedEnd = 0; idx < offEnd; idx = mappedEnd) { u32 mappedLength = 0; wil::com_ptr mappedFontFace; - _mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast(_api.bufferLine.size()) - idx, &mappedLength, mappedFontFace.addressof()); + _mapCharacters(_api.bufferLine.data() + idx, gsl::narrow_cast(offEnd - idx), &mappedLength, mappedFontFace.addressof()); mappedEnd = idx + mappedLength; if (!mappedFontFace) @@ -697,7 +755,7 @@ void AtlasEngine::_flushBufferLine() _api.glyphProps = Buffer{ size }; } - if (_api.s->font->fontFeatures.empty()) + if (_p.s->font->fontFeatures.empty()) { // We can reuse idx here, as it'll be reset to "idx = mappedEnd" in the outer loop anyways. for (u32 complexityLength = 0; idx < mappedEnd; idx += complexityLength) @@ -712,10 +770,10 @@ void AtlasEngine::_flushBufferLine() for (size_t i = 0; i < complexityLength; ++i) { - const size_t col1 = _api.bufferLineColumn[idx + i + 0]; - const size_t col2 = _api.bufferLineColumn[idx + i + 1]; + const auto col1 = _api.bufferLineColumn[idx + i + 0]; + const auto col2 = _api.bufferLineColumn[idx + i + 1]; const auto glyphAdvance = (col2 - col1) * _p.s->font->cellSize.x; - const auto fg = colors[col1 << shift]; + const auto fg = colors[static_cast(col1) << shift]; row.glyphIndices.emplace_back(_api.glyphIndices[i]); row.glyphAdvances.emplace_back(static_cast(glyphAdvance)); row.glyphOffsets.emplace_back(); @@ -750,9 +808,31 @@ void AtlasEngine::_flushBufferLine() } } +void AtlasEngine::_mapBuiltinGlyphs(size_t offBeg, size_t offEnd) +{ + auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y]; + auto initialIndicesCount = row.glyphIndices.size(); + const auto shift = gsl::narrow_cast(row.lineRendition != LineRendition::SingleWidth); + const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y; + const auto base = reinterpret_cast(_api.bufferLine.data()); + const auto len = offEnd - offBeg; + + row.glyphIndices.insert(row.glyphIndices.end(), base + offBeg, base + offEnd); + row.glyphAdvances.insert(row.glyphAdvances.end(), len, static_cast(_p.s->font->cellSize.x)); + row.glyphOffsets.insert(row.glyphOffsets.end(), len, {}); + + for (size_t i = offBeg; i < offEnd; ++i) + { + const auto col = _api.bufferLineColumn[i]; + row.colors.emplace_back(colors[static_cast(col) << shift]); + } + + row.mappings.emplace_back(nullptr, gsl::narrow_cast(initialIndicesCount), gsl::narrow_cast(row.glyphIndices.size())); +} + void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const { - TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), text, textLength }; + TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), text, textLength }; const auto& textFormatAxis = _api.textFormatAxes[static_cast(_api.attributes)]; // We don't read from scale anyways. @@ -807,7 +887,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len { _api.analysisResults.clear(); - TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow(_api.bufferLine.size()) }; + TextAnalysisSource analysisSource{ _p.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow(_api.bufferLine.size()) }; TextAnalysisSink analysisSink{ _api.analysisResults }; THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink)); @@ -852,7 +932,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len /* isSideways */ false, /* isRightToLeft */ 0, /* scriptAnalysis */ &a.analysis, - /* localeName */ _api.userLocaleName.c_str(), + /* localeName */ _p.userLocaleName.c_str(), /* numberSubstitution */ nullptr, /* features */ &features, /* featureRangeLengths */ &featureRangeLengths, @@ -904,7 +984,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len /* isSideways */ false, /* isRightToLeft */ 0, /* scriptAnalysis */ &a.analysis, - /* localeName */ _api.userLocaleName.c_str(), + /* localeName */ _p.userLocaleName.c_str(), /* features */ &features, /* featureRangeLengths */ &featureRangeLengths, /* featureRanges */ featureRanges, @@ -979,53 +1059,31 @@ void AtlasEngine::_mapReplacementCharacter(u32 from, u32 to, ShapedRow& row) return; } - auto pos1 = from; - auto pos2 = pos1; - size_t col1 = _api.bufferLineColumn[from]; - size_t col2 = col1; + auto pos = from; + auto col1 = _api.bufferLineColumn[from]; auto initialIndicesCount = row.glyphIndices.size(); - const auto softFontAvailable = !_p.s->font->softFontPattern.empty(); - auto currentlyMappingSoftFont = isSoftFontChar(_api.bufferLine[pos1]); const auto shift = gsl::narrow_cast(row.lineRendition != LineRendition::SingleWidth); const auto colors = _p.foregroundBitmap.begin() + _p.colorBitmapRowStride * _api.lastPaintBufferLineCoord.y; - while (pos2 < to) + while (pos < to) { - col2 = _api.bufferLineColumn[++pos2]; + const auto col2 = _api.bufferLineColumn[++pos]; if (col1 == col2) { continue; } - const auto cols = col2 - col1; - const auto ch = static_cast(_api.bufferLine[pos1]); - const auto nowMappingSoftFont = isSoftFontChar(ch); - - row.glyphIndices.emplace_back(nowMappingSoftFont ? ch : _api.replacementCharacterGlyphIndex); - row.glyphAdvances.emplace_back(static_cast(cols * _p.s->font->cellSize.x)); + row.glyphIndices.emplace_back(_api.replacementCharacterGlyphIndex); + row.glyphAdvances.emplace_back(static_cast((col2 - col1) * _p.s->font->cellSize.x)); row.glyphOffsets.emplace_back(); - row.colors.emplace_back(colors[col1 << shift]); - - if (currentlyMappingSoftFont != nowMappingSoftFont) - { - const auto indicesCount = row.glyphIndices.size(); - const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get(); - - if (indicesCount > initialIndicesCount) - { - row.mappings.emplace_back(fontFace, gsl::narrow_cast(initialIndicesCount), gsl::narrow_cast(indicesCount)); - initialIndicesCount = indicesCount; - } - } + row.colors.emplace_back(colors[static_cast(col1) << shift]); - pos1 = pos2; col1 = col2; - currentlyMappingSoftFont = nowMappingSoftFont; } { const auto indicesCount = row.glyphIndices.size(); - const auto fontFace = currentlyMappingSoftFont && softFontAvailable ? nullptr : _api.replacementCharacterFontFace.get(); + const auto fontFace = _api.replacementCharacterFontFace.get(); if (indicesCount > initialIndicesCount) { diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 8d6d76795c0..a32fa4dc2fc 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -86,6 +86,8 @@ namespace Microsoft::Console::Render::Atlas void _recreateFontDependentResources(); void _recreateCellCountDependentResources(); void _flushBufferLine(); + void _mapRegularText(size_t offBeg, size_t offEnd); + void _mapBuiltinGlyphs(size_t offBeg, size_t offEnd); void _mapCharacters(const wchar_t* text, u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const; void _mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 length, ShapedRow& row); ATLAS_ATTR_COLD void _mapReplacementCharacter(u32 from, u32 to, ShapedRow& row); @@ -117,6 +119,18 @@ namespace Microsoft::Console::Render::Atlas std::unique_ptr _b; RenderingPayload _p; + // _p.s->font->builtinGlyphs is the setting which decides whether we should map box drawing glyphs to + // our own builtin versions. There's just one problem: BackendD2D doesn't have this functionality. + // But since AtlasEngine shapes the text before it's handed to the backends, it would need to know + // whether BackendD2D is in use, before BackendD2D even exists. These two flags solve the issue + // by triggering a complete, immediate redraw whenever the backend type changes. + // + // The proper solution is to move text shaping into the backends. + // Someone just needs to write a generic "TextBuffer to DWRITE_GLYPH_RUN" function. + bool _hackIsBackendD2D = false; + bool _hackWantsBuiltinGlyphs = true; + bool _hackTriggerRedrawAll = false; + struct ApiState { GenerationalSettings s = DirtyGenerationalSettings(); @@ -133,8 +147,6 @@ namespace Microsoft::Console::Render::Atlas std::vector bufferLine; std::vector bufferLineColumn; - std::wstring userLocaleName; - std::array, 4> textFormatAxes; std::vector analysisResults; Buffer clusterMap; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 92649553245..0c546a8dceb 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -32,7 +32,7 @@ using namespace Microsoft::Console::Render::Atlas; [[nodiscard]] HRESULT AtlasEngine::Present() noexcept try { - if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent()) + if (!_p.dxgi.adapter) { _recreateAdapter(); } @@ -77,7 +77,7 @@ CATCH_RETURN() [[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept { - return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()); + return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()) || _hackTriggerRedrawAll; } void AtlasEngine::WaitUntilCanRender() noexcept @@ -276,12 +276,14 @@ void AtlasEngine::_recreateBackend() _b = std::make_unique(_p); } - // !!! NOTE !!! - // Normally the viewport is indirectly marked as dirty by `AtlasEngine::_handleSettingsUpdate()` whenever - // the settings change, but the `!_p.dxgi.factory->IsCurrent()` check is not part of the settings change - // flow and so we have to manually recreate how AtlasEngine.cpp marks viewports as dirty here. - // This ensures that the backends redraw their entire viewports whenever a new swap chain is created. + // This ensures that the backends redraw their entire viewports whenever a new swap chain is created, + // EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.). _p.MarkAllAsDirty(); + + const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode; + _hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs; + _hackIsBackendD2D = d2dMode; + _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; } void AtlasEngine::_handleSwapChainUpdate() @@ -319,13 +321,15 @@ void AtlasEngine::_createSwapChain() // 3 buffers seems to guarantee a stable framerate at display frequency at all times. .BufferCount = 3, .Scaling = DXGI_SCALING_NONE, - // DXGI_SWAP_EFFECT_FLIP_DISCARD is a mode that was created at a time were display drivers - // lacked support for Multiplane Overlays (MPO) and were copying buffers was expensive. + // DXGI_SWAP_EFFECT_FLIP_DISCARD is the easiest to use, because it's fast and uses little memory. + // But it's a mode that was created at a time were display drivers lacked support + // for Multiplane Overlays (MPO) and were copying buffers was expensive. // This allowed DWM to quickly draw overlays (like gamebars) on top of rendered content. // With faster GPU memory in general and with support for MPO in particular this isn't // really an advantage anymore. Instead DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL allows for a // more "intelligent" composition and display updates to occur like Panel Self Refresh // (PSR) which requires dirty rectangles (Present1 API) to work correctly. + // We were asked by DWM folks to use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL for this reason (PSR). .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, // If our background is opaque we can enable "independent" flips by setting DXGI_ALPHA_MODE_IGNORE. // As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically. diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index 0ca325a5383..9f2736eceef 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -74,11 +74,6 @@ namespace Microsoft::Console::Render::Atlas return val < min ? min : (max < val ? max : val); } - constexpr bool isSoftFontChar(wchar_t ch) noexcept - { - return ch >= 0xEF20 && ch < 0xEF80; - } - inline constexpr D2D1_RECT_F GlyphRunEmptyBounds{ 1e38f, 1e38f, -1e38f, -1e38f }; void GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 221cf3b5ba3..dd4dcbf0c22 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -4,11 +4,14 @@ #include "pch.h" #include "BackendD3D.h" +#include + #include #include #include #include +#include "BuiltinGlyphs.h" #include "dwrite.h" #include "../../types/inc/ColorFix.hpp" @@ -42,45 +45,8 @@ TIL_FAST_MATH_BEGIN using namespace Microsoft::Console::Render::Atlas; -template<> -struct std::hash -{ - constexpr size_t operator()(u16 key) const noexcept - { - return til::flat_set_hash_integer(key); - } -}; - -template<> -struct std::hash -{ - constexpr size_t operator()(u16 key) const noexcept - { - return til::flat_set_hash_integer(key); - } - - constexpr size_t operator()(const BackendD3D::AtlasGlyphEntry& slot) const noexcept - { - return til::flat_set_hash_integer(slot.glyphIndex); - } -}; - -template<> -struct std::hash -{ - using T = BackendD3D::AtlasFontFaceEntry; - - size_t operator()(const BackendD3D::AtlasFontFaceKey& key) const noexcept - { - return til::flat_set_hash_integer(std::bit_cast(key.fontFace) | static_cast(key.lineRendition)); - } - - size_t operator()(const BackendD3D::AtlasFontFaceEntry& slot) const noexcept - { - const auto& inner = *slot.inner; - return til::flat_set_hash_integer(std::bit_cast(inner.fontFace.get()) | static_cast(inner.lineRendition)); - } -}; +static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 }; +static constexpr D2D1_COLOR_F whiteColor{ 1, 1, 1, 1 }; BackendD3D::BackendD3D(const RenderingPayload& p) { @@ -329,7 +295,11 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) _fontChangedResetGlyphAtlas = true; _textShadingType = font.antialiasingMode == AntialiasingMode::ClearType ? ShadingType::TextClearType : ShadingType::TextGrayscale; + // _ligatureOverhangTriggerLeft/Right are essentially thresholds for a glyph's width at + // which point we consider it wider than allowed and "this looks like a coding ligature". + // See _drawTextOverlapSplit for more information about what this does. { + // No ligatures -> No thresholds. auto ligaturesDisabled = false; for (const auto& feature : font.fontFeatures) { @@ -752,11 +722,15 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p) // It's not great, but it's not terrible. for (auto& slot : _glyphAtlasMap.container()) { - if (slot.inner) + for (auto& glyphs : slot.glyphs) { - slot.inner->glyphs.clear(); + glyphs.clear(); } } + for (auto& glyphs : _builtinGlyphs.glyphs) + { + glyphs.clear(); + } _d2dBeginDrawing(); _d2dRenderTarget->Clear(); @@ -797,9 +771,6 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const _d2dRenderTarget.try_query_to(_d2dRenderTarget4.addressof()); _d2dRenderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS); - // We don't really use D2D for anything except DWrite, but it - // can't hurt to ensure that everything it does is pixel aligned. - _d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); // Ensure that D2D uses the exact same gamma as our shader uses. _d2dRenderTarget->SetTextRenderingParams(_textRenderingParams.get()); @@ -821,9 +792,8 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const } { - static constexpr D2D1_COLOR_F color{ 1, 1, 1, 1 }; - THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _emojiBrush.put())); - THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&color, nullptr, _brush.put())); + THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _emojiBrush.put())); + THROW_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(&whiteColor, nullptr, _brush.put())); } ID3D11ShaderResourceView* resources[]{ _backgroundBitmapView.get(), _glyphAtlasView.get() }; @@ -955,7 +925,7 @@ void BackendD3D::_drawBackground(const RenderingPayload& p) } _appendQuad() = { - .shadingType = ShadingType::Background, + .shadingType = static_cast(ShadingType::Background), .size = p.s->targetSize, }; } @@ -1013,65 +983,65 @@ void BackendD3D::_drawText(RenderingPayload& p) for (const auto& m : row->mappings) { auto x = m.glyphsFrom; - const AtlasFontFaceKey fontFaceKey{ - .fontFace = m.fontFace.get(), - .lineRendition = row->lineRendition, - }; + const auto glyphsTo = m.glyphsTo; + const auto fontFace = m.fontFace.get(); - // This goto label exists to allow us to retry rendering a glyph if the glyph atlas was full. - // We need to goto here, because a retry will cause the atlas texture as well as the - // _glyphCache hashmap to be cleared, and so we'll have to call insert() again. - drawGlyphRetry: - const auto [fontFaceEntryOuter, fontFaceInserted] = _glyphAtlasMap.insert(fontFaceKey); - auto& fontFaceEntry = *fontFaceEntryOuter.inner; - - if (fontFaceInserted) + // The lack of a fontFace indicates a soft font. + AtlasFontFaceEntry* fontFaceEntry = &_builtinGlyphs; + if (fontFace) [[likely]] { - _initializeFontFaceEntry(fontFaceEntry); + fontFaceEntry = _glyphAtlasMap.insert(fontFace).first; } - while (x < m.glyphsTo) + const auto& glyphs = fontFaceEntry->glyphs[WI_EnumValue(row->lineRendition)]; + + while (x < glyphsTo) { - const auto [glyphEntry, inserted] = fontFaceEntry.glyphs.insert(row->glyphIndices[x]); + size_t dx = 1; + u32 glyphIndex = row->glyphIndices[x]; - if (inserted && !_drawGlyph(p, fontFaceEntry, glyphEntry)) + // Note: !fontFace is only nullptr for builtin glyphs which then use glyphIndices for UTF16 code points. + // In other words, this doesn't accidentally corrupt any actual glyph indices. + if (!fontFace && til::is_leading_surrogate(glyphIndex)) { - // A deadlock in this retry loop is detected in _drawGlyphPrepareRetry. - // - // Yes, I agree, avoid goto. Sometimes. It's not my fault that C++ still doesn't - // have a `continue outerloop;` like other languages had it for decades. :( -#pragma warning(suppress : 26438) // Avoid 'goto' (es.76). -#pragma warning(suppress : 26448) // Consider using gsl::finally if final action is intended (gsl.util). - goto drawGlyphRetry; + glyphIndex = til::combine_surrogates(glyphIndex, row->glyphIndices[x + 1]); + dx = 2; } - if (glyphEntry.data.shadingType != ShadingType::Default) + auto glyphEntry = glyphs.lookup(glyphIndex); + if (!glyphEntry) + { + glyphEntry = _drawGlyph(p, *row, *fontFaceEntry, glyphIndex); + } + + // A shadingType of 0 (ShadingType::Default) indicates a glyph that is whitespace. + if (glyphEntry->shadingType != ShadingType::Default) { auto l = static_cast(lrintf((baselineX + row->glyphOffsets[x].advanceOffset) * scaleX)); auto t = static_cast(lrintf((baselineY - row->glyphOffsets[x].ascenderOffset) * scaleY)); - l += glyphEntry.data.offset.x; - t += glyphEntry.data.offset.y; + l += glyphEntry->offset.x; + t += glyphEntry->offset.y; row->dirtyTop = std::min(row->dirtyTop, t); - row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry.data.size.y); + row->dirtyBottom = std::max(row->dirtyBottom, t + glyphEntry->size.y); _appendQuad() = { - .shadingType = glyphEntry.data.shadingType, + .shadingType = static_cast(glyphEntry->shadingType), .position = { static_cast(l), static_cast(t) }, - .size = glyphEntry.data.size, - .texcoord = glyphEntry.data.texcoord, + .size = glyphEntry->size, + .texcoord = glyphEntry->texcoord, .color = row->colors[x], }; - if (glyphEntry.data.overlapSplit) + if (glyphEntry->overlapSplit) { _drawTextOverlapSplit(p, y); } } baselineX += row->glyphAdvances[x]; - ++x; + x += dx; } } @@ -1189,71 +1159,22 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y) } } -void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry) -{ - if (!fontFaceEntry.fontFace) - { - return; - } - - ALLOW_UNINITIALIZED_BEGIN - std::array codepoints; - std::array indices; - ALLOW_UNINITIALIZED_END - - for (u32 i = 0; i < codepoints.size(); ++i) - { - codepoints[i] = 0x2500 + i; - } - - THROW_IF_FAILED(fontFaceEntry.fontFace->GetGlyphIndicesW(codepoints.data(), codepoints.size(), indices.data())); - - for (u32 i = 0; i < indices.size(); ++i) - { - if (const auto idx = indices[i]) - { - fontFaceEntry.boxGlyphs.insert(idx); - } - } -} - -bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { + // The lack of a fontFace indicates a soft font. if (!fontFaceEntry.fontFace) { - return _drawSoftFontGlyph(p, fontFaceEntry, glyphEntry); + return _drawBuiltinGlyph(p, row, fontFaceEntry, glyphIndex); } + const auto glyphIndexU16 = static_cast(glyphIndex); const DWRITE_GLYPH_RUN glyphRun{ .fontFace = fontFaceEntry.fontFace.get(), .fontEmSize = p.s->font->fontSize, .glyphCount = 1, - .glyphIndices = &glyphEntry.glyphIndex, + .glyphIndices = &glyphIndexU16, }; - // To debug issues with this function it may be helpful to know which file - // a given font face corresponds to. This code works for most cases. -#if 0 - wchar_t filePath[MAX_PATH]{}; - { - UINT32 fileCount = 1; - wil::com_ptr file; - if (SUCCEEDED(fontFaceEntry.fontFace->GetFiles(&fileCount, file.addressof()))) - { - wil::com_ptr loader; - THROW_IF_FAILED(file->GetLoader(loader.addressof())); - - if (const auto localLoader = loader.try_query()) - { - void const* fontFileReferenceKey; - UINT32 fontFileReferenceKeySize; - THROW_IF_FAILED(file->GetReferenceKey(&fontFileReferenceKey, &fontFileReferenceKeySize)); - THROW_IF_FAILED(localLoader->GetFilePathFromKey(fontFileReferenceKey, fontFileReferenceKeySize, &filePath[0], MAX_PATH)); - } - } - } -#endif - // It took me a while to figure out how to rasterize glyphs manually with DirectWrite without depending on Direct2D. // The benefits are a reduction in memory usage, an increase in performance under certain circumstances and most // importantly, the ability to debug the renderer more easily, because many graphics debuggers don't support Direct2D. @@ -1322,16 +1243,13 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI // The buffer now contains a grayscale alpha mask. #endif - const auto lineRendition = static_cast(fontFaceEntry.lineRendition); - const auto needsTransform = lineRendition != LineRendition::SingleWidth; - - static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 }; + const int scale = row.lineRendition != LineRendition::SingleWidth; D2D1_MATRIX_3X2_F transform = identityTransform; - if (needsTransform) + if (scale) { transform.m11 = 2.0f; - transform.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f; + transform.m22 = row.lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f; _d2dRenderTarget->SetTransform(&transform); } @@ -1387,26 +1305,10 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI } } - // Overhangs for box glyphs can produce unsightly effects, where the antialiased edges of horizontal - // and vertical lines overlap between neighboring glyphs and produce "boldened" intersections. - // It looks a little something like this: - // ---+---+--- - // This avoids the issue in most cases by simply clipping the glyph to the size of a single cell. - // The downside is that it fails to work well for custom line heights, etc. - const auto isBoxGlyph = fontFaceEntry.boxGlyphs.lookup(glyphEntry.glyphIndex) != nullptr; - if (isBoxGlyph) - { - // NOTE: As mentioned above, the "origin" of a glyph's coordinate system is its baseline. - bounds.left = std::max(bounds.left, 0.0f); - bounds.top = std::max(bounds.top, static_cast(-p.s->font->baseline) * transform.m22); - bounds.right = std::min(bounds.right, static_cast(p.s->font->cellSize.x) * transform.m11); - bounds.bottom = std::min(bounds.bottom, static_cast(p.s->font->descender) * transform.m22); - } - // The bounds may be empty if the glyph is whitespace. if (bounds.left >= bounds.right || bounds.top >= bounds.bottom) { - return true; + return _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); } const auto bl = lrintf(bounds.left); @@ -1418,37 +1320,15 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI .w = br - bl, .h = bb - bt, }; - if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) - { - _drawGlyphPrepareRetry(p); - return false; - } + _drawGlyphAtlasAllocate(p, rect); + _d2dBeginDrawing(); const D2D1_POINT_2F baselineOrigin{ static_cast(rect.x - bl), static_cast(rect.y - bt), }; - _d2dBeginDrawing(); - - if (isBoxGlyph) - { - const D2D1_RECT_F clipRect{ - static_cast(rect.x) / transform.m11, - static_cast(rect.y) / transform.m22, - static_cast(rect.x + rect.w) / transform.m11, - static_cast(rect.y + rect.h) / transform.m22, - }; - _d2dRenderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED); - } - const auto boxGlyphCleanup = wil::scope_exit([&]() { - if (isBoxGlyph) - { - _d2dRenderTarget->PopAxisAlignedClip(); - } - }); - - if (needsTransform) + if (scale) { transform.dx = (1.0f - transform.m11) * baselineOrigin.x; transform.dy = (1.0f - transform.m22) * baselineOrigin.y; @@ -1474,56 +1354,85 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryI // // The former condition makes sure to exclude diacritics and such from being considered a ligature, // while the latter condition-pair makes sure to exclude regular BMP wide glyphs that overlap a little. - const auto horizontalScale = lineRendition != LineRendition::SingleWidth ? 2 : 1; - const auto triggerLeft = _ligatureOverhangTriggerLeft * horizontalScale; - const auto triggerRight = _ligatureOverhangTriggerRight * horizontalScale; + const auto triggerLeft = _ligatureOverhangTriggerLeft << scale; + const auto triggerRight = _ligatureOverhangTriggerRight << scale; const auto overlapSplit = rect.w >= p.s->font->cellSize.x && (bl <= triggerLeft || br >= triggerRight); - glyphEntry.data.shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; - glyphEntry.data.overlapSplit = overlapSplit; - glyphEntry.data.offset.x = bl; - glyphEntry.data.offset.y = bt; - glyphEntry.data.size.x = rect.w; - glyphEntry.data.size.y = rect.h; - glyphEntry.data.texcoord.x = rect.x; - glyphEntry.data.texcoord.y = rect.y; + const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); + glyphEntry->shadingType = isColorGlyph ? ShadingType::TextPassthrough : _textShadingType; + glyphEntry->overlapSplit = overlapSplit; + glyphEntry->offset.x = bl; + glyphEntry->offset.y = bt; + glyphEntry->size.x = rect.w; + glyphEntry->size.y = rect.h; + glyphEntry->texcoord.x = rect.x; + glyphEntry->texcoord.y = rect.y; - if (lineRendition >= LineRendition::DoubleHeightTop) + if (row.lineRendition >= LineRendition::DoubleHeightTop) { - _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry); + _splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry); } - return true; + return glyphEntry; } -bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { + auto baseline = p.s->font->baseline; stbrp_rect rect{ .w = p.s->font->cellSize.x, .h = p.s->font->cellSize.y, }; - - const auto lineRendition = static_cast(fontFaceEntry.lineRendition); - auto baseline = p.s->font->baseline; - - if (lineRendition != LineRendition::SingleWidth) + if (row.lineRendition != LineRendition::SingleWidth) { - const auto heightShift = static_cast(lineRendition >= LineRendition::DoubleHeightTop); + const auto heightShift = static_cast(row.lineRendition >= LineRendition::DoubleHeightTop); rect.w <<= 1; rect.h <<= heightShift; baseline <<= heightShift; } - if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) + _drawGlyphAtlasAllocate(p, rect); + _d2dBeginDrawing(); + + if (BuiltinGlyphs::IsSoftFontChar(glyphIndex)) { - _drawGlyphPrepareRetry(p); - return false; + _drawSoftFontGlyph(p, rect, glyphIndex); + } + else + { + const D2D1_RECT_F r{ + static_cast(rect.x), + static_cast(rect.y), + static_cast(rect.x + rect.w), + static_cast(rect.y + rect.h), + }; + BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); + } + + const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); + glyphEntry->shadingType = ShadingType::TextGrayscale; + glyphEntry->overlapSplit = 0; + glyphEntry->offset.x = 0; + glyphEntry->offset.y = -baseline; + glyphEntry->size.x = rect.w; + glyphEntry->size.y = rect.h; + glyphEntry->texcoord.x = rect.x; + glyphEntry->texcoord.y = rect.y; + + if (row.lineRendition >= LineRendition::DoubleHeightTop) + { + _splitDoubleHeightGlyph(p, row, fontFaceEntry, glyphEntry); } + return glyphEntry; +} + +void BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const stbrp_rect& rect, u32 glyphIndex) +{ if (!_softFontBitmap) { // Allocating such a tiny texture is very wasteful (min. texture size on GPUs - // right now is 64kB), but this is a seldom used feature so it's fine... + // right now is 64kB), but this is a seldomly used feature so it's fine... const D2D1_SIZE_U size{ static_cast(p.s->font->softFontCellSize.width), static_cast(p.s->font->softFontCellSize.height), @@ -1536,6 +1445,30 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa THROW_IF_FAILED(_d2dRenderTarget->CreateBitmap(size, nullptr, 0, &bitmapProperties, _softFontBitmap.addressof())); } + { + const auto width = static_cast(p.s->font->softFontCellSize.width); + const auto height = static_cast(p.s->font->softFontCellSize.height); + + auto bitmapData = Buffer{ width * height }; + const auto softFontIndex = glyphIndex - 0xEF20u; + auto src = p.s->font->softFontPattern.begin() + height * softFontIndex; + auto dst = bitmapData.begin(); + + for (size_t y = 0; y < height; y++) + { + auto srcBits = *src++; + for (size_t x = 0; x < width; x++) + { + const auto srcBitIsSet = (srcBits & 0x8000) != 0; + *dst++ = srcBitIsSet ? 0xffffffff : 0x00000000; + srcBits <<= 1; + } + } + + const auto pitch = static_cast(width * sizeof(u32)); + THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch)); + } + const auto interpolation = p.s->font->antialiasingMode == AntialiasingMode::Aliased ? D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR : D2D1_INTERPOLATION_MODE_HIGH_QUALITY_CUBIC; const D2D1_RECT_F dest{ static_cast(rect.x), @@ -1545,118 +1478,67 @@ bool BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFa }; _d2dBeginDrawing(); - _drawSoftFontGlyphInBitmap(p, glyphEntry); _d2dRenderTarget->DrawBitmap(_softFontBitmap.get(), &dest, 1, interpolation, nullptr, nullptr); - - glyphEntry.data.shadingType = ShadingType::TextGrayscale; - glyphEntry.data.overlapSplit = 0; - glyphEntry.data.offset.x = 0; - glyphEntry.data.offset.y = -baseline; - glyphEntry.data.size.x = rect.w; - glyphEntry.data.size.y = rect.h; - glyphEntry.data.texcoord.x = rect.x; - glyphEntry.data.texcoord.y = rect.y; - - if (lineRendition >= LineRendition::DoubleHeightTop) - { - _splitDoubleHeightGlyph(p, fontFaceEntry, glyphEntry); - } - - return true; } -void BackendD3D::_drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const +void BackendD3D::_drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect) { - if (!isSoftFontChar(glyphEntry.glyphIndex)) - { - // AtlasEngine::_mapReplacementCharacter should have filtered inputs with isSoftFontChar already. - assert(false); - return; - } - - const auto width = static_cast(p.s->font->softFontCellSize.width); - const auto height = static_cast(p.s->font->softFontCellSize.height); - const auto& softFontPattern = p.s->font->softFontPattern; - - // The isSoftFontChar() range is [0xEF20,0xEF80). - const auto offset = glyphEntry.glyphIndex - 0xEF20u; - const auto offsetBeg = offset * height; - const auto offsetEnd = offsetBeg + height; - - if (offsetEnd > softFontPattern.size()) + if (stbrp_pack_rects(&_rectPacker, &rect, 1)) { - // Out of range values should not occur, but they do and it's unknown why: GH#15553 - assert(false); return; } - Buffer bitmapData{ width * height }; - auto dst = bitmapData.begin(); - auto it = softFontPattern.begin() + offsetBeg; - const auto end = softFontPattern.begin() + offsetEnd; + _d2dEndDrawing(); + _flushQuads(p); + _resetGlyphAtlas(p); - while (it != end) + if (!stbrp_pack_rects(&_rectPacker, &rect, 1)) { - auto srcBits = *it++; - for (size_t x = 0; x < width; x++) - { - const auto srcBitIsSet = (srcBits & 0x8000) != 0; - *dst++ = srcBitIsSet ? 0xffffffff : 0x00000000; - srcBits <<= 1; - } + THROW_HR(HRESULT_FROM_WIN32(ERROR_POSSIBLE_DEADLOCK)); } - - const auto pitch = static_cast(width * sizeof(u32)); - THROW_IF_FAILED(_softFontBitmap->CopyFromMemory(nullptr, bitmapData.data(), pitch)); } -void BackendD3D::_drawGlyphPrepareRetry(const RenderingPayload& p) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { - THROW_HR_IF_MSG(E_UNEXPECTED, _glyphAtlasMap.empty(), "BackendD3D::_drawGlyph deadlock"); - _d2dEndDrawing(); - _flushQuads(p); - _resetGlyphAtlas(p); + const auto glyphEntry = fontFaceEntry.glyphs[WI_EnumValue(row.lineRendition)].insert(glyphIndex).first; + glyphEntry->shadingType = ShadingType::Default; + return glyphEntry; } // If this is a double-height glyph (DECDHL), we need to split it into 2 glyph entries: // One for the top/bottom half each, because that's how DECDHL works. This will clip the // `glyphEntry` to only contain the one specified by `fontFaceEntry.lineRendition` // and create a second entry in our glyph cache hashmap that contains the other half. -void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry) +void BackendD3D::_splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry) { // Twice the line height, twice the descender gap. For both. - glyphEntry.data.offset.y -= p.s->font->descender; + glyphEntry->offset.y -= p.s->font->descender; - const auto isTop = fontFaceEntry.lineRendition == LineRendition::DoubleHeightTop; - - const AtlasFontFaceKey key2{ - .fontFace = fontFaceEntry.fontFace.get(), - .lineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop, - }; + const auto isTop = row.lineRendition == LineRendition::DoubleHeightTop; + const auto otherLineRendition = isTop ? LineRendition::DoubleHeightBottom : LineRendition::DoubleHeightTop; + const auto entry2 = fontFaceEntry.glyphs[WI_EnumValue(otherLineRendition)].insert(glyphEntry->glyphIndex).first; - auto& glyphCache = _glyphAtlasMap.insert(key2).first.inner->glyphs; - auto& entry2 = glyphCache.insert(glyphEntry.glyphIndex).first; - entry2.data = glyphEntry.data; + *entry2 = *glyphEntry; - auto& top = isTop ? glyphEntry : entry2; - auto& bottom = isTop ? entry2 : glyphEntry; + const auto top = isTop ? glyphEntry : entry2; + const auto bottom = isTop ? entry2 : glyphEntry; + const auto topSize = clamp(-glyphEntry->offset.y - p.s->font->baseline, 0, static_cast(glyphEntry->size.y)); - const auto topSize = clamp(-glyphEntry.data.offset.y - p.s->font->baseline, 0, static_cast(glyphEntry.data.size.y)); - top.data.offset.y += p.s->font->cellSize.y; - top.data.size.y = topSize; - bottom.data.offset.y += topSize; - bottom.data.size.y = std::max(0, bottom.data.size.y - topSize); - bottom.data.texcoord.y += topSize; + top->offset.y += p.s->font->cellSize.y; + top->size.y = topSize; + bottom->offset.y += topSize; + bottom->size.y = std::max(0, bottom->size.y - topSize); + bottom->texcoord.y += topSize; // Things like diacritics might be so small that they only exist on either half of the // double-height row. This effectively turns the other (unneeded) side into whitespace. - if (!top.data.size.y) + if (!top->size.y) { - top.data.shadingType = ShadingType::Default; + top->shadingType = ShadingType::Default; } - if (!bottom.data.size.y) + if (!bottom->size.y) { - bottom.data.shadingType = ShadingType::Default; + bottom->shadingType = ShadingType::Default; } } @@ -1691,7 +1573,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) for (; posX < end; posX += textCellWidth) { _appendQuad() = { - .shadingType = ShadingType::SolidLine, + .shadingType = static_cast(ShadingType::SolidLine), .position = { static_cast(posX), rowTop }, .size = { width, p.s->font->cellSize.y }, .color = r.gridlineColor, @@ -1713,7 +1595,7 @@ void BackendD3D::_drawGridlines(const RenderingPayload& p, u16 y) if (rt < rb) { _appendQuad() = { - .shadingType = shadingType, + .shadingType = static_cast(shadingType), .renditionScale = { static_cast(1 << horizontalShift), static_cast(1 << verticalShift) }, .position = { left, static_cast(rt) }, .size = { width, static_cast(rb - rt) }, @@ -1803,8 +1685,8 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p) } const i16x2 position{ - p.s->font->cellSize.x * x0, - p.s->font->cellSize.y * p.cursorRect.top, + static_cast(p.s->font->cellSize.x * x0), + static_cast(p.s->font->cellSize.y * p.cursorRect.top), }; const u16x2 size{ static_cast(p.s->font->cellSize.x * (x1 - x0)), @@ -1887,7 +1769,7 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p) for (const auto& c : _cursorRects) { _appendQuad() = { - .shadingType = ShadingType::Cursor, + .shadingType = static_cast(ShadingType::Cursor), .position = c.position, .size = c.size, .color = c.background, @@ -1911,7 +1793,7 @@ void BackendD3D::_drawCursorForeground() // start and end of this "block" here in advance. for (; instancesOffset < instancesCount; ++instancesOffset) { - const auto shadingType = _instances[instancesOffset].shadingType; + const auto shadingType = static_cast(_instances[instancesOffset].shadingType); if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast) { break; @@ -1931,7 +1813,7 @@ void BackendD3D::_drawCursorForeground() // Now do the same thing as above, but backwards from the end. for (; instancesCount > instancesOffset; --instancesCount) { - const auto shadingType = _instances[instancesCount - 1].shadingType; + const auto shadingType = static_cast(_instances[instancesCount - 1].shadingType); if (shadingType >= ShadingType::TextDrawingFirst && shadingType <= ShadingType::TextDrawingLast) { break; @@ -1990,7 +1872,7 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t off // There's one special exception to the rule: Emojis. We currently don't really support inverting // (or reversing) colored glyphs like that, so we can return early here and avoid cutting them up. // It'd be too expensive to check for these rare glyph types inside the _drawCursorForeground() loop. - if (it.shadingType == ShadingType::TextPassthrough) + if (static_cast(it.shadingType) == ShadingType::TextPassthrough) { return 0; } @@ -2121,14 +2003,14 @@ void BackendD3D::_drawSelection(const RenderingPayload& p) else { _appendQuad() = { - .shadingType = ShadingType::Selection, + .shadingType = static_cast(ShadingType::Selection), .position = { - p.s->font->cellSize.x * row->selectionFrom, - p.s->font->cellSize.y * y, + static_cast(p.s->font->cellSize.x * row->selectionFrom), + static_cast(p.s->font->cellSize.y * y), }, .size = { static_cast(p.s->font->cellSize.x * (row->selectionTo - row->selectionFrom)), - p.s->font->cellSize.y, + static_cast(p.s->font->cellSize.y), }, .color = p.s->misc->selectionColor, }; diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index cdf9937cc61..3fddb161d49 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -57,7 +57,7 @@ namespace Microsoft::Console::Render::Atlas #pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier }; - enum class ShadingType : u16 + enum class ShadingType : u8 // u8 so that it packs perfectly into AtlasGlyphEntry { Default = 0, Background = 0, @@ -89,7 +89,7 @@ namespace Microsoft::Console::Render::Atlas // impact on performance and power draw. If (when?) displays with >32k resolution make their // appearance in the future, this should be changed to f32x2. But if you do so, please change // all other occurrences of i16x2 positions/offsets throughout the class to keep it consistent. - alignas(u16) ShadingType shadingType; + alignas(u16) u16 shadingType; // not using ShadingType here to avoid struct padding which causes optimization failures with MSVC alignas(u16) u8x2 renditionScale; alignas(u32) i16x2 position; alignas(u32) u16x2 size; @@ -97,8 +97,12 @@ namespace Microsoft::Console::Render::Atlas alignas(u32) u32 color; }; - struct alignas(u32) AtlasGlyphEntryData + // NOTE: Don't initialize any members in this struct. This ensures that no + // zero-initialization needs to occur when we allocate large buffers of this object. + struct AtlasGlyphEntry { + u32 glyphIndex; + u8 occupied; ShadingType shadingType; u16 overlapSplit; i16x2 offset; @@ -106,80 +110,71 @@ namespace Microsoft::Console::Render::Atlas u16x2 texcoord; }; - // NOTE: Don't initialize any members in this struct. This ensures that no - // zero-initialization needs to occur when we allocate large buffers of this object. - struct AtlasGlyphEntry + struct AtlasGlyphEntryHashTrait { - u16 glyphIndex; - // All data in QuadInstance is u32-aligned anyways, so this simultaneously serves as padding. - u16 _occupied; - - AtlasGlyphEntryData data; + static constexpr bool occupied(const AtlasGlyphEntry& entry) noexcept + { + return entry.occupied != 0; + } - constexpr bool operator==(u16 key) const noexcept + static constexpr size_t hash(const u16 glyphIndex) noexcept { - return glyphIndex == key; + return til::flat_set_hash_integer(glyphIndex); } - constexpr operator bool() const noexcept + static constexpr size_t hash(const AtlasGlyphEntry& entry) noexcept { - return _occupied != 0; + return til::flat_set_hash_integer(entry.glyphIndex); } - constexpr AtlasGlyphEntry& operator=(u16 key) noexcept + static constexpr bool equals(const AtlasGlyphEntry& entry, u16 glyphIndex) noexcept { - glyphIndex = key; - _occupied = 1; - return *this; + return entry.glyphIndex == glyphIndex; } - }; - // This exists so that we can look up a AtlasFontFaceEntry without AddRef()/Release()ing fontFace first. - struct AtlasFontFaceKey - { - IDWriteFontFace2* fontFace; - LineRendition lineRendition; + static constexpr void assign(AtlasGlyphEntry& entry, u16 glyphIndex) noexcept + { + entry.glyphIndex = glyphIndex; + entry.occupied = 1; + } }; - struct AtlasFontFaceEntryInner + struct AtlasFontFaceEntry { // BODGY: At the time of writing IDWriteFontFallback::MapCharacters returns the same IDWriteFontFace instance // for the same font face variant as long as someone is holding a reference to the instance (see ActiveFaceCache). // This allows us to hash the value of the pointer as if it was uniquely identifying the font face variant. wil::com_ptr fontFace; - LineRendition lineRendition = LineRendition::SingleWidth; - til::linear_flat_set glyphs; - // boxGlyphs gets an increased growth rate of 2^2 = 4x, because presumably fonts either contain very - // few or almost all of the box glyphs. This reduces the cost of _initializeFontFaceEntry quite a bit. - til::linear_flat_set boxGlyphs; + // The 4 entries map to the 4 corresponding LineRendition enum values. + til::linear_flat_set glyphs[4]; }; - struct AtlasFontFaceEntry + struct AtlasFontFaceEntryHashTrait { - // This being a heap allocated allows us to insert into `glyphs` in `_splitDoubleHeightGlyph` - // (which might resize the hashmap!), while the caller `_drawText` is holding onto `glyphs`. - // If it wasn't heap allocated, all pointers into `linear_flat_set` would be invalidated. - std::unique_ptr inner; + static bool occupied(const AtlasFontFaceEntry& entry) noexcept + { + return static_cast(entry.fontFace); + } + + static constexpr size_t hash(const IDWriteFontFace2* fontFace) noexcept + { + return til::flat_set_hash_integer(std::bit_cast(fontFace)); + } - bool operator==(const AtlasFontFaceKey& key) const noexcept + static size_t hash(const AtlasFontFaceEntry& entry) noexcept { - const auto& i = *inner; - return i.fontFace.get() == key.fontFace && i.lineRendition == key.lineRendition; + return hash(entry.fontFace.get()); } - operator bool() const noexcept + static bool equals(const AtlasFontFaceEntry& entry, const IDWriteFontFace2* fontFace) noexcept { - return static_cast(inner); + return entry.fontFace.get() == fontFace; } - AtlasFontFaceEntry& operator=(const AtlasFontFaceKey& key) + static void assign(AtlasFontFaceEntry& entry, IDWriteFontFace2* fontFace) noexcept { - inner = std::make_unique(); - auto& i = *inner; - i.fontFace = key.fontFace; - i.lineRendition = key.lineRendition; - return *this; + entry.fontFace = fontFace; } }; @@ -216,12 +211,12 @@ namespace Microsoft::Console::Render::Atlas void _uploadBackgroundBitmap(const RenderingPayload& p); void _drawText(RenderingPayload& p); ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y); - ATLAS_ATTR_COLD static void _initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry); - [[nodiscard]] ATLAS_ATTR_COLD bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); - bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); - void _drawSoftFontGlyphInBitmap(const RenderingPayload& p, const AtlasGlyphEntry& glyphEntry) const; - void _drawGlyphPrepareRetry(const RenderingPayload& p); - void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry); + [[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); + AtlasGlyphEntry* _drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); + void _drawSoftFontGlyph(const RenderingPayload& p, const stbrp_rect& rect, u32 glyphIndex); + void _drawGlyphAtlasAllocate(const RenderingPayload& p, stbrp_rect& rect); + static AtlasGlyphEntry* _drawGlyphAllocateEntry(const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); + static void _splitDoubleHeightGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, AtlasGlyphEntry* glyphEntry); void _drawGridlines(const RenderingPayload& p, u16 y); void _drawCursorBackground(const RenderingPayload& p); ATLAS_ATTR_COLD void _drawCursorForeground(); @@ -258,7 +253,8 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr _glyphAtlas; wil::com_ptr _glyphAtlasView; - til::linear_flat_set _glyphAtlasMap; + til::linear_flat_set _glyphAtlasMap; + AtlasFontFaceEntry _builtinGlyphs; Buffer _rectPackerData; stbrp_context _rectPacker{}; til::CoordType _ligatureOverhangTriggerLeft = 0; diff --git a/src/renderer/atlas/BuiltinGlyphs.cpp b/src/renderer/atlas/BuiltinGlyphs.cpp new file mode 100644 index 00000000000..541d5690324 --- /dev/null +++ b/src/renderer/atlas/BuiltinGlyphs.cpp @@ -0,0 +1,1276 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "BuiltinGlyphs.h" + +// Disable a bunch of warnings which get in the way of writing performant code. +#pragma warning(disable : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23). +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). +#pragma warning(disable : 26472) // Don't use a static_cast for arithmetic conversions. Use brace initialization, gsl::narrow_cast or gsl::narrow (type.1). +#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). +#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2). + +using namespace Microsoft::Console::Render::Atlas; +using namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs; + +union Instruction +{ + struct + { + u32 shape : 4; // Shape enum + u32 begX : 5; // Pos enum + u32 begY : 5; // Pos enum + u32 endX : 5; // Pos enum + u32 endY : 5; // Pos enum + }; + u32 value = 0; +}; +static_assert(sizeof(Instruction) == sizeof(u32)); + +static constexpr u32 InstructionsPerGlyph = 4; + +enum Shape : u32 +{ + Shape_Filled025, // axis aligned rectangle, 25% filled + Shape_Filled050, // axis aligned rectangle, 50% filled + Shape_Filled075, // axis aligned rectangle, 75% filled + Shape_Filled100, // axis aligned rectangle, 100% filled + Shape_LightLine, // 1/8th wide line + Shape_HeavyLine, // 1/4th wide line + Shape_EmptyRect, // axis aligned hollow rectangle + Shape_RoundRect, // axis aligned hollow, rounded rectangle + Shape_FilledEllipsis, // axis aligned, filled ellipsis + Shape_EmptyEllipsis, // axis aligned, hollow ellipsis + Shape_ClosedFilledPath, // filled path, the last segment connects to the first; set endX==Pos_Min to ignore + Shape_OpenLinePath, // regular line path; Pos_Min positions are ignored +}; + +// Pos indicates a fraction between 0 and 1 and is used as a UV coordinate with a cell. +// (0,0) is in the top-left corner. Some enum entries also contain a suffix. +// This suffix indicates an offset of that many times the line width, to be added to the position. +// This allows us to store 2 floats in just 5 bits and helps with keeping the Instruction tables compact. +enum Pos : u32 +{ + Pos_Min, + Pos_Max, + + Pos_0_1, + Pos_0_1_Add_0_5, + Pos_1_1, + Pos_1_1_Sub_0_5, + + Pos_1_2, + Pos_1_2_Sub_0_5, + Pos_1_2_Add_0_5, + Pos_1_2_Sub_1, + Pos_1_2_Add_1, + + Pos_1_4, + Pos_3_4, + + Pos_2_6, + Pos_3_6, + Pos_5_6, + + Pos_1_8, + Pos_3_8, + Pos_5_8, + Pos_7_8, + + Pos_2_9, + Pos_3_9, + Pos_5_9, + Pos_6_9, + Pos_8_9, + + Pos_2_12, + Pos_3_12, + Pos_5_12, + Pos_6_12, + Pos_8_12, + Pos_9_12, + Pos_11_12, +}; + +inline constexpr f32 Pos_Lut[][2] = { + /* Pos_Min */ { -0.5f, 0.0f }, + /* Pos_Max */ { 1.5f, 0.0f }, + + /* Pos_0_1 */ { 0.0f, 0.0f }, + /* Pos_0_1_Add_0_5 */ { 0.0f, 0.5f }, + /* Pos_1_1 */ { 1.0f, 0.0f }, + /* Pos_1_1_Sub_0_5 */ { 1.0f, -0.5f }, + + /* Pos_1_2 */ { 1.0f / 2.0f, 0.0f }, + /* Pos_1_2_Sub_0_5 */ { 1.0f / 2.0f, -0.5f }, + /* Pos_1_2_Add_0_5 */ { 1.0f / 2.0f, 0.5f }, + /* Pos_1_2_Sub_1 */ { 1.0f / 2.0f, -1.0f }, + /* Pos_1_2_Add_1 */ { 1.0f / 2.0f, 1.0f }, + + /* Pos_1_4 */ { 1.0f / 4.0f, 0.0f }, + /* Pos_3_4 */ { 3.0f / 4.0f, 0.0f }, + + /* Pos_2_6 */ { 2.0f / 6.0f, 0.0f }, + /* Pos_3_6 */ { 3.0f / 6.0f, 0.0f }, + /* Pos_5_6 */ { 5.0f / 6.0f, 0.0f }, + + /* Pos_1_8 */ { 1.0f / 8.0f, 0.0f }, + /* Pos_3_8 */ { 3.0f / 8.0f, 0.0f }, + /* Pos_5_8 */ { 5.0f / 8.0f, 0.0f }, + /* Pos_7_8 */ { 7.0f / 8.0f, 0.0f }, + + /* Pos_2_9 */ { 2.0f / 9.0f, 0.0f }, + /* Pos_3_9 */ { 3.0f / 9.0f, 0.0f }, + /* Pos_5_9 */ { 5.0f / 9.0f, 0.0f }, + /* Pos_6_9 */ { 6.0f / 9.0f, 0.0f }, + /* Pos_8_9 */ { 8.0f / 9.0f, 0.0f }, + + /* Pos_2_12 */ { 2.0f / 12.0f, 0.0f }, + /* Pos_3_12 */ { 3.0f / 12.0f, 0.0f }, + /* Pos_5_12 */ { 5.0f / 12.0f, 0.0f }, + /* Pos_6_12 */ { 6.0f / 12.0f, 0.0f }, + /* Pos_8_12 */ { 8.0f / 12.0f, 0.0f }, + /* Pos_9_12 */ { 9.0f / 12.0f, 0.0f }, + /* Pos_11_12 */ { 11.0f / 12.0f, 0.0f }, +}; + +static constexpr char32_t BoxDrawing_FirstChar = 0x2500; +static constexpr u32 BoxDrawing_CharCount = 0xA0; +static constexpr Instruction BoxDrawing[BoxDrawing_CharCount][InstructionsPerGlyph] = { + // U+2500 ─ BOX DRAWINGS LIGHT HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2501 ━ BOX DRAWINGS HEAVY HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2502 │ BOX DRAWINGS LIGHT VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2503 ┃ BOX DRAWINGS HEAVY VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2504 ┄ BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_9, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_9, Pos_1_2, Pos_5_9, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_6_9, Pos_1_2, Pos_8_9, Pos_1_2 }, + }, + // U+2505 ┅ BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_9, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_9, Pos_1_2, Pos_5_9, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_6_9, Pos_1_2, Pos_8_9, Pos_1_2 }, + }, + // U+2506 ┆ BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_9 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_9, Pos_1_2, Pos_5_9 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_6_9, Pos_1_2, Pos_8_9 }, + }, + // U+2507 ┇ BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_9 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_9, Pos_1_2, Pos_5_9 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_6_9, Pos_1_2, Pos_8_9 }, + }, + // U+2508 ┈ BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_12, Pos_1_2, Pos_5_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_6_12, Pos_1_2, Pos_8_12, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_9_12, Pos_1_2, Pos_11_12, Pos_1_2 }, + }, + // U+2509 ┉ BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_12, Pos_1_2, Pos_5_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_6_12, Pos_1_2, Pos_8_12, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_9_12, Pos_1_2, Pos_11_12, Pos_1_2 }, + }, + // U+250A ┊ BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_12, Pos_1_2, Pos_5_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_6_12, Pos_1_2, Pos_8_12 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_9_12, Pos_1_2, Pos_11_12 }, + }, + // U+250B ┋ BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_12, Pos_1_2, Pos_5_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_6_12, Pos_1_2, Pos_8_12 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_9_12, Pos_1_2, Pos_11_12 }, + }, + // U+250C ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250D ┍ BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250E ┎ BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+250F ┏ BOX DRAWINGS HEAVY DOWN AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2510 ┐ BOX DRAWINGS LIGHT DOWN AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2511 ┑ BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2512 ┒ BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2513 ┓ BOX DRAWINGS HEAVY DOWN AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2514 └ BOX DRAWINGS LIGHT UP AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2515 ┕ BOX DRAWINGS UP LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2516 ┖ BOX DRAWINGS UP HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2517 ┗ BOX DRAWINGS HEAVY UP AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2518 ┘ BOX DRAWINGS LIGHT UP AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2519 ┙ BOX DRAWINGS UP LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251A ┚ BOX DRAWINGS UP HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251B ┛ BOX DRAWINGS HEAVY UP AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+251C ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+251D ┝ BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+251E ┞ BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+251F ┟ BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2520 ┠ BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2521 ┡ BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2522 ┢ BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2523 ┣ BOX DRAWINGS HEAVY VERTICAL AND RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2524 ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2525 ┥ BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2526 ┦ BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2527 ┧ BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2528 ┨ BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2529 ┩ BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252A ┪ BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252B ┫ BOX DRAWINGS HEAVY VERTICAL AND LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+252C ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252D ┭ BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252E ┮ BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+252F ┯ BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2530 ┰ BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2531 ┱ BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2532 ┲ BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2533 ┳ BOX DRAWINGS HEAVY DOWN AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2534 ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2535 ┵ BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_0_5, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2536 ┶ BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_0_5, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2537 ┷ BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2538 ┸ BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2539 ┹ BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253A ┺ BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253B ┻ BOX DRAWINGS HEAVY UP AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+253C ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253D ┽ BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253E ┾ BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+253F ┿ BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2540 ╀ BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2541 ╁ BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2542 ╂ BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2543 ╃ BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2544 ╄ BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2545 ╅ BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2546 ╆ BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2547 ╇ BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2548 ╈ BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2549 ╉ BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254A ╊ BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254B ╋ BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+254C ╌ BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_2_6, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_3_6, Pos_1_2, Pos_5_6, Pos_1_2 }, + }, + // U+254D ╍ BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_2_6, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_3_6, Pos_1_2, Pos_5_6, Pos_1_2 }, + }, + // U+254E ╎ BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_6 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_3_6, Pos_1_2, Pos_5_6 }, + }, + // U+254F ╏ BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_2_6 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_3_6, Pos_1_2, Pos_5_6 }, + }, + // U+2550 ═ BOX DRAWINGS DOUBLE HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + }, + // U+2551 ║ BOX DRAWINGS DOUBLE VERTICAL + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + }, + // U+2552 ╒ BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1 }, + }, + // U+2553 ╓ BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2554 ╔ BOX DRAWINGS DOUBLE DOWN AND RIGHT + { + Instruction{ Shape_EmptyRect, Pos_1_2_Sub_1, Pos_1_2_Sub_1, Pos_Max, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2555 ╕ BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1 }, + }, + // U+2556 ╖ BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+2557 ╗ BOX DRAWINGS DOUBLE DOWN AND LEFT + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Add_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + }, + // U+2558 ╘ BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_0_5, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Add_1 }, + }, + // U+2559 ╙ BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+255A ╚ BOX DRAWINGS DOUBLE UP AND RIGHT + { + Instruction{ Shape_EmptyRect, Pos_1_2_Sub_1, Pos_Min, Pos_Max, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + }, + // U+255B ╛ BOX DRAWINGS UP SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Add_1 }, + }, + // U+255C ╜ BOX DRAWINGS UP DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2_Add_0_5 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+255D ╝ BOX DRAWINGS DOUBLE UP AND LEFT + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + }, + // U+255E ╞ BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+255F ╟ BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2560 ╠ BOX DRAWINGS DOUBLE VERTICAL AND RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2561 ╡ BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_2, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+2562 ╢ BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2_Sub_1, Pos_1_2 }, + }, + // U+2563 ╣ BOX DRAWINGS DOUBLE VERTICAL AND LEFT + { + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + }, + // U+2564 ╤ BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2_Add_1, Pos_1_2, Pos_1_1 }, + }, + // U+2565 ╥ BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_1_2, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_1_2, Pos_1_2_Add_1, Pos_1_1 }, + }, + // U+2566 ╦ BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+2567 ╧ BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2_Sub_1 }, + }, + // U+2568 ╨ BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_2 }, + }, + // U+2569 ╩ BOX DRAWINGS DOUBLE UP AND HORIZONTAL + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + }, + // U+256A ╪ BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2_Add_1, Pos_1_1, Pos_1_2_Add_1 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+256B ╫ BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE + { + Instruction{ Shape_LightLine, Pos_1_2_Sub_1, Pos_0_1, Pos_1_2_Sub_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_1_2_Add_1, Pos_0_1, Pos_1_2_Add_1, Pos_1_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+256C ╬ BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL + { + Instruction{ Shape_EmptyRect, Pos_Min, Pos_Min, Pos_1_2_Sub_1, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_Min, Pos_Max, Pos_1_2_Sub_1 }, + Instruction{ Shape_EmptyRect, Pos_Min, Pos_1_2_Add_1, Pos_1_2_Sub_1, Pos_Max }, + Instruction{ Shape_EmptyRect, Pos_1_2_Add_1, Pos_1_2_Add_1, Pos_Max, Pos_Max }, + }, + // U+256D ╭ BOX DRAWINGS LIGHT ARC DOWN AND RIGHT + { + Instruction{ Shape_RoundRect, Pos_1_2, Pos_1_2, Pos_Max, Pos_Max }, + }, + // U+256E ╮ BOX DRAWINGS LIGHT ARC DOWN AND LEFT + { + Instruction{ Shape_RoundRect, Pos_Min, Pos_1_2, Pos_1_2, Pos_Max }, + }, + // U+256F ╯ BOX DRAWINGS LIGHT ARC UP AND LEFT + { + Instruction{ Shape_RoundRect, Pos_Min, Pos_Min, Pos_1_2, Pos_1_2 }, + }, + // U+2570 ╰ BOX DRAWINGS LIGHT ARC UP AND RIGHT + { + Instruction{ Shape_RoundRect, Pos_1_2, Pos_Min, Pos_Max, Pos_1_2 }, + }, + // U+2571 ╱ BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+2572 ╲ BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2573 ╳ BOX DRAWINGS LIGHT DIAGONAL CROSS + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2574 ╴ BOX DRAWINGS LIGHT LEFT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + }, + // U+2575 ╵ BOX DRAWINGS LIGHT UP + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2576 ╶ BOX DRAWINGS LIGHT RIGHT + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+2577 ╷ BOX DRAWINGS LIGHT DOWN + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2578 ╸ BOX DRAWINGS HEAVY LEFT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + }, + // U+2579 ╹ BOX DRAWINGS HEAVY UP + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+257A ╺ BOX DRAWINGS HEAVY RIGHT + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257B ╻ BOX DRAWINGS HEAVY DOWN + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+257C ╼ BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257D ╽ BOX DRAWINGS LIGHT UP AND HEAVY DOWN + { + Instruction{ Shape_LightLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+257E ╾ BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT + { + Instruction{ Shape_HeavyLine, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+257F ╿ BOX DRAWINGS HEAVY UP AND LIGHT DOW + { + Instruction{ Shape_HeavyLine, Pos_1_2, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_LightLine, Pos_1_2, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2580 ▀ UPPER HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+2581 ▁ LOWER ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_7_8, Pos_1_1, Pos_1_1 }, + }, + // U+2582 ▂ LOWER ONE QUARTER BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_3_4, Pos_1_1, Pos_1_1 }, + }, + // U+2583 ▃ LOWER THREE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_5_8, Pos_1_1, Pos_1_1 }, + }, + // U+2584 ▄ LOWER HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+2585 ▅ LOWER FIVE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_3_8, Pos_1_1, Pos_1_1 }, + }, + // U+2586 ▆ LOWER THREE QUARTERS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_4, Pos_1_1, Pos_1_1 }, + }, + // U+2587 ▇ LOWER SEVEN EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_8, Pos_1_1, Pos_1_1 }, + }, + // U+2588 █ FULL BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2589 ▉ LEFT SEVEN EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_7_8, Pos_1_1 }, + }, + // U+258A ▊ LEFT THREE QUARTERS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_3_4, Pos_1_1 }, + }, + // U+258B ▋ LEFT FIVE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_5_8, Pos_1_1 }, + }, + // U+258C ▌ LEFT HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + }, + // U+258D ▍ LEFT THREE EIGHTHS BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_3_8, Pos_1_1 }, + }, + // U+258E ▎ LEFT ONE QUARTER BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_4, Pos_1_1 }, + }, + // U+258F ▏ LEFT ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_8, Pos_1_1 }, + }, + // U+2590 ▐ RIGHT HALF BLOCK + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2591 ░ LIGHT SHADE + { + Instruction{ Shape_Filled025, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2592 ▒ MEDIUM SHADE + { + Instruction{ Shape_Filled050, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2593 ▓ DARK SHADE + { + Instruction{ Shape_Filled075, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2594 ▔ UPPER ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_8 }, + }, + // U+2595 ▕ RIGHT ONE EIGHTH BLOCK + { + Instruction{ Shape_Filled100, Pos_7_8, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+2596 ▖ QUADRANT LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + }, + // U+2597 ▗ QUADRANT LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+2598 ▘ QUADRANT UPPER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + }, + // U+2599 ▙ QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+259A ▚ QUADRANT UPPER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_1_2, Pos_1_1, Pos_1_1 }, + }, + // U+259B ▛ QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259C ▜ QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_0_1, Pos_1_2, Pos_1_2 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+259D ▝ QUADRANT UPPER RIGHT + { + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259E ▞ QUADRANT UPPER RIGHT AND LOWER LEFT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_2 }, + }, + // U+259F ▟ QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + { + Instruction{ Shape_Filled100, Pos_0_1, Pos_1_2, Pos_1_2, Pos_1_1 }, + Instruction{ Shape_Filled100, Pos_1_2, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, +}; + +static constexpr char32_t Powerline_FirstChar = 0xE0B0; +static constexpr u32 Powerline_CharCount = 0x10; +static constexpr Instruction Powerline[Powerline_CharCount][InstructionsPerGlyph] = { + // U+E0B0 Right triangle solid + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_2 }, + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B1 Right triangle line + { + Instruction{ Shape_OpenLinePath, Pos_0_1, Pos_0_1, Pos_1_1_Sub_0_5, Pos_1_2 }, + Instruction{ Shape_OpenLinePath, Pos_0_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B2 Left triangle solid + { + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_0_1, Pos_1_2 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B3 Left triangle line + { + Instruction{ Shape_OpenLinePath, Pos_1_1, Pos_0_1, Pos_0_1_Add_0_5, Pos_1_2 }, + Instruction{ Shape_OpenLinePath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B4 Right semi-circle solid + { + Instruction{ Shape_FilledEllipsis, Pos_0_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+E0B5 Right semi-circle line + { + Instruction{ Shape_EmptyEllipsis, Pos_0_1, Pos_1_2, Pos_1_1_Sub_0_5, Pos_1_2_Sub_0_5 }, + }, + // U+E0B6 Left semi-circle solid + { + Instruction{ Shape_FilledEllipsis, Pos_1_1, Pos_1_2, Pos_1_1, Pos_1_2 }, + }, + // U+E0B7 Left semi-circle line + { + Instruction{ Shape_EmptyEllipsis, Pos_1_1, Pos_1_2, Pos_1_1_Sub_0_5, Pos_1_2_Sub_0_5 }, + }, + // U+E0B8 Lower left triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_0_1, Pos_1_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0B9 Backslash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, + // U+E0BA Lower right triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_1_1, Pos_1_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_Min, Pos_Min }, + }, + // U+E0BB Forward slash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+E0BC Upper left triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_1_1, Pos_0_1, Pos_0_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_0_1, Pos_Min, Pos_Min }, + }, + // U+E0BD Forward slash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_1_1, Pos_1_1, Pos_0_1 }, + }, + // U+E0BE Upper right triangle + { + Instruction{ Shape_ClosedFilledPath, Pos_0_1, Pos_0_1, Pos_1_1, Pos_0_1 }, + Instruction{ Shape_ClosedFilledPath, Pos_1_1, Pos_1_1, Pos_Min, Pos_Min }, + }, + // U+E0BF Backslash separator + { + Instruction{ Shape_LightLine, Pos_0_1, Pos_0_1, Pos_1_1, Pos_1_1 }, + }, +}; + +constexpr bool BoxDrawing_IsMapped(char32_t codepoint) +{ + return codepoint >= BoxDrawing_FirstChar && codepoint < (BoxDrawing_FirstChar + BoxDrawing_CharCount); +} + +constexpr bool Powerline_IsMapped(char32_t codepoint) +{ + return codepoint >= Powerline_FirstChar && codepoint < (Powerline_FirstChar + Powerline_CharCount); +} + +// How should I make this constexpr == inline, if it's an external symbol? Bad compiler! +#pragma warning(suppress : 26497) // You can attempt to make '...' constexpr unless it contains any undefined behavior (f.4). +bool BuiltinGlyphs::IsBuiltinGlyph(char32_t codepoint) noexcept +{ + return BoxDrawing_IsMapped(codepoint) || Powerline_IsMapped(codepoint); +} + +static const Instruction* GetInstructions(char32_t codepoint) noexcept +{ + if (BoxDrawing_IsMapped(codepoint)) + { + return &BoxDrawing[codepoint - BoxDrawing_FirstChar][0]; + } + if (Powerline_IsMapped(codepoint)) + { + return &Powerline[codepoint - Powerline_FirstChar][0]; + } + return nullptr; +} + +static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext* renderTarget, Shape shape) +{ + static constexpr u32 _ = 0; + static constexpr u32 w = 0xffffffff; + static constexpr u32 size = 4; + // clang-format off + static constexpr u32 shades[3][size * size] = { + { + w, _, _, _, + w, _, _, _, + _, _, w, _, + _, _, w, _, + }, + { + w, _, w, _, + _, w, _, w, + w, _, w, _, + _, w, _, w, + }, + { + _, w, w, w, + _, w, w, w, + w, w, _, w, + w, w, _, w, + }, + }; + // clang-format on + + static constexpr D2D1_SIZE_U bitmapSize{ size, size }; + static constexpr D2D1_BITMAP_PROPERTIES bitmapProps{ + .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, + .dpiX = 96, + .dpiY = 96, + }; + static constexpr D2D1_BITMAP_BRUSH_PROPERTIES bitmapBrushProps{ + .extendModeX = D2D1_EXTEND_MODE_WRAP, + .extendModeY = D2D1_EXTEND_MODE_WRAP, + .interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR + }; + + assert(shape < ARRAYSIZE(shades)); + + wil::com_ptr bitmap; + THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); + + wil::com_ptr bitmapBrush; + THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); + + return bitmapBrush; +} + +void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) +{ + renderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); + const auto restoreD2D = wil::scope_exit([&]() { + renderTarget->PopAxisAlignedClip(); + }); + + const auto instructions = GetInstructions(codepoint); + if (!instructions) + { + assert(false); // If everything in AtlasEngine works correctly, then this function should not get called when !IsBuiltinGlyph(codepoint). + renderTarget->Clear(nullptr); + return; + } + + const auto rectX = rect.left; + const auto rectY = rect.top; + const auto rectW = rect.right - rect.left; + const auto rectH = rect.bottom - rect.top; + const auto lightLineWidth = std::max(1.0f, roundf(rectW / 8.0f)); + D2D1_POINT_2F geometryPoints[2 * InstructionsPerGlyph]; + size_t geometryPointsCount = 0; + + for (size_t i = 0; i < InstructionsPerGlyph; ++i) + { + const auto& instruction = instructions[i]; + if (instruction.value == 0) + { + break; + } + + const auto shape = static_cast(instruction.shape); + auto begX = Pos_Lut[instruction.begX][0] * rectW; + auto begY = Pos_Lut[instruction.begY][0] * rectH; + auto endX = Pos_Lut[instruction.endX][0] * rectW; + auto endY = Pos_Lut[instruction.endY][0] * rectH; + + begX += Pos_Lut[instruction.begX][1] * lightLineWidth; + begY += Pos_Lut[instruction.begY][1] * lightLineWidth; + endX += Pos_Lut[instruction.endX][1] * lightLineWidth; + endY += Pos_Lut[instruction.endY][1] * lightLineWidth; + + const auto lineWidth = shape == Shape_HeavyLine ? lightLineWidth * 2.0f : lightLineWidth; + const auto lineWidthHalf = lineWidth * 0.5f; + const auto isHollowRect = shape == Shape_EmptyRect || shape == Shape_RoundRect; + const auto isLine = shape == Shape_LightLine || shape == Shape_HeavyLine; + const auto isLineX = isLine && begX == endX; + const auto isLineY = isLine && begY == endY; + const auto lineOffsetX = isHollowRect || isLineX ? lineWidthHalf : 0.0f; + const auto lineOffsetY = isHollowRect || isLineY ? lineWidthHalf : 0.0f; + + begX = roundf(begX - lineOffsetX) + lineOffsetX; + begY = roundf(begY - lineOffsetY) + lineOffsetY; + endX = roundf(endX + lineOffsetX) - lineOffsetX; + endY = roundf(endY + lineOffsetY) - lineOffsetY; + + const auto begXabs = begX + rectX; + const auto begYabs = begY + rectY; + const auto endXabs = endX + rectX; + const auto endYabs = endY + rectY; + + switch (shape) + { + case Shape_Filled025: + case Shape_Filled050: + case Shape_Filled075: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + const auto bitmapBrush = createShadedBitmapBrush(renderTarget, shape); + renderTarget->FillRectangle(&r, bitmapBrush.get()); + break; + } + case Shape_Filled100: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + renderTarget->FillRectangle(&r, brush); + break; + } + case Shape_LightLine: + case Shape_HeavyLine: + { + const D2D1_POINT_2F beg{ begXabs, begYabs }; + const D2D1_POINT_2F end{ endXabs, endYabs }; + renderTarget->DrawLine(beg, end, brush, lineWidth, nullptr); + break; + } + case Shape_EmptyRect: + { + const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs }; + renderTarget->DrawRectangle(&r, brush, lineWidth, nullptr); + break; + } + case Shape_RoundRect: + { + const D2D1_ROUNDED_RECT rr{ { begXabs, begYabs, endXabs, endYabs }, lightLineWidth * 2, lightLineWidth * 2 }; + renderTarget->DrawRoundedRectangle(&rr, brush, lineWidth, nullptr); + break; + } + case Shape_FilledEllipsis: + { + const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY }; + renderTarget->FillEllipse(&e, brush); + break; + } + case Shape_EmptyEllipsis: + { + const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY }; + renderTarget->DrawEllipse(&e, brush, lineWidth, nullptr); + break; + } + case Shape_ClosedFilledPath: + case Shape_OpenLinePath: + if (instruction.begX) + { + geometryPoints[geometryPointsCount++] = { begXabs, begYabs }; + } + if (instruction.endX) + { + geometryPoints[geometryPointsCount++] = { endXabs, endYabs }; + } + break; + } + } + + if (geometryPointsCount) + { + const auto shape = instructions[0].shape; + const auto beginType = shape == Shape_ClosedFilledPath ? D2D1_FIGURE_BEGIN_FILLED : D2D1_FIGURE_BEGIN_HOLLOW; + const auto endType = shape == Shape_ClosedFilledPath ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN; + + wil::com_ptr geometry; + THROW_IF_FAILED(factory->CreatePathGeometry(geometry.addressof())); + + wil::com_ptr sink; + THROW_IF_FAILED(geometry->Open(sink.addressof())); + + sink->BeginFigure(geometryPoints[0], beginType); + sink->AddLines(&geometryPoints[1], static_cast(geometryPointsCount - 1)); + sink->EndFigure(endType); + + THROW_IF_FAILED(sink->Close()); + + if (beginType == D2D1_FIGURE_BEGIN_FILLED) + { + renderTarget->FillGeometry(geometry.get(), brush, nullptr); + } + else + { + renderTarget->DrawGeometry(geometry.get(), brush, lightLineWidth, nullptr); + } + } +} diff --git a/src/renderer/atlas/BuiltinGlyphs.h b/src/renderer/atlas/BuiltinGlyphs.h new file mode 100644 index 00000000000..f399653893c --- /dev/null +++ b/src/renderer/atlas/BuiltinGlyphs.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "common.h" + +namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs +{ + bool IsBuiltinGlyph(char32_t codepoint) noexcept; + void DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint); + + // This is just an extra. It's not actually implemented as part of BuiltinGlyphs.cpp. + constexpr bool IsSoftFontChar(char32_t ch) noexcept + { + return ch >= 0xEF20 && ch < 0xEF80; + } +} diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj index 689ec6ff8d7..a2faefce901 100644 --- a/src/renderer/atlas/atlas.vcxproj +++ b/src/renderer/atlas/atlas.vcxproj @@ -16,6 +16,7 @@ + @@ -28,6 +29,7 @@ + diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 014f5487118..71010b31f77 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -360,6 +360,7 @@ namespace Microsoft::Console::Render::Atlas u16 dpi = 96; AntialiasingMode antialiasingMode = DefaultAntialiasingMode; + bool builtinGlyphs = false; std::vector softFontPattern; til::size softFontCellSize; @@ -420,8 +421,8 @@ namespace Microsoft::Console::Render::Atlas struct FontMapping { wil::com_ptr fontFace; - u32 glyphsFrom = 0; - u32 glyphsTo = 0; + size_t glyphsFrom = 0; + size_t glyphsTo = 0; }; struct GridLineRange @@ -450,11 +451,18 @@ namespace Microsoft::Console::Render::Atlas dirtyBottom = dirtyTop + cellHeight; } + // Each mappings from/to range indicates the range of indices/advances/offsets/colors this fontFace is to be used for. std::vector mappings; + // Stores glyph indices of the corresponding mappings.fontFace, unless fontFace is nullptr, + // in which case this stores UTF16 because we're dealing with a custom glyph (box glyph, etc.). std::vector glyphIndices; - std::vector glyphAdvances; // same size as glyphIndices - std::vector glyphOffsets; // same size as glyphIndices - std::vector colors; // same size as glyphIndices + // Same size as glyphIndices. + std::vector glyphAdvances; + // Same size as glyphIndices. + std::vector glyphOffsets; + // Same size as glyphIndices. + std::vector colors; + std::vector gridLineRanges; LineRendition lineRendition = LineRendition::SingleWidth; u16 selectionFrom = 0; @@ -499,6 +507,7 @@ namespace Microsoft::Console::Render::Atlas //// Parameters which change seldom. GenerationalSettings s; + std::wstring userLocaleName; //// Parameters which change every frame. // This is the backing buffer for `rows`. diff --git a/src/renderer/base/FontInfoDesired.cpp b/src/renderer/base/FontInfoDesired.cpp index 742883e7c14..b578960c812 100644 --- a/src/renderer/base/FontInfoDesired.cpp +++ b/src/renderer/base/FontInfoDesired.cpp @@ -29,6 +29,11 @@ void FontInfoDesired::SetCellSize(const CSSLengthPercentage& cellWidth, const CS _cellHeight = cellHeight; } +void FontInfoDesired::SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept +{ + _builtinGlyphs = builtinGlyphs; +} + const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept { return _cellWidth; @@ -39,6 +44,11 @@ const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept return _cellHeight; } +bool FontInfoDesired::GetEnableBuiltinGlyphs() const noexcept +{ + return _builtinGlyphs; +} + float FontInfoDesired::GetFontSize() const noexcept { return _fontSize; diff --git a/src/renderer/inc/FontInfoDesired.hpp b/src/renderer/inc/FontInfoDesired.hpp index 84643283183..a58425b4118 100644 --- a/src/renderer/inc/FontInfoDesired.hpp +++ b/src/renderer/inc/FontInfoDesired.hpp @@ -35,9 +35,11 @@ class FontInfoDesired : public FontInfoBase bool operator==(const FontInfoDesired& other) = delete; void SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept; + void SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept; const CSSLengthPercentage& GetCellWidth() const noexcept; const CSSLengthPercentage& GetCellHeight() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; float GetFontSize() const noexcept; til::size GetEngineSize() const noexcept; bool IsDefaultRasterFont() const noexcept; @@ -47,4 +49,5 @@ class FontInfoDesired : public FontInfoBase float _fontSize; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; + bool _builtinGlyphs = false; }; diff --git a/src/til/ut_til/FlatSetTests.cpp b/src/til/ut_til/FlatSetTests.cpp index 43e4c5fb2be..751f7f84059 100644 --- a/src/til/ut_til/FlatSetTests.cpp +++ b/src/til/ut_til/FlatSetTests.cpp @@ -12,37 +12,34 @@ using namespace WEX::TestExecution; struct Data { static constexpr auto emptyMarker = std::numeric_limits::max(); + size_t value = emptyMarker; +}; - constexpr operator bool() const noexcept +struct DataHashTrait +{ + static constexpr bool occupied(const Data& d) noexcept { - return value != emptyMarker; + return d.value != Data::emptyMarker; } - constexpr bool operator==(int key) const noexcept + static constexpr size_t hash(const size_t key) noexcept { - return value == static_cast(key); + return til::flat_set_hash_integer(key); } - constexpr Data& operator=(int key) noexcept + static constexpr size_t hash(const Data& d) noexcept { - value = static_cast(key); - return *this; + return til::flat_set_hash_integer(d.value); } - size_t value = emptyMarker; -}; - -template<> -struct ::std::hash -{ - constexpr size_t operator()(int key) const noexcept + static constexpr bool equals(const Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(static_cast(key)); + return d.value == key; } - constexpr size_t operator()(Data d) const noexcept + static constexpr void assign(Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(d.value); + d.value = key; } }; @@ -52,7 +49,7 @@ class FlatSetTests TEST_METHOD(Basic) { - til::linear_flat_set set; + til::linear_flat_set set; // This simultaneously demonstrates how the class can't just do "heterogeneous lookups" // like STL does, but also insert items with a different type. @@ -62,7 +59,7 @@ class FlatSetTests const auto [entry2, inserted2] = set.insert(123); VERIFY_IS_FALSE(inserted2); - VERIFY_ARE_EQUAL(&entry1, &entry2); - VERIFY_ARE_EQUAL(123u, entry2.value); + VERIFY_ARE_EQUAL(entry1, entry2); + VERIFY_ARE_EQUAL(123u, entry2->value); } };