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