Add support for double-width/double-height lines in conhost (#8664)

This PR adds support for the VT line rendition attributes, which allow
for double-width and double-height line renditions. These renditions are
enabled with the `DECDWL` (double-width line) and `DECDHL`
(double-height line) escape sequences. Both reset to the default
rendition with the `DECSWL` (single-width line) escape sequence. For now
this functionality is only supported by the GDI renderer in conhost.

There are a lot of changes, so this is just a general overview of the
main areas affected.

Previously it was safe to assume that the screen had a fixed width, at
least for a given point in time. But now we need to deal with the
possibility of different lines have different widths, so all the
functions that are constrained by the right border (text wrapping,
cursor movement operations, and sequences like `EL` and `ICH`) now need
to lookup the width of the active line in order to behave correctly.

Similarly it used to be safe to assume that buffer and screen
coordinates were the same thing, but that is no longer true. Lots of
places now need to translate back and forth between coordinate systems
dependent on the line rendition. This includes clipboard handling, the
conhost color selection and search, accessibility location tracking and
screen reading, IME editor positioning, "snapping" the viewport, and of
course all the rendering calculations.

For the rendering itself, I've had to introduce a new
`PrepareLineTransform` method that the render engines can use to setup
the necessary transform matrix for a given line rendition. This is also
now used to handle the horizontal viewport offset, since that could no
longer be achieved just by changing the target coordinates (on a double
width line, the viewport offset may be halfway through a character).

I've also had to change the renderer's existing `InvalidateCursor`
method to take a `SMALL_RECT` rather than a `COORD`, to allow for the
cursor being a variable width. Technically this was already a problem,
because the cursor could occupy two screen cells when over a
double-width character, but now it can be anything between one and four
screen cells (e.g. a double-width character on the double-width line).

In terms of architectural changes, there is now a new `lineRendition`
field in the `ROW` class that keeps track of the line rendition for each
row, and several new methods in the `ROW` and `TextBuffer` classes for
manipulating that state. This includes a few helper methods for handling
the various issues discussed above, e.g. position clamping and
translating between coordinate systems.

## Validation Steps Performed

I've manually confirmed all the double-width and double-height tests in
_Vttest_ are now working as expected, and the _VT100 Torture Test_ now
renders correctly (at least the line rendition aspects). I've also got
my own test scripts that check many of the line rendition boundary cases
and have confirmed that those are now passing.

I've manually tested as many areas of the conhost UI that I could think
of, that might be affected by line rendition, including things like
searching, selection, copying, and color highlighting. For
accessibility, I've confirmed that the _Magnifier_ and _Narrator_
correctly handle double-width lines. And I've also tested the Japanese
IME, which while not perfect, is at least useable.

Closes #7865
This commit is contained in:
James Holderness 2021-02-18 05:44:50 +00:00 committed by GitHub
parent 72cbe59078
commit 4c53c595e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 660 additions and 107 deletions

View file

@ -521,6 +521,8 @@ DECAUPSS
DECAWM
DECCKM
DECCOLM
DECDHL
DECDWL
DECEKBD
DECID
DECKPAM
@ -557,6 +559,7 @@ DECSR
decstandar
DECSTBM
DECSTR
DECSWL
DECTCEM
Dedupe
deduplicated
@ -2825,6 +2828,7 @@ Xes
XES
xff
XFile
XFORM
XManifest
XMath
XMFLOAT

View file

@ -0,0 +1,36 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- LineRendition.hpp
Abstract:
- Enumerated type for the VT line rendition attribute. This determines the
width and height scaling with which each line is rendered.
--*/
#pragma once
enum class LineRendition
{
SingleWidth,
DoubleWidth,
DoubleHeightTop,
DoubleHeightBottom
};
constexpr SMALL_RECT ScreenToBufferLine(const SMALL_RECT& line, const LineRendition lineRendition)
{
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
}
constexpr SMALL_RECT BufferToScreenLine(const SMALL_RECT& line, const LineRendition lineRendition)
{
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
const SHORT scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
}

View file

@ -21,6 +21,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
_rowWidth{ rowWidth },
_charRow{ rowWidth, this },
_attrRow{ rowWidth, fillAttribute },
_lineRendition{ LineRendition::SingleWidth },
_wrapForced{ false },
_doubleBytePadded{ false },
_pParent{ pParent }
@ -35,6 +36,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
// - <none>
bool ROW::Reset(const TextAttribute Attr)
{
_lineRendition = LineRendition::SingleWidth;
_wrapForced = false;
_doubleBytePadded = false;
_charRow.Reset();

View file

@ -21,6 +21,7 @@ Revision History:
#pragma once
#include "AttrRow.hpp"
#include "LineRendition.hpp"
#include "OutputCell.hpp"
#include "OutputCellIterator.hpp"
#include "CharRow.hpp"
@ -48,6 +49,9 @@ public:
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
SHORT GetId() const noexcept { return _id; }
void SetId(const SHORT id) noexcept { _id = id; }
@ -70,6 +74,7 @@ public:
private:
CharRow _charRow;
ATTR_ROW _attrRow;
LineRendition _lineRendition;
SHORT _id;
unsigned short _rowWidth;
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line

View file

@ -38,6 +38,7 @@
<ClInclude Include="..\cursor.h" />
<ClInclude Include="..\DbcsAttribute.hpp" />
<ClInclude Include="..\ICharRow.hpp" />
<ClInclude Include="..\LineRendition.hpp" />
<ClInclude Include="..\OutputCell.hpp" />
<ClInclude Include="..\OutputCellIterator.hpp" />
<ClInclude Include="..\OutputCellRect.hpp" />

View file

@ -97,7 +97,12 @@ bool Search::FindNext()
// - Takes the found word and selects it in the screen buffer
void Search::Select() const
{
_uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd);
// Convert buffer selection offsets into the equivalent screen coordinates
// required by SelectNewRegion, taking line renditions into account.
const auto& textBuffer = _uiaData.GetTextBuffer();
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
_uiaData.SelectNewRegion(selStart, selEnd);
}
// Routine Description:
@ -141,7 +146,10 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction)
const COORD textBufferEndPosition = uiaData.GetTextBufferEndPosition();
if (uiaData.IsSelectionActive())
{
auto anchor = uiaData.GetSelectionAnchor();
// Convert the screen position of the selection anchor into an equivalent
// buffer position to start searching, taking line rendition into account.
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
if (direction == Direction::Forward)
{
textBuffer.GetSize().IncrementInBoundsCircular(anchor);

View file

@ -290,13 +290,14 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
// We only need to compensate for leading bytes
if (dbcsAttribute.IsLeading())
{
short const sBufferWidth = GetSize().Width();
const auto cursorPosition = GetCursor().GetPosition();
const auto lineWidth = GetLineWidth(cursorPosition.Y);
// If we're about to lead on the last column in the row, we need to add a padding space
if (GetCursor().GetPosition().X == sBufferWidth - 1)
if (cursorPosition.X == lineWidth - 1)
{
// set that we're wrapping for double byte reasons
auto& row = GetRowByOffset(GetCursor().GetPosition().Y);
auto& row = GetRowByOffset(cursorPosition.Y);
row.SetDoubleBytePadded(true);
// then move the cursor forward and onto the next row
@ -496,7 +497,7 @@ bool TextBuffer::IncrementCursor()
// Cursor position is stored as logical array indices (starts at 0) for the window
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
const short iFinalColumnIndex = GetSize().RightInclusive();
const short iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
// Move the cursor one position to the right
GetCursor().IncrementXPosition(1);
@ -635,7 +636,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
// Return Value:
// - Coordinate position in screen coordinates of the character just before the cursor.
// - NOTE: Will return 0,0 if already in the top left corner
COORD TextBuffer::_GetPreviousFromCursor() const noexcept
COORD TextBuffer::_GetPreviousFromCursor() const
{
COORD coordPosition = GetCursor().GetPosition();
@ -649,11 +650,11 @@ COORD TextBuffer::_GetPreviousFromCursor() const noexcept
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
if (coordPosition.Y > 0)
{
// move the cursor to the right edge
coordPosition.X = GetSize().RightInclusive();
// and up one line
// move the cursor up one line
coordPosition.Y--;
// and to the right edge
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
}
}
@ -801,6 +802,78 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
_currentAttributes = currentAttributes;
}
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
{
const auto cursorPosition = GetCursor().GetPosition();
const auto rowIndex = cursorPosition.Y;
auto& row = GetRowByOffset(rowIndex);
if (row.GetLineRendition() != lineRendition)
{
row.SetLineRendition(lineRendition);
// If the line rendition has changed, the row can no longer be wrapped.
row.SetWrapForced(false);
// And if it's no longer single width, the right half of the row should be erased.
if (lineRendition != LineRendition::SingleWidth)
{
const auto fillChar = L' ';
auto fillAttrs = GetCurrentAttributes();
fillAttrs.SetStandardErase();
const size_t fillOffset = GetLineWidth(rowIndex);
const size_t fillLength = GetSize().Width() - fillOffset;
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
row.WriteCells(fillData, fillOffset, false);
// We also need to make sure the cursor is clamped within the new width.
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
}
_NotifyPaint(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
}
}
void TextBuffer::ResetLineRenditionRange(const size_t startRow, const size_t endRow)
{
for (auto row = startRow; row < endRow; row++)
{
GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth);
}
}
LineRendition TextBuffer::GetLineRendition(const size_t row) const
{
return GetRowByOffset(row).GetLineRendition();
}
bool TextBuffer::IsDoubleWidthLine(const size_t row) const
{
return GetLineRendition(row) != LineRendition::SingleWidth;
}
SHORT TextBuffer::GetLineWidth(const size_t row) const
{
// Use shift right to quickly divide the width by 2 for double width lines.
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
return GetSize().Width() >> scale;
}
COORD TextBuffer::ClampPositionWithinLine(const COORD position) const
{
const SHORT rightmostColumn = GetLineWidth(position.Y) - 1;
return { std::min(position.X, rightmostColumn), position.Y };
}
COORD TextBuffer::ScreenToBufferPosition(const COORD position) const
{
// Use shift right to quickly divide the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X >> scale, position.Y };
}
COORD TextBuffer::BufferToScreenPosition(const COORD position) const
{
// Use shift left to quickly multiply the X pos by 2 for double width lines.
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
return { position.X << scale, position.Y };
}
// Routine Description:
// - Resets the text contents of this buffer with the default character
// and the default current color attributes
@ -1425,9 +1498,11 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
// - blockSelection: when enabled, only get the rectangular text region,
// as opposed to the text extending to the left/right
// buffer margins
// - bufferCoordinates: when enabled, treat the coordinates as relative to
// the buffer rather than the screen.
// Return Value:
// - the delimiter class for the given char
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const
{
std::vector<SMALL_RECT> textRects;
@ -1461,6 +1536,13 @@ const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, b
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
}
// If we were passed screen coordinates, convert the given range into
// equivalent buffer offsets, taking line rendition into account.
if (!bufferCoordinates)
{
textRow = ScreenToBufferLine(textRow, GetLineRendition(row));
}
_ExpandTextRow(textRow);
textRects.emplace_back(textRow);
}
@ -2044,7 +2126,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
const short cOldRowsTotal = cOldLastChar.Y + 1;
const short cOldColsTotal = oldBuffer.GetSize().Width();
COORD cNewCursorPos = { 0 };
bool fFoundCursorPos = false;
@ -2056,9 +2137,19 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
{
// Fetch the row and its "right" which is the last printable character.
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
const CharRow& charRow = row.GetCharRow();
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
// If we're starting a new row, try and preserve the line rendition
// from the row in the original buffer.
const auto newBufferPos = newBuffer.GetCursor().GetPosition();
if (newBufferPos.X == 0)
{
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
newRow.SetLineRendition(row.GetLineRendition());
}
// There is a special case here. If the row has a "wrap"
// flag on it, but the right isn't equal to the width (one
// index past the final valid index in the row) then there

View file

@ -122,6 +122,16 @@ public:
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
void SetCurrentLineRendition(const LineRendition lineRendition);
void ResetLineRenditionRange(const size_t startRow, const size_t endRow);
LineRendition GetLineRendition(const size_t row) const;
bool IsDoubleWidthLine(const size_t row) const;
SHORT GetLineWidth(const size_t row) const;
COORD ClampPositionWithinLine(const COORD position) const;
COORD ScreenToBufferPosition(const COORD position) const;
COORD BufferToScreenPosition(const COORD position) const;
void Reset();
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
@ -141,7 +151,7 @@ public:
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
bool MoveToPreviousGlyph(til::point& pos) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const;
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
@ -212,7 +222,7 @@ private:
void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
COORD _GetPreviousFromCursor() const noexcept;
COORD _GetPreviousFromCursor() const;
void _SetWrapOnCurrentRow();
void _AdjustWrapOnCurrentRow(const bool fSet);

View file

@ -54,7 +54,7 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const noexcept
try
{
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection);
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection, false);
}
CATCH_LOG();
return result;

View file

@ -67,7 +67,8 @@ void CursorBlinker::FocusStart()
// - <none>
void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
{
Cursor& cursor = ScreenInfo.GetTextBuffer().GetCursor();
auto& buffer = ScreenInfo.GetTextBuffer();
auto& cursor = buffer.GetCursor();
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto* const _pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
@ -79,7 +80,9 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
// Update the cursor pos in USER so accessibility will work.
if (cursor.HasMoved())
{
const auto position = cursor.GetPosition();
// Convert the buffer position to the equivalent screen coordinates
// required by the notifier, taking line rendition into account.
const auto position = buffer.BufferToScreenPosition(cursor.GetPosition());
const auto viewport = ScreenInfo.GetViewport();
const auto fontSize = ScreenInfo.GetScreenFontSize();
cursor.SetHasMoved(false);

View file

@ -353,7 +353,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
const wchar_t* lpString = pwchRealUnicode;
const COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
// In VT mode, the width at which we wrap is determined by the line rendition attribute.
if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
{
coordScreenBufferSize.X = textBuffer.GetLineWidth(CursorPosition.Y);
}
while (*pcb < BufferSize)
{
@ -371,6 +376,11 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);
CursorPosition = cursor.GetPosition();
// In VT mode, we need to recalculate the width when moving to a new line.
if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
{
coordScreenBufferSize.X = textBuffer.GetLineWidth(CursorPosition.Y);
}
}
}

View file

@ -422,6 +422,10 @@ void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text,
// screen buffer and viewport positioning.
// Each conversion area write will adjust these to set up any subsequent calls to go onto the next line.
auto pos = screenInfo.GetTextBuffer().GetCursor().GetPosition();
// Convert the cursor buffer position to the equivalent screen
// coordinates, taking line rendition into account.
pos = screenInfo.GetTextBuffer().BufferToScreenPosition(pos);
const auto view = screenInfo.GetViewport();
// Set cursor position relative to viewport

View file

@ -704,13 +704,18 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
buffer.GetViewport().ToInclusive();
COORD delta{ 0 };
{
if (currentViewport.Left > position.X)
// When evaluating the X offset, we must convert the buffer position to
// equivalent screen coordinates, taking line rendition into account.
const auto lineRendition = buffer.GetTextBuffer().GetLineRendition(position.Y);
const auto screenPosition = BufferToScreenLine({ position.X, position.Y, position.X, position.Y }, lineRendition);
if (currentViewport.Left > screenPosition.Left)
{
delta.X = position.X - currentViewport.Left;
delta.X = screenPosition.Left - currentViewport.Left;
}
else if (currentViewport.Right < position.X)
else if (currentViewport.Right < screenPosition.Right)
{
delta.X = position.X - currentViewport.Right;
delta.X = screenPosition.Right - currentViewport.Right;
}
if (currentViewport.Top > position.Y)
@ -1410,6 +1415,10 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
{
cursorPosition.X = 0;
}
else
{
cursorPosition = textBuffer.ClampPositionWithinLine(cursorPosition);
}
return AdjustCursorPosition(screenInfo, cursorPosition, FALSE, nullptr);
}
@ -1427,7 +1436,8 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
const SMALL_RECT viewport = screenInfo.GetActiveBuffer().GetViewport().ToInclusive();
const COORD oldCursorPosition = screenInfo.GetTextBuffer().GetCursor().GetPosition();
const COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
newCursorPosition = screenInfo.GetTextBuffer().ClampPositionWithinLine(newCursorPosition);
// If the cursor is at the top of the viewport, we don't want to shift the viewport up.
// We want it to stay exactly where it is.

View file

@ -451,6 +451,13 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
{
const auto& view = remaining.at(i);
screenInfo.WriteRect(fillData, view);
// If we're scrolling an area that encompasses the full buffer width,
// then the filled rows should also have their line rendition reset.
if (view.Width() == buffer.Width() && destinationOriginGiven.X == 0)
{
screenInfo.GetTextBuffer().ResetLineRenditionRange(view.Top(), view.BottomExclusive());
}
}
}

View file

@ -125,7 +125,9 @@ bool ConhostInternalGetSet::SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BU
// - true if successful (see DoSrvSetConsoleCursorPosition). false otherwise.
bool ConhostInternalGetSet::SetConsoleCursorPosition(const COORD position)
{
return SUCCEEDED(ServiceLocator::LocateGlobals().api.SetConsoleCursorPositionImpl(_io.GetActiveOutputBuffer(), position));
auto& info = _io.GetActiveOutputBuffer();
const auto clampedPosition = info.GetTextBuffer().ClampPositionWithinLine(position);
return SUCCEEDED(ServiceLocator::LocateGlobals().api.SetConsoleCursorPositionImpl(info, clampedPosition));
}
// Routine Description:
@ -182,6 +184,48 @@ bool ConhostInternalGetSet::PrivateSetTextAttributes(const TextAttribute& attrs)
return true;
}
// Method Description:
// - Sets the line rendition attribute for the current row of the active screen
// buffer. This controls how character cells are scaled when the row is rendered.
// Arguments:
// - lineRendition: The new LineRendition attribute to use
// Return Value:
// - true if successful. false otherwise.
bool ConhostInternalGetSet::PrivateSetCurrentLineRendition(const LineRendition lineRendition)
{
auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
textBuffer.SetCurrentLineRendition(lineRendition);
return true;
}
// Method Description:
// - Resets the line rendition attribute to SingleWidth for a specified range
// of row numbers.
// Arguments:
// - startRow: The row number of first line to be modified
// - endRow: The row number following the last line to be modified
// Return Value:
// - true if successful. false otherwise.
bool ConhostInternalGetSet::PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow)
{
auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
textBuffer.ResetLineRenditionRange(startRow, endRow);
return true;
}
// Method Description:
// - Returns the number of cells that will fit on the specified row when
// rendered with its current line rendition.
// Arguments:
// - row: The row number of the line to measure
// Return Value:
// - the number of cells that will fit on the line
SHORT ConhostInternalGetSet::PrivateGetLineWidth(const size_t row) const
{
const auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
return textBuffer.GetLineWidth(row);
}
// Routine Description:
// - Connects the WriteConsoleInput API call directly into our Driver Message servicing call inside Conhost.exe
// Arguments:

View file

@ -65,6 +65,10 @@ public:
bool PrivateGetTextAttributes(TextAttribute& attrs) const override;
bool PrivateSetTextAttributes(const TextAttribute& attrs) override;
bool PrivateSetCurrentLineRendition(const LineRendition lineRendition) override;
bool PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow) override;
SHORT PrivateGetLineWidth(const size_t row) const override;
bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
size_t& eventsWritten) override;

View file

@ -2223,6 +2223,9 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
Write(fillData, fillPosition, false);
// Also reset the line rendition for the erased rows.
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
return S_OK;
}
@ -2395,7 +2398,9 @@ void SCREEN_INFORMATION::ClearTextData()
// - word boundary positions
std::pair<COORD, COORD> SCREEN_INFORMATION::GetWordBoundary(const COORD position) const
{
COORD clampedPosition = position;
// The position argument is in screen coordinates, but we need the
// equivalent buffer position, taking line rendition into account.
COORD clampedPosition = _textBuffer->ScreenToBufferPosition(position);
GetBufferSize().Clamp(clampedPosition);
COORD start{ clampedPosition };
@ -2464,6 +2469,12 @@ std::pair<COORD, COORD> SCREEN_INFORMATION::GetWordBoundary(const COORD position
}
}
}
// The calculated range is in buffer coordinates, but the caller is
// expecting screen offsets, so we have to convert these back again.
start = _textBuffer->BufferToScreenPosition(start);
end = _textBuffer->BufferToScreenPosition(end);
return { start, end };
}
@ -2541,6 +2552,8 @@ void SCREEN_INFORMATION::InitializeCursorRowAttributes()
auto fillAttributes = GetAttributes();
fillAttributes.SetStandardErase();
row.GetAttrRow().SetAttrToEnd(0, fillAttributes);
// The row should also be single width to start with.
row.SetLineRendition(LineRendition::SingleWidth);
}
}

View file

@ -59,7 +59,7 @@ std::vector<SMALL_RECT> Selection::GetSelectionRects() const
endSelectionAnchor.Y = (_coordSelectionAnchor.Y == _srSelectionRect.Top) ? _srSelectionRect.Bottom : _srSelectionRect.Top;
const auto blockSelection = !IsLineSelection();
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection);
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection, false);
}
// Routine Description:
@ -417,7 +417,7 @@ void Selection::ColorSelection(const COORD coordSelectionStart, const COORD coor
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& screenInfo = gci.GetActiveOutputBuffer();
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd);
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd, false, true);
for (const auto& rect : rectangles)
{
ColorSelection(rect, attr);

View file

@ -333,7 +333,7 @@ class SelectionTests
COORD startPos{ sTargetX, sTargetY };
COORD endPos{ base::ClampAdd(sTargetX, sLength), sTargetY };
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos);
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos, false, false);
VERIFY_ARE_EQUAL(static_cast<size_t>(1), selectionRects.size());
srSelection = selectionRects.at(0);

View file

@ -2324,7 +2324,7 @@ void TextBufferTests::GetTextRects()
COORD start{ 1, 0 };
COORD end{ 7, 4 };
const auto result = _buffer->GetTextRects(start, end, blockSelection);
const auto result = _buffer->GetTextRects(start, end, blockSelection, false);
VERIFY_ARE_EQUAL(expected.size(), result.size());
for (size_t i = 0; i < expected.size(); ++i)
{
@ -2370,7 +2370,7 @@ void TextBufferTests::GetText()
WriteLinesToBuffer(bufferText, *_buffer);
// simulate a selection from origin to {4,4}
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 4 }, blockSelection);
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 4 }, blockSelection, false);
std::wstring result = L"";
const auto textData = _buffer->GetText(includeCRLF, trimTrailingWhitespace, textRects).text;
@ -2471,7 +2471,7 @@ void TextBufferTests::GetText()
// |_____|
// simulate a selection from origin to {4,5}
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 5 }, blockSelection);
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 5 }, blockSelection, false);
std::wstring result = L"";

View file

@ -38,7 +38,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
[[nodiscard]] HRESULT BgfxEngine::InvalidateCursor(const COORD* const /*pcoordCursor*/) noexcept
[[nodiscard]] HRESULT BgfxEngine::InvalidateCursor(const SMALL_RECT* const /*psrRegion*/) noexcept
{
return S_OK;
}

View file

@ -34,7 +34,7 @@ namespace Microsoft::Console::Render
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;

View file

@ -41,6 +41,18 @@ HRESULT RenderEngineBase::PrepareRenderInfo(const RenderFrameInfo& /*info*/) noe
return S_FALSE;
}
HRESULT RenderEngineBase::ResetLineTransform() noexcept
{
return S_FALSE;
}
HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/,
const size_t /*targetRow*/,
const size_t /*viewportLeft*/) noexcept
{
return S_FALSE;
}
// Method Description:
// - By default, no one should need continuous redraw. It ruins performance
// in terms of CPU, memory, and battery life to just paint forever.

View file

@ -220,6 +220,18 @@ void Renderer::TriggerRedraw(const Viewport& region)
Viewport view = _viewport;
SMALL_RECT srUpdateRegion = region.ToExclusive();
// If the dirty region has double width lines, we need to double the size of
// the right margin to make sure all the affected cells are invalidated.
const auto& buffer = _pData->GetTextBuffer();
for (auto row = srUpdateRegion.Top; row < srUpdateRegion.Bottom; row++)
{
if (buffer.IsDoubleWidthLine(row))
{
srUpdateRegion.Right *= 2;
break;
}
}
if (view.TrimToViewport(&srUpdateRegion))
{
view.ConvertToOrigin(&srUpdateRegion);
@ -253,25 +265,34 @@ void Renderer::TriggerRedraw(const COORD* const pcoord)
// - <none>
void Renderer::TriggerRedrawCursor(const COORD* const pcoord)
{
Viewport view = _pData->GetViewport();
COORD updateCoord = *pcoord;
if (view.IsInBounds(updateCoord))
// We first need to make sure the cursor position is within the buffer,
// otherwise testing for a double width character can throw an exception.
const auto& buffer = _pData->GetTextBuffer();
if (buffer.GetSize().IsInBounds(*pcoord))
{
view.ConvertToOrigin(&updateCoord);
for (IRenderEngine* pEngine : _rgpEngines)
// We then calculate the region covered by the cursor. This requires
// converting the buffer coordinates to an equivalent range of screen
// cells for the cursor, taking line rendition into account.
const LineRendition lineRendition = buffer.GetLineRendition(pcoord->Y);
const SHORT cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
const SMALL_RECT cursorRect = { pcoord->X, pcoord->Y, pcoord->X + cursorWidth - 1, pcoord->Y };
Viewport cursorView = Viewport::FromInclusive(BufferToScreenLine(cursorRect, lineRendition));
// The region is clamped within the viewport boundaries and we only
// trigger a redraw if the region is not empty.
Viewport view = _pData->GetViewport();
cursorView = view.Clamp(cursorView);
if (cursorView.IsValid())
{
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateCoord));
// Double-wide cursors need to invalidate the right half as well.
if (_pData->IsCursorDoubleWidth())
const SMALL_RECT updateRect = view.ConvertToOrigin(cursorView).ToExclusive();
for (IRenderEngine* pEngine : _rgpEngines)
{
updateCoord.X++;
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateCoord));
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateRect));
}
}
_NotifyPaintFrame();
_NotifyPaintFrame();
}
}
}
@ -630,6 +651,11 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
gsl::span<const til::rectangle> dirtyAreas;
LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas));
// This is to make sure any transforms are reset when this paint is finished.
auto resetLineTransform = wil::scope_exit([&]() {
LOG_IF_FAILED(pEngine->ResetLineTransform());
});
for (const auto& dirtyRect : dirtyAreas)
{
auto dirty = Viewport::FromInclusive(dirtyRect);
@ -654,14 +680,19 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
{
// Calculate the boundaries of a single line. This is from the left to right edge of the dirty
// area in width and exactly 1 tall.
const auto bufferLine = Viewport::FromDimensions({ redraw.Left(), row }, { redraw.Width(), 1 });
const auto screenLine = SMALL_RECT{ redraw.Left(), row, redraw.RightInclusive(), row };
// Convert the screen coordinates of the line to an equivalent
// range of buffer cells, taking line rendition into account.
const auto lineRendition = buffer.GetLineRendition(row);
const auto bufferLine = Viewport::FromInclusive(ScreenToBufferLine(screenLine, lineRendition));
// Find where on the screen we should place this line information. This requires us to re-map
// the buffer-based origin of the line back onto the screen-based origin of the line
// For example, the screen might say we need to paint 1,1 because it is dirty but the viewport is actually looking
// at 13,26 relative to the buffer.
// This means that we need 14,27 out of the backing buffer to fill in the 1,1 cell of the screen.
const auto screenLine = Viewport::Offset(bufferLine, -view.Origin());
// the buffer-based origin of the line back onto the screen-based origin of the line.
// For example, the screen might say we need to paint line 1 because it is dirty but the viewport
// is actually looking at line 26 relative to the buffer. This means that we need line 27 out
// of the backing buffer to fill in line 1 of the screen.
const auto screenPosition = bufferLine.Origin() - COORD{ 0, view.Top() };
// Retrieve the cell information iterator limited to just this line we want to redraw.
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
@ -673,8 +704,11 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).WasWrapForced()) &&
(bufferLine.RightExclusive() == buffer.GetSize().Width());
// Prepare the appropriate line transform for the current row and viewport offset.
LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.Y, view.Left()));
// Ask the helper to paint through this specific line.
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
}
}
}
@ -959,11 +993,25 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin
// bottom of the viewport, the space that's not quite a full line in
// height. Since we don't draw that text, we shouldn't draw the cursor
// there either.
Viewport view = _pData->GetViewport();
if (view.IsInBounds(coordCursor))
// The cursor is never rendered as double height, so we don't care about
// the exact line rendition - only whether it's double width or not.
const auto doubleWidth = _pData->GetTextBuffer().IsDoubleWidthLine(coordCursor.Y);
const auto lineRendition = doubleWidth ? LineRendition::DoubleWidth : LineRendition::SingleWidth;
// We need to convert the screen coordinates of the viewport to an
// equivalent range of buffer cells, taking line rendition into account.
const auto view = ScreenToBufferLine(_pData->GetViewport().ToInclusive(), lineRendition);
// Note that we allow the X coordinate to be outside the left border by 1 position,
// because the cursor could still be visible if the focused character is double width.
const auto xInRange = coordCursor.X >= view.Left - 1 && coordCursor.X <= view.Right;
const auto yInRange = coordCursor.Y >= view.Top && coordCursor.Y <= view.Bottom;
if (xInRange && yInRange)
{
// Adjust cursor to viewport
view.ConvertToOrigin(&coordCursor);
// Adjust cursor Y offset to viewport.
// The viewport X offset is saved in the options and handled with a transform.
coordCursor.Y -= view.Top;
COLORREF cursorColor = _pData->GetCursorColor();
bool useColor = cursorColor != INVALID_COLOR;
@ -971,6 +1019,8 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin
// Build up the cursor parameters including position, color, and drawing options
CursorOptions options;
options.coordCursor = coordCursor;
options.viewportLeft = _pData->GetViewport().Left();
options.lineRendition = lineRendition;
options.ulCursorHeightPercent = _pData->GetCursorHeight();
options.cursorPixelWidth = _pData->GetCursorPixelWidth();
options.fIsDoubleWidth = _pData->IsCursorDoubleWidth();
@ -1165,14 +1215,20 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
// - A vector of rectangles representing the regions to select, line by line.
std::vector<SMALL_RECT> Renderer::_GetSelectionRects() const
{
const auto& buffer = _pData->GetTextBuffer();
auto rects = _pData->GetSelectionRects();
// Adjust rectangles to viewport
Viewport view = _pData->GetViewport();
std::vector<SMALL_RECT> result;
for (auto& rect : rects)
for (auto rect : rects)
{
// Convert buffer offsets to the equivalent range of screen cells
// expected by callers, taking line rendition into account.
const auto lineRendition = buffer.GetLineRendition(rect.Top());
rect = Viewport::FromInclusive(BufferToScreenLine(rect.ToInclusive(), lineRendition));
auto sr = view.ConvertToOrigin(rect).ToInclusive();
// hopefully temporary, we should be receiving the right selection sizes without correction.

View file

@ -1055,24 +1055,15 @@ try
CATCH_RETURN()
// Routine Description:
// - Invalidates one specific character coordinate
// - Invalidates the cells of the cursor
// Arguments:
// - pcoordCursor - single point in the character cell grid
// - psrRegion - the region covered by the cursor
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
try
[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
if (!_allInvalid)
{
_InvalidateRectangle(til::rectangle{ *pcoordCursor, til::size{ 1, 1 } });
}
return S_OK;
return Invalidate(psrRegion);
}
CATCH_RETURN()
// Routine Description:
// - Invalidates a rectangle describing a pixel area on the display

View file

@ -73,7 +73,7 @@ namespace Microsoft::Console::Render
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;

View file

@ -30,7 +30,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
@ -41,6 +41,11 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
const COORD coord,
@ -97,6 +102,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _FlushBufferLines() noexcept;
std::vector<RECT> cursorInvertRects;
XFORM cursorInvertTransform;
struct LineMetrics
{
@ -126,6 +132,9 @@ namespace Microsoft::Console::Render
COLORREF _lastBg;
bool _lastFontItalic;
XFORM _currentLineTransform;
LineRendition _currentLineRendition;
// Memory pooling to save alloc/free work to the OS for things
// frequently created and dropped.
// It's important the pool is first so it can be given to the others on construction.
@ -183,4 +192,11 @@ namespace Microsoft::Console::Render
HDC _debugContext;
#endif
};
constexpr XFORM IDENTITY_XFORM = { 1, 0, 0, 1 };
inline bool operator==(const XFORM& lhs, const XFORM& rhs) noexcept
{
return ::memcmp(&lhs, &rhs, sizeof(XFORM)) == 0;
};
}

View file

@ -81,13 +81,12 @@ HRESULT GdiEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept
// Routine Description:
// - Notifies us that the console has changed the position of the cursor.
// Arguments:
// - pcoordCursor - the new position of the cursor
// - psrRegion - the region covered by the cursor
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
HRESULT GdiEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
HRESULT GdiEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
{
SMALL_RECT sr = Viewport::FromCoord(*pcoordCursor).ToExclusive();
return this->Invalidate(&sr);
return this->Invalidate(psrRegion);
}
// Routine Description:

View file

@ -41,6 +41,9 @@ using namespace Microsoft::Console::Render;
_psInvalidData.hdc = GetDC(_hwndTargetWindow);
RETURN_HR_IF_NULL(E_FAIL, _psInvalidData.hdc);
// We need the advanced graphics mode in order to set a transform.
SetGraphicsMode(_psInvalidData.hdc, GM_ADVANCED);
// Signal that we're starting to paint.
_fPaintStarted = true;
@ -71,11 +74,28 @@ using namespace Microsoft::Console::Render;
// left behind cursor copies in the scrolled region.
if (cursorInvertRects.size() > 0)
{
// We first need to apply the transform that was active at the time the cursor
// was rendered otherwise we won't be clearing the right area of the display.
// We don't need to do this if it was an identity transform though.
const bool identityTransform = cursorInvertTransform == IDENTITY_XFORM;
if (!identityTransform)
{
LOG_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &cursorInvertTransform));
LOG_HR_IF(E_FAIL, !SetWorldTransform(_psInvalidData.hdc, &cursorInvertTransform));
}
for (RECT r : cursorInvertRects)
{
// Clean both the in-memory and actual window context.
RETURN_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));
RETURN_HR_IF(E_FAIL, !(InvertRect(_psInvalidData.hdc, &r)));
LOG_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));
LOG_HR_IF(E_FAIL, !(InvertRect(_psInvalidData.hdc, &r)));
}
// If we've applied a transform, then we need to reset it.
if (!identityTransform)
{
LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY));
LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_psInvalidData.hdc, nullptr, MWT_IDENTITY));
}
cursorInvertRects.clear();
@ -369,15 +389,21 @@ using namespace Microsoft::Console::Render;
}
}
// If the line rendition is double height, we need to adjust the top or bottom
// of the clipping rect to clip half the height of the rendered characters.
const auto halfHeight = coordFontSize.Y >> 1;
const auto topOffset = _currentLineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0;
const auto bottomOffset = _currentLineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0;
pPolyTextLine->lpstr = polyString.data();
pPolyTextLine->n = gsl::narrow<UINT>(clusters.size());
pPolyTextLine->x = ptDraw.x;
pPolyTextLine->y = ptDraw.y;
pPolyTextLine->uiFlags = ETO_OPAQUE | ETO_CLIPPED;
pPolyTextLine->rcl.left = pPolyTextLine->x;
pPolyTextLine->rcl.top = pPolyTextLine->y;
pPolyTextLine->rcl.right = pPolyTextLine->rcl.left + ((SHORT)cchCharWidths * coordFontSize.X);
pPolyTextLine->rcl.bottom = pPolyTextLine->rcl.top + coordFontSize.Y;
pPolyTextLine->rcl.top = pPolyTextLine->y + topOffset;
pPolyTextLine->rcl.right = pPolyTextLine->rcl.left + (SHORT)cchCharWidths;
pPolyTextLine->rcl.bottom = pPolyTextLine->y + coordFontSize.Y - bottomOffset;
pPolyTextLine->pdx = polyWidth.data();
if (trimLeft)
@ -628,6 +654,13 @@ using namespace Microsoft::Console::Render;
default:
return E_NOTIMPL;
}
// Prepare the appropriate line transform for the current row.
LOG_IF_FAILED(PrepareLineTransform(options.lineRendition, 0, options.viewportLeft));
auto resetLineTransform = wil::scope_exit([&]() {
LOG_IF_FAILED(ResetLineTransform());
});
// Either invert all the RECTs, or paint them.
if (options.fUseColor)
{
@ -642,6 +675,10 @@ using namespace Microsoft::Console::Render;
}
else
{
// Save the current line transform in case we need to reapply these
// inverted rects to hide the cursor in the ScrollFrame method.
cursorInvertTransform = _currentLineTransform;
for (RECT r : cursorInvertRects)
{
RETURN_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));

View file

@ -30,6 +30,8 @@ GdiEngine::GdiEngine() :
_lastFg(INVALID_COLOR),
_lastBg(INVALID_COLOR),
_lastFontItalic(false),
_currentLineTransform(IDENTITY_XFORM),
_currentLineRendition(LineRendition::SingleWidth),
_fPaintStarted(false),
_invalidCharacters{},
_hfont(nullptr),
@ -46,6 +48,9 @@ GdiEngine::GdiEngine() :
_hdcMemoryContext = CreateCompatibleDC(nullptr);
THROW_HR_IF_NULL(E_FAIL, _hdcMemoryContext);
// We need the advanced graphics mode in order to set a transform.
SetGraphicsMode(_hdcMemoryContext, GM_ADVANCED);
// On session zero, text GDI APIs might not be ready.
// Calling GetTextFace causes a wait that will be
// satisfied while GDI text APIs come online.
@ -123,6 +128,9 @@ GdiEngine::~GdiEngine()
HDC const hdcNewMemoryContext = CreateCompatibleDC(hdcRealWindow);
RETURN_HR_IF_NULL(E_FAIL, hdcNewMemoryContext);
// We need the advanced graphics mode in order to set a transform.
SetGraphicsMode(hdcNewMemoryContext, GM_ADVANCED);
// If we had an existing memory context stored, release it before proceeding.
if (nullptr != _hdcMemoryContext)
{
@ -185,6 +193,77 @@ GdiEngine::~GdiEngine()
return S_OK;
}
// Routine Description
// - Resets the world transform to the identity matrix.
// Arguments:
// - <none>
// Return Value:
// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error.
[[nodiscard]] HRESULT GdiEngine::ResetLineTransform() noexcept
{
// Return early if the current transform is already the identity matrix.
RETURN_HR_IF(S_FALSE, _currentLineTransform == IDENTITY_XFORM);
// Flush any buffer lines which would be expecting to use the current transform.
LOG_IF_FAILED(_FlushBufferLines());
// Reset the active transform to the identity matrix.
RETURN_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY));
// Reset the current state.
_currentLineTransform = IDENTITY_XFORM;
_currentLineRendition = LineRendition::SingleWidth;
return S_OK;
}
// Routine Description
// - Applies an appropriate transform for the given line rendition and viewport offset.
// Arguments:
// - lineRendition - The line rendition specifying the scaling of the line.
// - targetRow - The row on which the line is expected to be rendered.
// - viewportLeft - The left offset of the current viewport.
// Return Value:
// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error.
[[nodiscard]] HRESULT GdiEngine::PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept
{
XFORM lineTransform = {};
// The X delta is to account for the horizontal viewport offset.
lineTransform.eDx = viewportLeft ? -1.0f * viewportLeft * _GetFontSize().X : 0.0f;
switch (lineRendition)
{
case LineRendition::SingleWidth:
lineTransform.eM11 = 1; // single width
lineTransform.eM22 = 1; // single height
break;
case LineRendition::DoubleWidth:
lineTransform.eM11 = 2; // double width
lineTransform.eM22 = 1; // single height
break;
case LineRendition::DoubleHeightTop:
lineTransform.eM11 = 2; // double width
lineTransform.eM22 = 2; // double height
// The Y delta is to negate the offset caused by the scaled height.
lineTransform.eDy = -1.0f * targetRow * _GetFontSize().Y;
break;
case LineRendition::DoubleHeightBottom:
lineTransform.eM11 = 2; // double width
lineTransform.eM22 = 2; // double height
// The Y delta is to negate the offset caused by the scaled height.
// An extra row is added because we need the bottom half of the line.
lineTransform.eDy = -1.0f * (targetRow + 1) * _GetFontSize().Y;
break;
}
// Return early if the new matrix is the same as the current transform.
RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform);
// Flush any buffer lines which would be expecting to use the current transform.
LOG_IF_FAILED(_FlushBufferLines());
// Set the active transform with the new matrix.
RETURN_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &lineTransform));
// Save the current state.
_currentLineTransform = lineTransform;
_currentLineRendition = lineRendition;
return S_OK;
}
// Routine Description:
// - This method will set the GDI brushes in the drawing context (and update the hung-window background color)
// Arguments:

View file

@ -14,6 +14,7 @@ Author(s):
#pragma once
#include "../../buffer/out/LineRendition.hpp"
#include "../../inc/conattrs.hpp"
namespace Microsoft::Console::Render
@ -21,9 +22,15 @@ namespace Microsoft::Console::Render
struct CursorOptions
{
// Character cell in the grid to draw at
// This is relative to the viewport, not the buffer.
// This is relative to the top of the viewport, not the buffer
COORD coordCursor;
// Left offset of the viewport, which may alter the horizontal position
SHORT viewportLeft;
// Line rendition of the current row, which can affect the cursor width
LineRendition lineRendition;
// For an underscore type _ cursor, how tall it should be as a % of cell height
ULONG ulCursorHeightPercent;

View file

@ -18,6 +18,7 @@ Author(s):
#include "Cluster.hpp"
#include "FontInfoDesired.hpp"
#include "IRenderData.hpp"
#include "../../buffer/out/LineRendition.hpp"
namespace Microsoft::Console::Render
{
@ -64,7 +65,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept = 0;
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
@ -75,6 +76,11 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0;
[[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0;
[[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
const COORD coord,

View file

@ -40,6 +40,11 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
const size_t targetRow,
const size_t viewportLeft) noexcept override;
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept override;
void WaitUntilCanRender() noexcept override;

View file

@ -21,6 +21,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
_cursorChanged{ false },
_isEnabled{ true },
_prevSelection{},
_prevCursorRegion{},
RenderEngineBase()
{
}
@ -66,18 +67,18 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - Notifies us that the console has changed the position of the cursor.
// For UIA, this doesn't mean anything. So do nothing.
// Arguments:
// - pcoordCursor - the new position of the cursor
// - psrRegion - the region covered by the cursor
// Return Value:
// - S_OK
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
try
{
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
// check if cursor moved
if (*pcoordCursor != _prevCursorPos)
if (*psrRegion != _prevCursorRegion)
{
_prevCursorPos = *pcoordCursor;
_prevCursorRegion = *psrRegion;
_cursorChanged = true;
}
return S_OK;

View file

@ -43,7 +43,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
@ -86,6 +86,6 @@ namespace Microsoft::Console::Render
Microsoft::Console::Types::IUiaEventDispatcher* _dispatcher;
std::vector<SMALL_RECT> _prevSelection;
til::point _prevCursorPos;
SMALL_RECT _prevCursorRegion;
};
}

View file

@ -59,10 +59,10 @@ CATCH_RETURN();
// Routine Description:
// - Notifies us that the console has changed the position of the cursor.
// Arguments:
// - pcoordCursor - the new position of the cursor
// - psrRegion - the region covered by the cursor
// Return Value:
// - S_OK
[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
{
// If we just inherited the cursor, we're going to get an InvalidateCursor
// for both where the old cursor was, and where the new cursor is
@ -70,9 +70,9 @@ CATCH_RETURN();
// We should ignore the first one, but after that, if the client application
// is moving the cursor around in the viewport, move our virtual top
// up to meet their changes.
if (!_skipCursor && _virtualTop > pcoordCursor->Y)
if (!_skipCursor && _virtualTop > psrRegion->Top)
{
_virtualTop = pcoordCursor->Y;
_virtualTop = psrRegion->Top;
}
_skipCursor = false;

View file

@ -49,7 +49,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;

View file

@ -166,7 +166,7 @@ bool WddmConEngine::IsInitialized()
return S_OK;
}
[[nodiscard]] HRESULT WddmConEngine::InvalidateCursor(const COORD* const /*pcoordCursor*/) noexcept
[[nodiscard]] HRESULT WddmConEngine::InvalidateCursor(const SMALL_RECT* const /*psrRegion*/) noexcept
{
return S_OK;
}

View file

@ -26,7 +26,7 @@ namespace Microsoft::Console::Render
// IRenderEngine Members
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;

View file

@ -13,6 +13,7 @@ Abstract:
*/
#pragma once
#include "DispatchTypes.hpp"
#include "../buffer/out/LineRendition.hpp"
namespace Microsoft::Console::VirtualTerminal
{
@ -88,6 +89,7 @@ public:
virtual bool EraseCharacters(const size_t numChars) = 0; // ECH
virtual bool SetGraphicsRendition(const VTParameters options) = 0; // SGR
virtual bool SetLineRendition(const LineRendition rendition) = 0; // DECSWL, DECDWL, DECDHL
virtual bool PushGraphicsRendition(const VTParameters options) = 0; // XTPUSHSGR
virtual bool PopGraphicsRendition() = 0; // XTPOPSGR

View file

@ -432,10 +432,9 @@ bool AdaptDispatch::_InsertDeleteHelper(const size_t count, const bool isInsert)
const auto cursor = csbiex.dwCursorPosition;
// Rectangle to cut out of the existing buffer. This is inclusive.
// It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
SMALL_RECT srScroll;
srScroll.Left = cursor.X;
srScroll.Right = SHORT_MAX;
srScroll.Right = _pConApi->PrivateGetLineWidth(cursor.Y) - 1;
srScroll.Top = cursor.Y;
srScroll.Bottom = srScroll.Top;
@ -534,7 +533,7 @@ bool AdaptDispatch::_EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& c
case DispatchTypes::EraseType::ToEnd:
case DispatchTypes::EraseType::All:
// Remember the .X value is 1 farther than the right most column in the buffer. Therefore no +1.
nLength = csbiex.dwSize.X - coordStartPosition.X;
nLength = _pConApi->PrivateGetLineWidth(lineId) - coordStartPosition.X;
break;
}
@ -622,6 +621,23 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
if (success)
{
// When erasing the display, every line that is erased in full should be
// reset to single width. When erasing to the end, this could include
// the current line, if the cursor is in the first column. When erasing
// from the beginning, though, the current line would never be included,
// because the cursor could never be in the rightmost column (assuming
// the line is double width).
if (eraseType == DispatchTypes::EraseType::FromBeginning)
{
const auto endRow = csbiex.dwCursorPosition.Y;
_pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow);
}
if (eraseType == DispatchTypes::EraseType::ToEnd)
{
const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0);
_pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom);
}
// What we need to erase is grouped into 3 types:
// 1. Lines before cursor
// 2. Cursor Line
@ -697,6 +713,18 @@ bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
return success;
}
// Routine Description:
// - DECSWL/DECDWL/DECDHL - Sets the line rendition attribute for the current line.
// Arguments:
// - rendition - Determines whether the line will be rendered as single width, double
// width, or as one half of a double height line.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
{
return _pConApi->PrivateSetCurrentLineRendition(rendition);
}
// Routine Description:
// - DSR - Reports status of a console property back to the STDIN based on the type of status requested.
// - This particular routine responds to ANSI status patterns only (CSI # n), not the DEC format (CSI ? # n)
@ -1931,6 +1959,8 @@ bool AdaptDispatch::ScreenAlignmentPattern()
auto fillPosition = COORD{ 0, csbiex.srWindow.Top };
const auto fillLength = (csbiex.srWindow.Bottom - csbiex.srWindow.Top) * csbiex.dwSize.X;
success = _pConApi->PrivateFillRegion(fillPosition, fillLength, L'E', false);
// Reset the line rendition for all of these rows.
success = success && _pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, csbiex.srWindow.Bottom);
// Reset the meta/extended attributes (but leave the colors unchanged).
TextAttribute attr;
if (_pConApi->PrivateGetTextAttributes(attr))
@ -1995,6 +2025,8 @@ bool AdaptDispatch::_EraseScrollback()
const COORD coordBelowStartPosition = { 0, height };
// Again we need to use the default attributes, hence standardFillAttrs is false.
success = _pConApi->PrivateFillRegion(coordBelowStartPosition, totalAreaBelow, L' ', false);
// Also reset the line rendition for all of the cleared rows.
success = success && _pConApi->PrivateResetLineRenditionRange(height, csbiex.dwSize.Y);
if (success)
{

View file

@ -57,6 +57,7 @@ namespace Microsoft::Console::VirtualTerminal
bool InsertCharacter(const size_t count) override; // ICH
bool DeleteCharacter(const size_t count) override; // DCH
bool SetGraphicsRendition(const VTParameters options) override; // SGR
bool SetLineRendition(const LineRendition rendition) override; // DECSWL, DECDWL, DECDHL
bool PushGraphicsRendition(const VTParameters options) override; // XTPUSHSGR
bool PopGraphicsRendition() override; // XTPOPSGR
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR

View file

@ -16,6 +16,7 @@ Author(s):
#pragma once
#include "../../types/inc/IInputEvent.hpp"
#include "../../buffer/out/LineRendition.hpp"
#include "../../buffer/out/TextAttribute.hpp"
#include "../../inc/conattrs.hpp"
@ -39,6 +40,10 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateGetTextAttributes(TextAttribute& attrs) const = 0;
virtual bool PrivateSetTextAttributes(const TextAttribute& attrs) = 0;
virtual bool PrivateSetCurrentLineRendition(const LineRendition lineRendition) = 0;
virtual bool PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow) = 0;
virtual SHORT PrivateGetLineWidth(const size_t row) const = 0;
virtual bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
size_t& eventsWritten) = 0;
virtual bool SetConsoleWindowInfo(const bool absolute,

View file

@ -82,6 +82,7 @@ public:
bool EraseCharacters(const size_t /*numChars*/) noexcept override { return false; } // ECH
bool SetGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // SGR
bool SetLineRendition(const LineRendition /*rendition*/) noexcept override { return false; } // DECSWL, DECDWL, DECDHL
bool PushGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // XTPUSHSGR
bool PopGraphicsRendition() noexcept override { return false; } // XTPOPSGR

View file

@ -249,6 +249,27 @@ public:
return _privateSetTextAttributesResult;
}
bool PrivateSetCurrentLineRendition(const LineRendition /*lineRendition*/)
{
Log::Comment(L"PrivateSetCurrentLineRendition MOCK called...");
return false;
}
bool PrivateResetLineRenditionRange(const size_t /*startRow*/, const size_t /*endRow*/)
{
Log::Comment(L"PrivateResetLineRenditionRange MOCK called...");
return false;
}
SHORT PrivateGetLineWidth(const size_t /*row*/) const
{
Log::Comment(L"PrivateGetLineWidth MOCK called...");
return _bufferSize.X;
}
bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
size_t& eventsWritten) override
{

View file

@ -265,6 +265,22 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id)
success = _dispatch->LockingShiftRight(3);
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R);
break;
case EscActionCodes::DECDHL_DoubleHeightLineTop:
_dispatch->SetLineRendition(LineRendition::DoubleHeightTop);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDHL);
break;
case EscActionCodes::DECDHL_DoubleHeightLineBottom:
_dispatch->SetLineRendition(LineRendition::DoubleHeightBottom);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDHL);
break;
case EscActionCodes::DECSWL_SingleWidthLine:
_dispatch->SetLineRendition(LineRendition::SingleWidth);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSWL);
break;
case EscActionCodes::DECDWL_DoubleWidthLine:
_dispatch->SetLineRendition(LineRendition::DoubleWidth);
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDWL);
break;
case EscActionCodes::DECALN_ScreenAlignmentPattern:
success = _dispatch->ScreenAlignmentPattern();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN);

View file

@ -86,6 +86,10 @@ namespace Microsoft::Console::VirtualTerminal
LS1R_LockingShift = VTID("~"),
LS2R_LockingShift = VTID("}"),
LS3R_LockingShift = VTID("|"),
DECDHL_DoubleHeightLineTop = VTID("#3"),
DECDHL_DoubleHeightLineBottom = VTID("#4"),
DECSWL_SingleWidthLine = VTID("#5"),
DECDWL_DoubleWidthLine = VTID("#6"),
DECALN_ScreenAlignmentPattern = VTID("#8")
};

View file

@ -273,6 +273,9 @@ void TermTelemetry::WriteFinalTraceLog() const
TraceLoggingUInt32(_uiTimesUsed[OSCBG], "OscBackgroundColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"),
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
TraceLoggingUInt32(_uiTimesUsed[DECSWL], "DECSWL"),
TraceLoggingUInt32(_uiTimesUsed[DECDWL], "DECDWL"),
TraceLoggingUInt32(_uiTimesUsed[DECDHL], "DECDHL"),
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"),
TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"),

View file

@ -99,6 +99,9 @@ namespace Microsoft::Console::VirtualTerminal
REP,
OSCFG,
OSCBG,
DECSWL,
DECDWL,
DECDHL,
DECALN,
OSCSCB,
XTPUSHSGR,

View file

@ -410,7 +410,8 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
// GH#6402: Get the actual buffer size here, instead of the one
// constrained by the virtual bottom.
const auto bufferSize = _pData->GetTextBuffer().GetSize();
const auto& buffer = _pData->GetTextBuffer();
const auto bufferSize = buffer.GetSize();
// these viewport vars are converted to the buffer coordinate space
const auto viewport = bufferSize.ConvertToOrigin(_pData->GetViewport());
@ -448,11 +449,14 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
}
else
{
const auto textRects = _pData->GetTextBuffer().GetTextRects(startAnchor, endAnchor, _blockRange);
const auto textRects = buffer.GetTextRects(startAnchor, endAnchor, _blockRange, true);
for (const auto& rect : textRects)
{
til::rectangle r{ rect };
// Convert the buffer coordinates to an equivalent range of
// screen cells, taking line rendition into account.
const auto lineRendition = buffer.GetLineRendition(rect.Top);
til::rectangle r{ BufferToScreenLine(rect, lineRendition) };
r -= viewportOrigin;
_getBoundingRect(r, coords);
}
@ -551,7 +555,7 @@ std::wstring UiaTextRangeBase::_getTextValue(std::optional<unsigned int> maxLeng
auto inclusiveEnd = _end;
bufferSize.DecrementInBounds(inclusiveEnd, true);
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange);
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange, true);
const auto bufferData = buffer.GetText(true,
false,
textRects);