From ad2391c9b5482e7744176fc85dd4709f90dd7f69 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 20 Feb 2024 01:25:00 +0100 Subject: [PATCH 1/5] Support for custom box drawing and powerline glyphs --- src/cascadia/TerminalControl/ControlCore.cpp | 2 + src/cascadia/TerminalControl/ControlCore.h | 1 + .../TerminalControl/IControlSettings.idl | 1 + .../TerminalSettingsEditor/Appearances.h | 1 + .../TerminalSettingsEditor/Appearances.idl | 1 + .../TerminalSettingsEditor/Appearances.xaml | 9 + .../Resources/en-US/Resources.resw | 8 + .../TerminalSettingsModel/FontConfig.idl | 1 + .../TerminalSettingsModel/MTSMSettings.h | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 1 + src/cascadia/inc/ControlProperties.h | 1 + src/inc/til/flat_set.h | 36 +- src/inc/til/unicode.h | 13 +- src/renderer/atlas/AtlasEngine.api.cpp | 6 +- src/renderer/atlas/AtlasEngine.cpp | 137 +- src/renderer/atlas/AtlasEngine.h | 4 +- src/renderer/atlas/AtlasEngine.r.cpp | 15 +- src/renderer/atlas/Backend.h | 5 - src/renderer/atlas/BackendD3D.cpp | 473 +++--- src/renderer/atlas/BackendD3D.h | 109 +- src/renderer/atlas/CustomGlyphs.cpp | 1265 +++++++++++++++++ src/renderer/atlas/CustomGlyphs.h | 18 + src/renderer/atlas/atlas.vcxproj | 4 +- src/renderer/atlas/common.h | 6 +- src/renderer/base/FontInfoDesired.cpp | 10 + src/renderer/inc/FontInfoDesired.hpp | 3 + src/til/ut_til/FlatSetTests.cpp | 37 +- 28 files changed, 1716 insertions(+), 453 deletions(-) create mode 100644 src/renderer/atlas/CustomGlyphs.cpp create mode 100644 src/renderer/atlas/CustomGlyphs.h diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 68702b27500..4e8453e3b35 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -869,6 +869,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto lock = _terminal->LockForWriting(); + _customGlyphs = _settings->CustomGlyphs(); _cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str()); _cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str()); _runtimeOpacity = std::nullopt; @@ -1047,6 +1048,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false }; _actualFontFaceName = { fontFace }; + _desiredFont.SetCustomGlyphs(_customGlyphs); _desiredFont.SetCellSize(_cellWidth, _cellHeight); const auto before = _actualFont.GetSize(); diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 6b513f2bf86..d58be349ffa 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 _customGlyphs = true; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2ccfbd5b90d..4f27bae15b2 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -43,6 +43,7 @@ namespace Microsoft.Terminal.Control String Padding { get; }; Windows.Foundation.Collections.IMap FontFeatures { get; }; Windows.Foundation.Collections.IMap FontAxes { get; }; + Boolean CustomGlyphs { get; }; String CellWidth { get; }; String CellHeight { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index bc34e62978c..366d8660fd2 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(), CustomGlyphs); 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..e84fd08cdb5 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, CustomGlyphs); 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..4203dcfcf48 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. + + Custom 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_CustomGlyphs" 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..aa1aaea8e89 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, CustomGlyphs); 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 d0dc6bfcca5..d5d9e32a65e 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -115,6 +115,7 @@ Author(s): X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \ X(IFontAxesMap, FontAxes, "axes") \ X(IFontFeatureMap, FontFeatures, "features") \ + X(bool, CustomGlyphs, "customGlyphs", 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 580b06089fd..ef4ac132ed6 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(); + _CustomGlyphs = fontInfo.CustomGlyphs(); _CellWidth = fontInfo.CellWidth(); _CellHeight = fontInfo.CellHeight(); _Padding = profile.Padding(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 009e6503da0..5d11f5d299d 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, CustomGlyphs, 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 1b61775becc..974d009622c 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, CustomGlyphs, true) \ X(winrt::hstring, CellWidth) \ X(winrt::hstring, CellHeight) \ X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \ 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/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 0e2f7941688..a0d95e49851 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->customGlyphs = fontInfoDesired.GetCustomGlyphs(); } } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 93821394ce1..c57c75eb8f0 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 "CustomGlyphs.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; @@ -575,7 +579,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()) @@ -666,14 +670,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 (!_api.s->font->customGlyphs) + { + _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 = CustomGlyphs::IsCustomGlyph(codepoint) || CustomGlyphs::IsSoftFontChar(codepoint); + if (custom != c) + { + break; + } + + segmentEnd = i; + } while (segmentEnd < len); + + if (segmentBeg != segmentEnd) + { + if (custom) + { + _mapCustomGlyphs(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) @@ -712,10 +767,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 +805,31 @@ void AtlasEngine::_flushBufferLine() } } +void AtlasEngine::_mapCustomGlyphs(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 +884,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 +929,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 +981,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 +1056,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..02abadae497 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 _mapCustomGlyphs(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); @@ -133,8 +135,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..4b51227fd24 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(); } @@ -276,11 +276,8 @@ 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(); } @@ -319,13 +316,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 69d597ed0bd..58cd0328385 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 "CustomGlyphs.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) { @@ -340,7 +306,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) { @@ -764,11 +734,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 : _customGlyphs.glyphs) + { + glyphs.clear(); + } _d2dBeginDrawing(); _d2dRenderTarget->Clear(); @@ -809,9 +783,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()); @@ -833,9 +804,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() }; @@ -967,7 +937,7 @@ void BackendD3D::_drawBackground(const RenderingPayload& p) } _appendQuad() = { - .shadingType = ShadingType::Background, + .shadingType = static_cast(ShadingType::Background), .size = p.s->targetSize, }; } @@ -1025,58 +995,53 @@ 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 = &_customGlyphs; + if (m.fontFace) [[likely]] { - _initializeFontFaceEntry(fontFaceEntry); + fontFaceEntry = _glyphAtlasMap.insert(m.fontFace.get()).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]); + u32 glyphIndex = row->glyphIndices[x]; + if (!fontFace && til::is_leading_surrogate(glyphIndex)) + { + glyphIndex = til::combine_surrogates(glyphIndex, row->glyphIndices[x + 1]); + } - if (inserted && !_drawGlyph(p, fontFaceEntry, glyphEntry)) + auto glyphEntry = glyphs.lookup(glyphIndex); + if (!glyphEntry) { - // 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; + glyphEntry = _drawGlyph(p, *row, *fontFaceEntry, glyphIndex); } - if (glyphEntry.data.shadingType != ShadingType::Default) + // 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); } @@ -1201,71 +1166,22 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y) } } -void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry) +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; - } - - 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) -{ - if (!fontFaceEntry.fontFace) - { - return _drawSoftFontGlyph(p, fontFaceEntry, glyphEntry); + return _drawCustomGlyph(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. @@ -1334,16 +1250,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); } @@ -1399,26 +1312,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); @@ -1430,37 +1327,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; @@ -1486,56 +1361,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::_drawCustomGlyph(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 (CustomGlyphs::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), + }; + CustomGlyphs::DrawCustomGlyph(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), @@ -1548,6 +1452,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), @@ -1557,118 +1485,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; } } @@ -1703,7 +1580,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, @@ -1725,7 +1602,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) }, @@ -1815,8 +1692,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)), @@ -1899,7 +1776,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, @@ -1923,7 +1800,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; @@ -1943,7 +1820,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; @@ -2002,7 +1879,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; } @@ -2133,14 +2010,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 4c248ed3ff3..361cd4b5aa2 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -11,6 +11,11 @@ namespace Microsoft::Console::Render::Atlas { + namespace CustomGlyphs + { + union Instruction; + } + struct BackendD3D : IBackend { BackendD3D(const RenderingPayload& p); @@ -58,7 +63,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, @@ -90,7 +95,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; @@ -98,8 +103,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; @@ -107,80 +116,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; } }; @@ -217,12 +217,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* _drawCustomGlyph(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(); @@ -259,7 +259,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 _customGlyphs; Buffer _rectPackerData; stbrp_context _rectPacker{}; til::CoordType _ligatureOverhangTriggerLeft = 0; diff --git a/src/renderer/atlas/CustomGlyphs.cpp b/src/renderer/atlas/CustomGlyphs.cpp new file mode 100644 index 00000000000..51f9cce2b02 --- /dev/null +++ b/src/renderer/atlas/CustomGlyphs.cpp @@ -0,0 +1,1265 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "CustomGlyphs.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::CustomGlyphs; + +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_Filled100, // axis aligned rectangle, 100% filled + Shape_Filled075, // axis aligned rectangle, 75% filled + Shape_Filled050, // axis aligned rectangle, 50% filled + Shape_Filled025, // axis aligned rectangle, 25% 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 CustomGlyphs::IsCustomGlyph(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; +} + +void CustomGlyphs::DrawCustomGlyph(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 !IsCustomGlyph(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 + rectX; + auto begY = Pos_Lut[instruction.begY][0] * rectH + rectY; + auto endX = Pos_Lut[instruction.endX][0] * rectW + rectX; + auto endY = Pos_Lut[instruction.endY][0] * rectH + rectY; + + 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; + + switch (shape) + { + case Shape_LightLine: + case Shape_HeavyLine: + { + const D2D1_POINT_2F beg{ begX, begY }; + const D2D1_POINT_2F end{ endX, endY }; + renderTarget->DrawLine(beg, end, brush, lineWidth, nullptr); + break; + } + case Shape_EmptyRect: + case Shape_RoundRect: + { + const D2D1_ROUNDED_RECT r{ { begX, begY, endX, endY }, lightLineWidth * 2, lightLineWidth * 2 }; + if (shape == Shape_EmptyRect) + { + renderTarget->DrawRectangle(&r.rect, brush, lineWidth, nullptr); + } + else + { + renderTarget->DrawRoundedRectangle(&r, brush, lineWidth, nullptr); + } + break; + } + case Shape_FilledEllipsis: + case Shape_EmptyEllipsis: + { + const D2D1_ELLIPSE e{ { begX, begY }, endX, endY }; + if (shape == Shape_FilledEllipsis) + { + renderTarget->FillEllipse(&e, brush); + } + else + { + renderTarget->DrawEllipse(&e, brush, lineWidth, nullptr); + } + break; + } + case Shape_ClosedFilledPath: + case Shape_OpenLinePath: + if (instruction.begX) + { + geometryPoints[geometryPointsCount++] = { begX, begY }; + } + if (instruction.endX) + { + geometryPoints[geometryPointsCount++] = { endX, endY }; + } + break; + default: + { + wil::com_ptr bitmapBrush; + ID2D1Brush* b = brush; + + if (shape >= Shape_Filled075 && shape <= Shape_Filled025) + { + 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 + }; + + wil::com_ptr bitmap; + THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape - Shape_Filled075][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); + THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); + + b = bitmapBrush.get(); + } + + const D2D1_RECT_F r{ begX, begY, endX, endY }; + renderTarget->FillRectangle(&r, b); + 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/CustomGlyphs.h b/src/renderer/atlas/CustomGlyphs.h new file mode 100644 index 00000000000..471e3abc1c8 --- /dev/null +++ b/src/renderer/atlas/CustomGlyphs.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "common.h" + +namespace Microsoft::Console::Render::Atlas::CustomGlyphs +{ + bool IsCustomGlyph(char32_t codepoint) noexcept; + void DrawCustomGlyph(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 CustomGlyphs.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..07ea407b28e 100644 --- a/src/renderer/atlas/atlas.vcxproj +++ b/src/renderer/atlas/atlas.vcxproj @@ -16,6 +16,7 @@ + @@ -28,6 +29,7 @@ + @@ -99,4 +101,4 @@ $(SolutionDir)\oss\stb;$(OutDir)$(ProjectName);%(AdditionalIncludeDirectories) - + \ No newline at end of file diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 014f5487118..ab7a16d3c9f 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 customGlyphs = 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 @@ -499,6 +500,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..c9a83d0afd7 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::SetCustomGlyphs(bool customGlyphs) noexcept +{ + _customGlyphs = customGlyphs; +} + const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept { return _cellWidth; @@ -39,6 +44,11 @@ const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept return _cellHeight; } +bool FontInfoDesired::GetCustomGlyphs() const noexcept +{ + return _customGlyphs; +} + float FontInfoDesired::GetFontSize() const noexcept { return _fontSize; diff --git a/src/renderer/inc/FontInfoDesired.hpp b/src/renderer/inc/FontInfoDesired.hpp index 84643283183..06dbdf9c29b 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 SetCustomGlyphs(bool customGlyphs) noexcept; const CSSLengthPercentage& GetCellWidth() const noexcept; const CSSLengthPercentage& GetCellHeight() const noexcept; + bool GetCustomGlyphs() 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 _customGlyphs = false; }; diff --git a/src/til/ut_til/FlatSetTests.cpp b/src/til/ut_til/FlatSetTests.cpp index 43e4c5fb2be..751f7f84059 100644 --- a/src/til/ut_til/FlatSetTests.cpp +++ b/src/til/ut_til/FlatSetTests.cpp @@ -12,37 +12,34 @@ using namespace WEX::TestExecution; struct Data { static constexpr auto emptyMarker = std::numeric_limits::max(); + size_t value = emptyMarker; +}; - constexpr operator bool() const noexcept +struct DataHashTrait +{ + static constexpr bool occupied(const Data& d) noexcept { - return value != emptyMarker; + return d.value != Data::emptyMarker; } - constexpr bool operator==(int key) const noexcept + static constexpr size_t hash(const size_t key) noexcept { - return value == static_cast(key); + return til::flat_set_hash_integer(key); } - constexpr Data& operator=(int key) noexcept + static constexpr size_t hash(const Data& d) noexcept { - value = static_cast(key); - return *this; + return til::flat_set_hash_integer(d.value); } - size_t value = emptyMarker; -}; - -template<> -struct ::std::hash -{ - constexpr size_t operator()(int key) const noexcept + static constexpr bool equals(const Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(static_cast(key)); + return d.value == key; } - constexpr size_t operator()(Data d) const noexcept + static constexpr void assign(Data& d, size_t key) noexcept { - return til::flat_set_hash_integer(d.value); + d.value = key; } }; @@ -52,7 +49,7 @@ class FlatSetTests TEST_METHOD(Basic) { - til::linear_flat_set set; + til::linear_flat_set set; // This simultaneously demonstrates how the class can't just do "heterogeneous lookups" // like STL does, but also insert items with a different type. @@ -62,7 +59,7 @@ class FlatSetTests const auto [entry2, inserted2] = set.insert(123); VERIFY_IS_FALSE(inserted2); - VERIFY_ARE_EQUAL(&entry1, &entry2); - VERIFY_ARE_EQUAL(123u, entry2.value); + VERIFY_ARE_EQUAL(entry1, entry2); + VERIFY_ARE_EQUAL(123u, entry2->value); } }; From 3be7d11b74ed7f0eb090f2f6b69a790b1e7c847f Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 00:00:44 +0100 Subject: [PATCH 2/5] Fixed round glyphs, Added conhost setting, Fixed D2D mode --- dep/Console/winconp.h | 27 ++- .../TerminalSettingsEditor/Appearances.xaml | 2 +- src/host/screenInfo.cpp | 4 + src/host/settings.cpp | 5 + src/host/settings.hpp | 2 + src/propslib/RegistrySerialization.cpp | 5 +- src/renderer/atlas/AtlasEngine.cpp | 7 +- src/renderer/atlas/AtlasEngine.h | 11 ++ src/renderer/atlas/AtlasEngine.r.cpp | 9 +- src/renderer/atlas/BackendD3D.cpp | 4 +- src/renderer/atlas/CustomGlyphs.cpp | 181 ++++++++++-------- src/renderer/atlas/common.h | 13 +- 12 files changed, 159 insertions(+), 111 deletions(-) diff --git a/dep/Console/winconp.h b/dep/Console/winconp.h index 23fba76c144..c9f94e30a0e 100644 --- a/dep/Console/winconp.h +++ b/dep/Console/winconp.h @@ -85,25 +85,22 @@ GetConsoleKeyboardLayoutNameW( #define CONSOLE_REGISTRY_SCROLLSCALE L"ScrollScale" // V2 console settings -#define CONSOLE_REGISTRY_FORCEV2 L"ForceV2" -#define CONSOLE_REGISTRY_LINESELECTION L"LineSelection" -#define CONSOLE_REGISTRY_FILTERONPASTE L"FilterOnPaste" -#define CONSOLE_REGISTRY_LINEWRAP L"LineWrap" -#define CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED L"CtrlKeyShortcutsDisabled" #define CONSOLE_REGISTRY_ALLOW_ALTF4_CLOSE L"AllowAltF4Close" -#define CONSOLE_REGISTRY_VIRTTERM_LEVEL L"VirtualTerminalLevel" - -#define CONSOLE_REGISTRY_CURSORTYPE L"CursorType" +#define CONSOLE_REGISTRY_COPYCOLOR L"CopyColor" +#define CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED L"CtrlKeyShortcutsDisabled" #define CONSOLE_REGISTRY_CURSORCOLOR L"CursorColor" - +#define CONSOLE_REGISTRY_CURSORTYPE L"CursorType" +#define CONSOLE_REGISTRY_CUSTOM_GLYPHS L"CustomGlyphs" +#define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" +#define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" +#define CONSOLE_REGISTRY_FILTERONPASTE L"FilterOnPaste" +#define CONSOLE_REGISTRY_FORCEV2 L"ForceV2" #define CONSOLE_REGISTRY_INTERCEPTCOPYPASTE L"InterceptCopyPaste" - -#define CONSOLE_REGISTRY_COPYCOLOR L"CopyColor" +#define CONSOLE_REGISTRY_LINESELECTION L"LineSelection" +#define CONSOLE_REGISTRY_LINEWRAP L"LineWrap" +#define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" #define CONSOLE_REGISTRY_USEDX L"UseDx" - -#define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" -#define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" -#define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" +#define CONSOLE_REGISTRY_VIRTTERM_LEVEL L"VirtualTerminalLevel" // end V2 console settings /* diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 4203dcfcf48..e0748cc1b58 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -287,7 +287,7 @@ - + font->customGlyphs) + if (!_hackWantsCustomGlyphs) { _mapRegularText(0, len); return; @@ -752,7 +753,7 @@ void AtlasEngine::_mapRegularText(size_t offBeg, size_t offEnd) _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) diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 02abadae497..d7952a45f02 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -119,6 +119,17 @@ namespace Microsoft::Console::Render::Atlas std::unique_ptr _b; RenderingPayload _p; + // _p.s->font->customGlyphs 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 _hackWantsCustomGlyphs = true; + bool _hackTriggerRedrawAll = false; + struct ApiState { GenerationalSettings s = DirtyGenerationalSettings(); diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 4b51227fd24..9deb63434a0 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -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 @@ -279,6 +279,13 @@ void AtlasEngine::_recreateBackend() // 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 hackWantsCustomGlyphs = _p.s->font->customGlyphs && !d2dMode; + if (_hackWantsCustomGlyphs != hackWantsCustomGlyphs) + { + _hackWantsCustomGlyphs = hackWantsCustomGlyphs; + _hackTriggerRedrawAll = true; + } } void AtlasEngine::_handleSwapChainUpdate() diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 58cd0328385..e0edfbc66bf 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1000,9 +1000,9 @@ void BackendD3D::_drawText(RenderingPayload& p) // The lack of a fontFace indicates a soft font. AtlasFontFaceEntry* fontFaceEntry = &_customGlyphs; - if (m.fontFace) [[likely]] + if (fontFace) [[likely]] { - fontFaceEntry = _glyphAtlasMap.insert(m.fontFace.get()).first; + fontFaceEntry = _glyphAtlasMap.insert(fontFace).first; } const auto& glyphs = fontFaceEntry->glyphs[WI_EnumValue(row->lineRendition)]; diff --git a/src/renderer/atlas/CustomGlyphs.cpp b/src/renderer/atlas/CustomGlyphs.cpp index 51f9cce2b02..e819c9fc93f 100644 --- a/src/renderer/atlas/CustomGlyphs.cpp +++ b/src/renderer/atlas/CustomGlyphs.cpp @@ -32,10 +32,10 @@ static constexpr u32 InstructionsPerGlyph = 4; enum Shape : u32 { - Shape_Filled100, // axis aligned rectangle, 100% filled - Shape_Filled075, // axis aligned rectangle, 75% filled - Shape_Filled050, // axis aligned rectangle, 50% filled 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 @@ -1071,6 +1071,57 @@ static const Instruction* GetInstructions(char32_t codepoint) noexcept return nullptr; } +static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext* renderTarget, Shape shape) +{ + static constexpr u32 _ = 0; + static constexpr u32 w = 0xffffffff; + static constexpr u32 size = 4; + // clang-format off + static constexpr u32 shades[3][size * size] = { + { + _, _, _, w, + _, _, w, _, + _, w, _, _, + w, _, _, _, + }, + { + w, _, w, _, + _, w, _, w, + w, _, w, _, + _, w, _, w, + }, + { + w, w, w, _, + w, w, _, w, + w, _, w, w, + _, w, w, w, + }, + }; + // clang-format on + + static constexpr D2D1_SIZE_U bitmapSize{ size, size }; + static constexpr D2D1_BITMAP_PROPERTIES bitmapProps{ + .pixelFormat = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }, + .dpiX = 96, + .dpiY = 96, + }; + static constexpr D2D1_BITMAP_BRUSH_PROPERTIES bitmapBrushProps{ + .extendModeX = D2D1_EXTEND_MODE_WRAP, + .extendModeY = D2D1_EXTEND_MODE_WRAP, + .interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR + }; + + assert(shape < ARRAYSIZE(shades)); + + wil::com_ptr bitmap; + THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); + + wil::com_ptr bitmapBrush; + THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); + + return bitmapBrush; +} + void CustomGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) { renderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED); @@ -1103,10 +1154,10 @@ void CustomGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* re } const auto shape = static_cast(instruction.shape); - auto begX = Pos_Lut[instruction.begX][0] * rectW + rectX; - auto begY = Pos_Lut[instruction.begY][0] * rectH + rectY; - auto endX = Pos_Lut[instruction.endX][0] * rectW + rectX; - auto endY = Pos_Lut[instruction.endY][0] * rectH + rectY; + 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; @@ -1127,112 +1178,72 @@ void CustomGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* re 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{ begX, begY }; - const D2D1_POINT_2F end{ endX, endY }; + 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 r{ { begX, begY, endX, endY }, lightLineWidth * 2, lightLineWidth * 2 }; - if (shape == Shape_EmptyRect) - { - renderTarget->DrawRectangle(&r.rect, brush, lineWidth, nullptr); - } - else - { - renderTarget->DrawRoundedRectangle(&r, brush, lineWidth, nullptr); - } + 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{ { begX, begY }, endX, endY }; - if (shape == Shape_FilledEllipsis) - { - renderTarget->FillEllipse(&e, brush); - } - else - { - renderTarget->DrawEllipse(&e, brush, lineWidth, nullptr); - } + 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++] = { begX, begY }; + geometryPoints[geometryPointsCount++] = { begXabs, begYabs }; } if (instruction.endX) { - geometryPoints[geometryPointsCount++] = { endX, endY }; - } - break; - default: - { - wil::com_ptr bitmapBrush; - ID2D1Brush* b = brush; - - if (shape >= Shape_Filled075 && shape <= Shape_Filled025) - { - 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 - }; - - wil::com_ptr bitmap; - THROW_IF_FAILED(renderTarget->CreateBitmap(bitmapSize, &shades[shape - Shape_Filled075][0], sizeof(u32) * size, &bitmapProps, bitmap.addressof())); - THROW_IF_FAILED(renderTarget->CreateBitmapBrush(bitmap.get(), &bitmapBrushProps, nullptr, bitmapBrush.addressof())); - - b = bitmapBrush.get(); + geometryPoints[geometryPointsCount++] = { endXabs, endYabs }; } - - const D2D1_RECT_F r{ begX, begY, endX, endY }; - renderTarget->FillRectangle(&r, b); break; } - } } if (geometryPointsCount) diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index ab7a16d3c9f..dea8d82108c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -451,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; From 710eb1bc657bed31238eb899f088aabc3fa6d632 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 03:01:21 +0100 Subject: [PATCH 3/5] custom glyphs -> builtin glyphs --- dep/Console/winconp.h | 27 ++++++++++--------- src/cascadia/TerminalControl/ControlCore.cpp | 4 +-- src/cascadia/TerminalControl/ControlCore.h | 2 +- .../TerminalControl/IControlSettings.idl | 2 +- .../TerminalSettingsEditor/Appearances.h | 2 +- .../TerminalSettingsEditor/Appearances.idl | 2 +- .../TerminalSettingsEditor/Appearances.xaml | 12 ++++----- .../Resources/en-US/Resources.resw | 8 +++--- .../TerminalSettingsModel/FontConfig.idl | 2 +- .../TerminalSettingsModel/MTSMSettings.h | 2 +- .../TerminalSettings.cpp | 2 +- .../TerminalSettingsModel/TerminalSettings.h | 2 +- src/cascadia/inc/ControlProperties.h | 2 +- src/host/screenInfo.cpp | 4 +-- src/host/settings.cpp | 4 +-- src/host/settings.hpp | 4 +-- src/propslib/RegistrySerialization.cpp | 2 +- src/renderer/atlas/AtlasEngine.api.cpp | 2 +- src/renderer/atlas/AtlasEngine.cpp | 10 +++---- src/renderer/atlas/AtlasEngine.h | 6 ++--- src/renderer/atlas/AtlasEngine.r.cpp | 6 ++--- src/renderer/atlas/BackendD3D.cpp | 10 +++---- src/renderer/atlas/BackendD3D.h | 4 +-- .../{CustomGlyphs.cpp => BuiltinGlyphs.cpp} | 8 +++--- .../atlas/{CustomGlyphs.h => BuiltinGlyphs.h} | 4 +-- src/renderer/atlas/atlas.vcxproj | 6 ++--- src/renderer/atlas/common.h | 2 +- src/renderer/base/FontInfoDesired.cpp | 8 +++--- src/renderer/inc/FontInfoDesired.hpp | 6 ++--- 29 files changed, 79 insertions(+), 76 deletions(-) rename src/renderer/atlas/{CustomGlyphs.cpp => BuiltinGlyphs.cpp} (99%) rename src/renderer/atlas/{CustomGlyphs.h => BuiltinGlyphs.h} (82%) diff --git a/dep/Console/winconp.h b/dep/Console/winconp.h index c9f94e30a0e..23fba76c144 100644 --- a/dep/Console/winconp.h +++ b/dep/Console/winconp.h @@ -85,22 +85,25 @@ GetConsoleKeyboardLayoutNameW( #define CONSOLE_REGISTRY_SCROLLSCALE L"ScrollScale" // V2 console settings -#define CONSOLE_REGISTRY_ALLOW_ALTF4_CLOSE L"AllowAltF4Close" -#define CONSOLE_REGISTRY_COPYCOLOR L"CopyColor" -#define CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED L"CtrlKeyShortcutsDisabled" -#define CONSOLE_REGISTRY_CURSORCOLOR L"CursorColor" -#define CONSOLE_REGISTRY_CURSORTYPE L"CursorType" -#define CONSOLE_REGISTRY_CUSTOM_GLYPHS L"CustomGlyphs" -#define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" -#define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" -#define CONSOLE_REGISTRY_FILTERONPASTE L"FilterOnPaste" #define CONSOLE_REGISTRY_FORCEV2 L"ForceV2" -#define CONSOLE_REGISTRY_INTERCEPTCOPYPASTE L"InterceptCopyPaste" #define CONSOLE_REGISTRY_LINESELECTION L"LineSelection" +#define CONSOLE_REGISTRY_FILTERONPASTE L"FilterOnPaste" #define CONSOLE_REGISTRY_LINEWRAP L"LineWrap" -#define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" -#define CONSOLE_REGISTRY_USEDX L"UseDx" +#define CONSOLE_REGISTRY_CTRLKEYSHORTCUTS_DISABLED L"CtrlKeyShortcutsDisabled" +#define CONSOLE_REGISTRY_ALLOW_ALTF4_CLOSE L"AllowAltF4Close" #define CONSOLE_REGISTRY_VIRTTERM_LEVEL L"VirtualTerminalLevel" + +#define CONSOLE_REGISTRY_CURSORTYPE L"CursorType" +#define CONSOLE_REGISTRY_CURSORCOLOR L"CursorColor" + +#define CONSOLE_REGISTRY_INTERCEPTCOPYPASTE L"InterceptCopyPaste" + +#define CONSOLE_REGISTRY_COPYCOLOR L"CopyColor" +#define CONSOLE_REGISTRY_USEDX L"UseDx" + +#define CONSOLE_REGISTRY_DEFAULTFOREGROUND L"DefaultForeground" +#define CONSOLE_REGISTRY_DEFAULTBACKGROUND L"DefaultBackground" +#define CONSOLE_REGISTRY_TERMINALSCROLLING L"TerminalScrolling" // end V2 console settings /* diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4e8453e3b35..97d06e1ece6 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -869,7 +869,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto lock = _terminal->LockForWriting(); - _customGlyphs = _settings->CustomGlyphs(); + _builtinGlyphs = _settings->EnableBuiltinGlyphs(); _cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str()); _cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str()); _runtimeOpacity = std::nullopt; @@ -1048,7 +1048,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false }; _actualFontFaceName = { fontFace }; - _desiredFont.SetCustomGlyphs(_customGlyphs); + _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 d58be349ffa..de7a611e1fa 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -316,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation FontInfoDesired _desiredFont; FontInfo _actualFont; winrt::hstring _actualFontFaceName; - bool _customGlyphs = true; + bool _builtinGlyphs = true; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 4f27bae15b2..22354d4e448 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -43,7 +43,7 @@ namespace Microsoft.Terminal.Control String Padding { get; }; Windows.Foundation.Collections.IMap FontFeatures { get; }; Windows.Foundation.Collections.IMap FontAxes { get; }; - Boolean CustomGlyphs { 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 366d8660fd2..4f220ef5e78 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -87,7 +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(), CustomGlyphs); + 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 e84fd08cdb5..ddab88a6beb 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -41,7 +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, CustomGlyphs); + 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 e0748cc1b58..f1f13d8fbd6 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -287,12 +287,12 @@ - - - + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 70d8e2015e8..4ef75723213 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -886,13 +886,13 @@ 1.2 "1.2" is a decimal number. - - Custom Glyphs + + 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_CustomGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification. + A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification. Font weight diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.idl b/src/cascadia/TerminalSettingsModel/FontConfig.idl index aa1aaea8e89..995e7c6a651 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.idl +++ b/src/cascadia/TerminalSettingsModel/FontConfig.idl @@ -20,7 +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, CustomGlyphs); + 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 d5d9e32a65e..4ea3310f8b4 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -115,7 +115,7 @@ Author(s): X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \ X(IFontAxesMap, FontAxes, "axes") \ X(IFontFeatureMap, FontFeatures, "features") \ - X(bool, CustomGlyphs, "customGlyphs", true) \ + 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 ef4ac132ed6..8031f03eb4b 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -289,7 +289,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _FontWeight = fontInfo.FontWeight(); _FontFeatures = fontInfo.FontFeatures(); _FontAxes = fontInfo.FontAxes(); - _CustomGlyphs = fontInfo.CustomGlyphs(); + _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 5d11f5d299d..a9cc3d191ae 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -129,7 +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, CustomGlyphs, true); + 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 974d009622c..90253d0e1f9 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -63,7 +63,7 @@ X(winrt::Windows::UI::Text::FontWeight, FontWeight) \ X(IFontFeatureMap, FontFeatures) \ X(IFontAxesMap, FontAxes) \ - X(bool, CustomGlyphs, true) \ + 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 3a93569d11f..9b0ff2ca4e4 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -64,7 +64,7 @@ SCREEN_INFORMATION::SCREEN_INFORMATION( { OutputMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; } - _desiredFont.SetCustomGlyphs(gci.GetCustomGlyphs()); + _desiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); } // Routine Description: @@ -543,7 +543,7 @@ void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont) auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); FontInfoDesired fiDesiredFont(*pfiNewFont); - fiDesiredFont.SetCustomGlyphs(gci.GetCustomGlyphs()); + fiDesiredFont.SetEnableBuiltinGlyphs(gci.GetEnableBuiltinGlyphs()); GetDesiredFont() = fiDesiredFont; diff --git a/src/host/settings.cpp b/src/host/settings.cpp index 189f0216889..0239897a2fc 100644 --- a/src/host/settings.cpp +++ b/src/host/settings.cpp @@ -777,7 +777,7 @@ bool Settings::GetCopyColor() const noexcept return _fCopyColor; } -bool Settings::GetCustomGlyphs() const noexcept +bool Settings::GetEnableBuiltinGlyphs() const noexcept { - return _fCustomGlyphs; + return _fEnableBuiltinGlyphs; } diff --git a/src/host/settings.hpp b/src/host/settings.hpp index 9b185f467f4..41e0f8d4aaa 100644 --- a/src/host/settings.hpp +++ b/src/host/settings.hpp @@ -178,7 +178,7 @@ class Settings UseDx GetUseDx() const noexcept; bool GetCopyColor() const noexcept; - bool GetCustomGlyphs() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; private: RenderSettings _renderSettings; @@ -222,7 +222,7 @@ class Settings DWORD _dwVirtTermLevel; UseDx _fUseDx; bool _fCopyColor; - bool _fCustomGlyphs = true; + bool _fEnableBuiltinGlyphs = true; // this is used for the special STARTF_USESIZE mode. bool _fUseWindowSizePixels; diff --git a/src/propslib/RegistrySerialization.cpp b/src/propslib/RegistrySerialization.cpp index 064d9160517..15e81d40d8e 100644 --- a/src/propslib/RegistrySerialization.cpp +++ b/src/propslib/RegistrySerialization.cpp @@ -63,7 +63,7 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa { _RegPropertyType::Dword, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) }, { _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) }, #if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED - { _RegPropertyType::Boolean, CONSOLE_REGISTRY_CUSTOM_GLYPHS, SET_FIELD_AND_SIZE(_fCustomGlyphs) }, + { _RegPropertyType::Boolean, L"EnableBuiltinGlyphs", SET_FIELD_AND_SIZE(_fEnableBuiltinGlyphs) }, #endif // Special cases that are handled manually in Registry::LoadFromRegistry: diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index a0d95e49851..fa7290371e8 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -775,6 +775,6 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo fontMetrics->doubleUnderline[1] = { doubleUnderlinePosBottomU16, thinLineWidthU16 }; fontMetrics->overline = { 0, underlineWidthU16 }; - fontMetrics->customGlyphs = fontInfoDesired.GetCustomGlyphs(); + fontMetrics->builtinGlyphs = fontInfoDesired.GetEnableBuiltinGlyphs(); } } diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 29443744de4..2bee5fe2281 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -7,7 +7,7 @@ #include #include "Backend.h" -#include "CustomGlyphs.h" +#include "BuiltinGlyphs.h" #include "DWriteTextAnalysis.h" #include "../../interactivity/win32/CustomWindowMessages.h" @@ -677,7 +677,7 @@ void AtlasEngine::_flushBufferLine() size_t segmentEnd = 0; bool custom = false; - if (!_hackWantsCustomGlyphs) + if (!_hackWantsBuiltinGlyphs) { _mapRegularText(0, len); return; @@ -695,7 +695,7 @@ void AtlasEngine::_flushBufferLine() codepoint = til::combine_surrogates(codepoint, beg[i++]); } - const auto c = CustomGlyphs::IsCustomGlyph(codepoint) || CustomGlyphs::IsSoftFontChar(codepoint); + const auto c = BuiltinGlyphs::IsCustomGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint); if (custom != c) { break; @@ -708,7 +708,7 @@ void AtlasEngine::_flushBufferLine() { if (custom) { - _mapCustomGlyphs(segmentBeg, segmentEnd); + _mapBuiltinGlyphs(segmentBeg, segmentEnd); } else { @@ -806,7 +806,7 @@ void AtlasEngine::_mapRegularText(size_t offBeg, size_t offEnd) } } -void AtlasEngine::_mapCustomGlyphs(size_t offBeg, size_t offEnd) +void AtlasEngine::_mapBuiltinGlyphs(size_t offBeg, size_t offEnd) { auto& row = *_p.rows[_api.lastPaintBufferLineCoord.y]; auto initialIndicesCount = row.glyphIndices.size(); diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index d7952a45f02..48717b4afef 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -87,7 +87,7 @@ namespace Microsoft::Console::Render::Atlas void _recreateCellCountDependentResources(); void _flushBufferLine(); void _mapRegularText(size_t offBeg, size_t offEnd); - void _mapCustomGlyphs(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); @@ -119,7 +119,7 @@ namespace Microsoft::Console::Render::Atlas std::unique_ptr _b; RenderingPayload _p; - // _p.s->font->customGlyphs is the setting which decides whether we should map box drawing glyphs to + // _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 @@ -127,7 +127,7 @@ namespace Microsoft::Console::Render::Atlas // // 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 _hackWantsCustomGlyphs = true; + bool _hackWantsBuiltinGlyphs = true; bool _hackTriggerRedrawAll = false; struct ApiState diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index 9deb63434a0..c8a451746f4 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -280,10 +280,10 @@ void AtlasEngine::_recreateBackend() // EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.). _p.MarkAllAsDirty(); - const auto hackWantsCustomGlyphs = _p.s->font->customGlyphs && !d2dMode; - if (_hackWantsCustomGlyphs != hackWantsCustomGlyphs) + const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode; + if (_hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs) { - _hackWantsCustomGlyphs = hackWantsCustomGlyphs; + _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; _hackTriggerRedrawAll = true; } } diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index e0edfbc66bf..6c6562e03d8 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -11,7 +11,7 @@ #include #include -#include "CustomGlyphs.h" +#include "BuiltinGlyphs.h" #include "dwrite.h" #include "../../types/inc/ColorFix.hpp" @@ -739,7 +739,7 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p) glyphs.clear(); } } - for (auto& glyphs : _customGlyphs.glyphs) + for (auto& glyphs : _builtinGlyphs.glyphs) { glyphs.clear(); } @@ -999,7 +999,7 @@ void BackendD3D::_drawText(RenderingPayload& p) const auto fontFace = m.fontFace.get(); // The lack of a fontFace indicates a soft font. - AtlasFontFaceEntry* fontFaceEntry = &_customGlyphs; + AtlasFontFaceEntry* fontFaceEntry = &_builtinGlyphs; if (fontFace) [[likely]] { fontFaceEntry = _glyphAtlasMap.insert(fontFace).first; @@ -1401,7 +1401,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawCustomGlyph(const RenderingPayload _drawGlyphAtlasAllocate(p, rect); _d2dBeginDrawing(); - if (CustomGlyphs::IsSoftFontChar(glyphIndex)) + if (BuiltinGlyphs::IsSoftFontChar(glyphIndex)) { _drawSoftFontGlyph(p, rect, glyphIndex); } @@ -1413,7 +1413,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawCustomGlyph(const RenderingPayload static_cast(rect.x + rect.w), static_cast(rect.y + rect.h), }; - CustomGlyphs::DrawCustomGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); + BuiltinGlyphs::DrawCustomGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); } const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 361cd4b5aa2..96d477e56d2 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -11,7 +11,7 @@ namespace Microsoft::Console::Render::Atlas { - namespace CustomGlyphs + namespace BuiltinGlyphs { union Instruction; } @@ -260,7 +260,7 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr _glyphAtlas; wil::com_ptr _glyphAtlasView; til::linear_flat_set _glyphAtlasMap; - AtlasFontFaceEntry _customGlyphs; + AtlasFontFaceEntry _builtinGlyphs; Buffer _rectPackerData; stbrp_context _rectPacker{}; til::CoordType _ligatureOverhangTriggerLeft = 0; diff --git a/src/renderer/atlas/CustomGlyphs.cpp b/src/renderer/atlas/BuiltinGlyphs.cpp similarity index 99% rename from src/renderer/atlas/CustomGlyphs.cpp rename to src/renderer/atlas/BuiltinGlyphs.cpp index e819c9fc93f..4e52c3a03c4 100644 --- a/src/renderer/atlas/CustomGlyphs.cpp +++ b/src/renderer/atlas/BuiltinGlyphs.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT license. #include "pch.h" -#include "CustomGlyphs.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). @@ -12,7 +12,7 @@ #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::CustomGlyphs; +using namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs; union Instruction { @@ -1053,7 +1053,7 @@ constexpr bool Powerline_IsMapped(char32_t codepoint) // 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 CustomGlyphs::IsCustomGlyph(char32_t codepoint) noexcept +bool BuiltinGlyphs::IsCustomGlyph(char32_t codepoint) noexcept { return BoxDrawing_IsMapped(codepoint) || Powerline_IsMapped(codepoint); } @@ -1122,7 +1122,7 @@ static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext return bitmapBrush; } -void CustomGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) +void BuiltinGlyphs::DrawCustomGlyph(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([&]() { diff --git a/src/renderer/atlas/CustomGlyphs.h b/src/renderer/atlas/BuiltinGlyphs.h similarity index 82% rename from src/renderer/atlas/CustomGlyphs.h rename to src/renderer/atlas/BuiltinGlyphs.h index 471e3abc1c8..b5fbf5c359c 100644 --- a/src/renderer/atlas/CustomGlyphs.h +++ b/src/renderer/atlas/BuiltinGlyphs.h @@ -5,12 +5,12 @@ #include "common.h" -namespace Microsoft::Console::Render::Atlas::CustomGlyphs +namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs { bool IsCustomGlyph(char32_t codepoint) noexcept; void DrawCustomGlyph(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 CustomGlyphs.cpp. + // 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 07ea407b28e..a2faefce901 100644 --- a/src/renderer/atlas/atlas.vcxproj +++ b/src/renderer/atlas/atlas.vcxproj @@ -16,7 +16,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -101,4 +101,4 @@ $(SolutionDir)\oss\stb;$(OutDir)$(ProjectName);%(AdditionalIncludeDirectories) - \ No newline at end of file + diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index dea8d82108c..71010b31f77 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -360,7 +360,7 @@ namespace Microsoft::Console::Render::Atlas u16 dpi = 96; AntialiasingMode antialiasingMode = DefaultAntialiasingMode; - bool customGlyphs = false; + bool builtinGlyphs = false; std::vector softFontPattern; til::size softFontCellSize; diff --git a/src/renderer/base/FontInfoDesired.cpp b/src/renderer/base/FontInfoDesired.cpp index c9a83d0afd7..b578960c812 100644 --- a/src/renderer/base/FontInfoDesired.cpp +++ b/src/renderer/base/FontInfoDesired.cpp @@ -29,9 +29,9 @@ void FontInfoDesired::SetCellSize(const CSSLengthPercentage& cellWidth, const CS _cellHeight = cellHeight; } -void FontInfoDesired::SetCustomGlyphs(bool customGlyphs) noexcept +void FontInfoDesired::SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept { - _customGlyphs = customGlyphs; + _builtinGlyphs = builtinGlyphs; } const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept @@ -44,9 +44,9 @@ const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept return _cellHeight; } -bool FontInfoDesired::GetCustomGlyphs() const noexcept +bool FontInfoDesired::GetEnableBuiltinGlyphs() const noexcept { - return _customGlyphs; + return _builtinGlyphs; } float FontInfoDesired::GetFontSize() const noexcept diff --git a/src/renderer/inc/FontInfoDesired.hpp b/src/renderer/inc/FontInfoDesired.hpp index 06dbdf9c29b..a58425b4118 100644 --- a/src/renderer/inc/FontInfoDesired.hpp +++ b/src/renderer/inc/FontInfoDesired.hpp @@ -35,11 +35,11 @@ class FontInfoDesired : public FontInfoBase bool operator==(const FontInfoDesired& other) = delete; void SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept; - void SetCustomGlyphs(bool customGlyphs) noexcept; + void SetEnableBuiltinGlyphs(bool builtinGlyphs) noexcept; const CSSLengthPercentage& GetCellWidth() const noexcept; const CSSLengthPercentage& GetCellHeight() const noexcept; - bool GetCustomGlyphs() const noexcept; + bool GetEnableBuiltinGlyphs() const noexcept; float GetFontSize() const noexcept; til::size GetEngineSize() const noexcept; bool IsDefaultRasterFont() const noexcept; @@ -49,5 +49,5 @@ class FontInfoDesired : public FontInfoBase float _fontSize; CSSLengthPercentage _cellWidth; CSSLengthPercentage _cellHeight; - bool _customGlyphs = false; + bool _builtinGlyphs = false; }; From ac1c8b694f0741bd9d3485c4bd8404f75a41424e Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 03:02:59 +0100 Subject: [PATCH 4/5] Fix builtinGlyphs setting hot-reload --- src/renderer/atlas/AtlasEngine.cpp | 2 ++ src/renderer/atlas/AtlasEngine.h | 1 + src/renderer/atlas/AtlasEngine.r.cpp | 8 +++----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 2bee5fe2281..26b111e38a3 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -611,6 +611,8 @@ void AtlasEngine::_recreateFontDependentResources() _api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() }; } } + + _hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D; } void AtlasEngine::_recreateCellCountDependentResources() diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 48717b4afef..a32fa4dc2fc 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -127,6 +127,7 @@ namespace Microsoft::Console::Render::Atlas // // 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; diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp index c8a451746f4..0c546a8dceb 100644 --- a/src/renderer/atlas/AtlasEngine.r.cpp +++ b/src/renderer/atlas/AtlasEngine.r.cpp @@ -281,11 +281,9 @@ void AtlasEngine::_recreateBackend() _p.MarkAllAsDirty(); const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !d2dMode; - if (_hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs) - { - _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; - _hackTriggerRedrawAll = true; - } + _hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs; + _hackIsBackendD2D = d2dMode; + _hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs; } void AtlasEngine::_handleSwapChainUpdate() From 7fc081a7c05a2cf9204704085e6619cf7a12f196 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 21 Feb 2024 21:28:10 +0100 Subject: [PATCH 5/5] More CustomGlyphs -> BuiltinGlyphs, Add Comment, Better patterns --- src/renderer/atlas/AtlasEngine.cpp | 2 +- src/renderer/atlas/BackendD3D.cpp | 13 +++++++++---- src/renderer/atlas/BackendD3D.h | 7 +------ src/renderer/atlas/BuiltinGlyphs.cpp | 18 +++++++++--------- src/renderer/atlas/BuiltinGlyphs.h | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 26b111e38a3..01403ef4b38 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -697,7 +697,7 @@ void AtlasEngine::_flushBufferLine() codepoint = til::combine_surrogates(codepoint, beg[i++]); } - const auto c = BuiltinGlyphs::IsCustomGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint); + const auto c = BuiltinGlyphs::IsBuiltinGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint); if (custom != c) { break; diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 6c6562e03d8..23db73bded4 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1009,10 +1009,15 @@ void BackendD3D::_drawText(RenderingPayload& p) while (x < glyphsTo) { + size_t dx = 1; u32 glyphIndex = row->glyphIndices[x]; + + // 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)) { glyphIndex = til::combine_surrogates(glyphIndex, row->glyphIndices[x + 1]); + dx = 2; } auto glyphEntry = glyphs.lookup(glyphIndex); @@ -1048,7 +1053,7 @@ void BackendD3D::_drawText(RenderingPayload& p) } baselineX += row->glyphAdvances[x]; - ++x; + x += dx; } } @@ -1171,7 +1176,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyph(const RenderingPayload& p, c // The lack of a fontFace indicates a soft font. if (!fontFaceEntry.fontFace) { - return _drawCustomGlyph(p, row, fontFaceEntry, glyphIndex); + return _drawBuiltinGlyph(p, row, fontFaceEntry, glyphIndex); } const auto glyphIndexU16 = static_cast(glyphIndex); @@ -1383,7 +1388,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawGlyph(const RenderingPayload& p, c return glyphEntry; } -BackendD3D::AtlasGlyphEntry* BackendD3D::_drawCustomGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) +BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex) { auto baseline = p.s->font->baseline; stbrp_rect rect{ @@ -1413,7 +1418,7 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawCustomGlyph(const RenderingPayload static_cast(rect.x + rect.w), static_cast(rect.y + rect.h), }; - BuiltinGlyphs::DrawCustomGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); + BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex); } const auto glyphEntry = _drawGlyphAllocateEntry(row, fontFaceEntry, glyphIndex); diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 96d477e56d2..fb34f9f71c0 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -11,11 +11,6 @@ namespace Microsoft::Console::Render::Atlas { - namespace BuiltinGlyphs - { - union Instruction; - } - struct BackendD3D : IBackend { BackendD3D(const RenderingPayload& p); @@ -218,7 +213,7 @@ namespace Microsoft::Console::Render::Atlas void _drawText(RenderingPayload& p); ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y); [[nodiscard]] ATLAS_ATTR_COLD AtlasGlyphEntry* _drawGlyph(const RenderingPayload& p, const ShapedRow& row, AtlasFontFaceEntry& fontFaceEntry, u32 glyphIndex); - AtlasGlyphEntry* _drawCustomGlyph(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); diff --git a/src/renderer/atlas/BuiltinGlyphs.cpp b/src/renderer/atlas/BuiltinGlyphs.cpp index 4e52c3a03c4..541d5690324 100644 --- a/src/renderer/atlas/BuiltinGlyphs.cpp +++ b/src/renderer/atlas/BuiltinGlyphs.cpp @@ -1053,7 +1053,7 @@ constexpr bool Powerline_IsMapped(char32_t codepoint) // 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::IsCustomGlyph(char32_t codepoint) noexcept +bool BuiltinGlyphs::IsBuiltinGlyph(char32_t codepoint) noexcept { return BoxDrawing_IsMapped(codepoint) || Powerline_IsMapped(codepoint); } @@ -1079,10 +1079,10 @@ static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext // clang-format off static constexpr u32 shades[3][size * size] = { { - _, _, _, w, - _, _, w, _, - _, w, _, _, w, _, _, _, + w, _, _, _, + _, _, w, _, + _, _, w, _, }, { w, _, w, _, @@ -1091,10 +1091,10 @@ static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext _, 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 @@ -1122,7 +1122,7 @@ static wil::com_ptr createShadedBitmapBrush(ID2D1DeviceContext return bitmapBrush; } -void BuiltinGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint) +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([&]() { @@ -1132,7 +1132,7 @@ void BuiltinGlyphs::DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* r const auto instructions = GetInstructions(codepoint); if (!instructions) { - assert(false); // If everything in AtlasEngine works correctly, then this function should not get called when !IsCustomGlyph(codepoint). + assert(false); // If everything in AtlasEngine works correctly, then this function should not get called when !IsBuiltinGlyph(codepoint). renderTarget->Clear(nullptr); return; } diff --git a/src/renderer/atlas/BuiltinGlyphs.h b/src/renderer/atlas/BuiltinGlyphs.h index b5fbf5c359c..f399653893c 100644 --- a/src/renderer/atlas/BuiltinGlyphs.h +++ b/src/renderer/atlas/BuiltinGlyphs.h @@ -7,8 +7,8 @@ namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs { - bool IsCustomGlyph(char32_t codepoint) noexcept; - void DrawCustomGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint); + 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