Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for underline style and color in VT #15795

Merged
merged 42 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
07cb660
add support for underline color & style in VT parser
tusharsnx Aug 5, 2023
c26b688
add underline color and style support to VT renderer
tusharsnx Aug 6, 2023
0093d93
use const& when reading TextAttribute without mutation
tusharsnx Aug 6, 2023
ac2b786
more attributes read to const& refactoring
tusharsnx Aug 6, 2023
87a43c0
respect underline style and color in DECRARA and DECCARA
tusharsnx Aug 6, 2023
3dcb214
remaining refactor for const& for reading TextAttribute
tusharsnx Aug 6, 2023
1027e1b
refactor _SetRgbColorsHelperFromSubParams
tusharsnx Aug 7, 2023
1673c6e
let DECCARA apply underline color
tusharsnx Aug 8, 2023
a8bede3
store underline style within CharacterAttribute
tusharsnx Aug 10, 2023
00833f9
use public apis to manipulate flags in CharacterAttributes
tusharsnx Aug 10, 2023
87b3fa7
Merge remote-tracking branch 'upstream/main' into temp
tusharsnx Aug 12, 2023
0b38f07
update DECCIR to use styles instead of boolean when setting the under…
tusharsnx Aug 11, 2023
7d12a21
let's bid adieu to CharacterAttribute::DoublyUnderlined
tusharsnx Aug 12, 2023
1163542
update routines for XTPUSHSGR / XTPOPSGR
tusharsnx Aug 12, 2023
a7d2437
update routines for DECRQSS
tusharsnx Aug 12, 2023
d18f4d7
check underline color is not Index16 based
tusharsnx Aug 12, 2023
42f6066
update routines for DECCIR
tusharsnx Aug 12, 2023
9660e2a
update the tests
tusharsnx Aug 12, 2023
fd165f2
format
tusharsnx Aug 12, 2023
0a3e9ae
fix little things
tusharsnx Aug 12, 2023
5350ac4
fix a wrong default'ing for drawing underlines
tusharsnx Aug 12, 2023
6d4ac54
fix wordings
tusharsnx Aug 12, 2023
eeff6a5
write seq for doubly underlined directly
tusharsnx Aug 12, 2023
21309d5
report singly and doubly underlined with sgr 4 and 21 in DECRQSS resp…
tusharsnx Aug 12, 2023
a117f30
update test and add ITU color test for the underline
tusharsnx Aug 12, 2023
ac62677
renderer: merge NoUnderline into the default case
tusharsnx Aug 13, 2023
5bb3531
adaptDispatch: maintain rendition attrs' numeric order while sending …
tusharsnx Aug 13, 2023
da50289
revert the comment added to SetDefaultRenditionAttributes
tusharsnx Aug 13, 2023
e33130c
initialize underline color to INVALID_COLOR where it make sense to do so
tusharsnx Aug 14, 2023
32bd78a
avoid false errors due to missing headers within editor
tusharsnx Aug 14, 2023
820cbc0
update test to use CurlyUnderlined instead of SinglyUnderlined
tusharsnx Aug 14, 2023
f5ab09a
reset doubly underlined before sending the new style
tusharsnx Aug 15, 2023
be681de
pass a separate boolean reverseUnderline in ChangeOps to reverse unde…
tusharsnx Aug 15, 2023
7fd1129
update restore logic for XTPUSHSGR/XTPOPSGR
tusharsnx Aug 16, 2023
432d4df
Merge remote-tracking branch 'upstream/main' into feat-underline-styl…
tusharsnx Aug 18, 2023
20755f3
inline mapping functions for mapping underline style to/from characte…
tusharsnx Aug 20, 2023
2935818
Merge remote-tracking branch 'upstream/main' into feat-underline-styl…
tusharsnx Aug 22, 2023
8812867
use util to generate subparams and ranges
tusharsnx Aug 22, 2023
31455cd
test underline color and style in GraphicsSingleWithSubParamTests
tusharsnx Aug 22, 2023
ea7dcfe
revert changes to DECRARA
tusharsnx Sep 1, 2023
a67a1ab
update the DECRARA testcase to test for intense attr instead of under…
tusharsnx Sep 1, 2023
dc1b95f
treat invalid style values as singly underlined
tusharsnx Sep 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 62 additions & 3 deletions src/buffer/out/TextAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

// Keeping TextColor compact helps us keeping TextAttribute compact,
// which in turn ensures that our buffer memory usage is low.
static_assert(sizeof(TextAttribute) == 12);
static_assert(sizeof(TextAttribute) == 18);
static_assert(alignof(TextAttribute) == 2);
// Ensure that we can memcpy() and memmove() the struct for performance.
static_assert(std::is_trivially_copyable_v<TextAttribute>);
Expand Down Expand Up @@ -156,6 +156,16 @@ uint16_t TextAttribute::GetHyperlinkId() const noexcept
return _hyperlinkId;
}

TextColor TextAttribute::GetUnderlineColor() const noexcept
{
return _underlineColor;
}

UnderlineStyle TextAttribute::GetUnderlineStyle() const noexcept
{
return _underlineStyle;
}

void TextAttribute::SetForeground(const TextColor foreground) noexcept
{
_foreground = foreground;
Expand All @@ -166,6 +176,11 @@ void TextAttribute::SetBackground(const TextColor background) noexcept
_background = background;
}

void TextAttribute::SetUnderlineColor(const TextColor color) noexcept
{
_underlineColor = color;
}

void TextAttribute::SetForeground(const COLORREF rgbForeground) noexcept
{
_foreground = TextColor(rgbForeground);
Expand Down Expand Up @@ -277,6 +292,9 @@ bool TextAttribute::IsCrossedOut() const noexcept
return WI_IsFlagSet(_attrs, CharacterAttributes::CrossedOut);
}

// Method description:
// - Returns true if the text is underlined with the any underline style,
// Eg. singly, doubly, curly, dotted, and dashed.
bool TextAttribute::IsUnderlined() const noexcept
{
return WI_IsFlagSet(_attrs, CharacterAttributes::Underlined);
Expand Down Expand Up @@ -334,9 +352,43 @@ void TextAttribute::SetCrossedOut(bool isCrossedOut) noexcept

void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
{
WI_UpdateFlag(_attrs, CharacterAttributes::Underlined, isUnderlined);
if (isUnderlined)
{
SetUnderlineStyle(UnderlineStyle::SinglyUnderlined);
}
else
{
SetUnderlineStyle(UnderlineStyle::NoUnderline);
}
}

void TextAttribute::SetUnderlineStyle(const UnderlineStyle underlineStyle) noexcept
{
switch (underlineStyle)
{
case UnderlineStyle::NoUnderline:
WI_ClearFlag(_attrs, CharacterAttributes::Underlined);
_underlineStyle = underlineStyle;
break;
case UnderlineStyle::SinglyUnderlined:
case UnderlineStyle::DoublyUnderlined:
case UnderlineStyle::CurlyUnderlined:
case UnderlineStyle::DottedUnderlined:
case UnderlineStyle::DashedUnderlined:
WI_SetFlag(_attrs, CharacterAttributes::Underlined);
_underlineStyle = underlineStyle;
break;
default:
/* do nothing */
break;
}
}

// Method description:
// - Sets doubly underlined in the attribute.
// - This is used for SGR 21 sequences. (do not use with SGR 4:x)
// Arguments:
// - isDoublyUnderlined - whether to set doubly underlined or not.
void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept
{
WI_UpdateFlag(_attrs, CharacterAttributes::DoublyUnderlined, isDoublyUnderlined);
Expand Down Expand Up @@ -374,12 +426,18 @@ void TextAttribute::SetDefaultBackground() noexcept
_background = TextColor();
}

void TextAttribute::SetDefaultUnderlineColor() noexcept
{
_underlineColor = TextColor{};
}

// Method description:
// - Resets only the rendition character attributes, which includes everything
// except the Protected attribute.
// except the Protected attribute. Additionally, resets the underline style.
void TextAttribute::SetDefaultRenditionAttributes() noexcept
{
_attrs &= ~CharacterAttributes::Rendition;
_underlineStyle = UnderlineStyle::NoUnderline;
}

// Method Description:
Expand All @@ -404,5 +462,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept
void TextAttribute::SetStandardErase() noexcept
{
_attrs = CharacterAttributes::Normal;
_underlineStyle = UnderlineStyle::NoUnderline;
_hyperlinkId = 0;
}
44 changes: 41 additions & 3 deletions src/buffer/out/TextAttribute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,38 @@ Revision History:
#include "WexTestClass.h"
#endif

enum class UnderlineStyle : uint8_t
{
NoUnderline = 0,
SinglyUnderlined = 1,
DoublyUnderlined = 2,
CurlyUnderlined = 3,
DottedUnderlined = 4,
DashedUnderlined = 5,
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of storing the underline style in a separate attribute, which ends up requiring an additional two bytes, I would much prefer if we could just add it to the existing CharacterAttributes enum.

I think all we really need is one more bit. Maybe move the Faint to 0x20, and then reserve 0x40, 0x80, and 0x100 for the underline styles. Something like this:

SingleUnderline = 0x40,
DoubleUnderline = 0x80,
CurlyUnderline = 0xC0,
DottedUnderline = 0x100,
DashedUnderline = 0x140,
AnyUnderline = 0x1C0,
NoUnderline = 0,

Then you can access those attributes with something like this (pseudo code):

// Testing for a particular style from the list above.
bool TextAttribute::IsUnderlined(int style)
{
    return (_attrs & AnyUnderline) == style;
}

// Testing for any underline style if we want a separate method for that.
bool TextAttribute::IsUnderlined()
{
    return IsUnderlined(NoUnderline);
}

// Setting a particular style from the list above.
void TextAttribute::SetUnderlined(int style)
{
    _attrs = (_attrs & ~AnyUnderline) | style;
}

// Remove all underlines if we want a separate method for that.
void TextAttribute::ResetUnderlined()
{
    SetUnderlined(NoUnderline);
}

There are probably some WI_ macros you can use that'll make the implementation cleaner - I just wanted to demonstrate the general idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! I think I can implement this, with little tweaks maybe.

Wondering if it's safe to assume only one of the underline style can be active at any point, including doublyUnderlined even if it's done using SGR 21. I guess we don't need to track doubly underline separately, do we?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we don't need to track doubly underline separately, do we?

I'm glad you asked this, because we do track them separately at the moment, but I don't think we need to retain that behavior.

At the time I added double underline support there was no consensus in how this was handled amongst other terminals, so I just followed what XTerm was doing. But in retrospect I think that was probably the wrong decision, and now that we're adding more underline styles it makes even less sense. So yeah, I'd say we don't need to track double underline separately.

Copy link
Contributor Author

@tusharsnx tusharsnx Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look at the discussion over at #2916 (comment) where you mentioned:

However in XTerm, underline and double-underline are actually separate attributes, and double-underline takes precedence when both are set, regardless of the order they were applied.

I assume this needs to be taken care of?

Copy link
Contributor Author

@tusharsnx tusharsnx Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't apply to SGR 4:x.

Copy link
Collaborator

@j4james j4james Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However in XTerm, underline and double-underline are actually separate attributes, and double-underline takes precedence when both are set, regardless of the order they were applied.

I assume this needs to be taken care of?

No. That's what I was trying to explain above. It seemed like the right thing to do at the time, but in retrospect, copying XTerm was the wrong decision. I don't have a record of my testing from back then, but having just checked now, I can't find anyone that still matches XTerm other than us. And in our case, we only really match XTerm in the DX renderer - the Atlas renderer draws the single underline on top of the double underline when both are applied.

There is no reason for us to retain the current behavior. This is such a ridiculous edge case anyway that nobody is going to be expecting it to work in a particular way.


class TextAttribute final
{
public:
constexpr TextAttribute() noexcept :
_attrs{ CharacterAttributes::Normal },
_foreground{},
_background{},
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
_attrs{ gsl::narrow_cast<WORD>(wLegacyAttr & USED_META_ATTRS) },
_foreground{ gsl::at(s_legacyForegroundColorMap, wLegacyAttr & FG_ATTRS) },
_background{ gsl::at(s_legacyBackgroundColorMap, (wLegacyAttr & BG_ATTRS) >> 4) },
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

Expand All @@ -51,7 +67,21 @@ class TextAttribute final
_attrs{ CharacterAttributes::Normal },
_foreground{ rgbForeground },
_background{ rgbBackground },
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

constexpr TextAttribute(const CharacterAttributes attrs, const TextColor foreground, const TextColor background, const uint16_t hyperlinkId, const TextColor underlineColor, const UnderlineStyle underlineStyle) noexcept :
_attrs{ attrs },
_foreground{ foreground },
_background{ background },
_hyperlinkId{ hyperlinkId },
_underlineColor{ underlineColor },
_underlineStyle{ underlineStyle },
__unused{}
{
}

Expand Down Expand Up @@ -99,6 +129,7 @@ class TextAttribute final
void SetInvisible(bool isInvisible) noexcept;
void SetCrossedOut(bool isCrossedOut) noexcept;
void SetUnderlined(bool isUnderlined) noexcept;
void SetUnderlineStyle(const UnderlineStyle underlineStyle) noexcept;
void SetDoublyUnderlined(bool isDoublyUnderlined) noexcept;
void SetOverlined(bool isOverlined) noexcept;
void SetReverseVideo(bool isReversed) noexcept;
Expand All @@ -118,8 +149,11 @@ class TextAttribute final
TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
uint16_t GetHyperlinkId() const noexcept;
TextColor GetUnderlineColor() const noexcept;
UnderlineStyle GetUnderlineStyle() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetUnderlineColor(const TextColor color) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
void SetBackground(const COLORREF rgbBackground) noexcept;
void SetIndexedForeground(const BYTE fgIndex) noexcept;
Expand All @@ -131,6 +165,7 @@ class TextAttribute final

void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
void SetDefaultUnderlineColor() noexcept;
void SetDefaultRenditionAttributes() noexcept;

bool BackgroundIsDefault() const noexcept;
Expand Down Expand Up @@ -175,6 +210,9 @@ class TextAttribute final
uint16_t _hyperlinkId; // sizeof: 2, alignof: 2
TextColor _foreground; // sizeof: 4, alignof: 1
TextColor _background; // sizeof: 4, alignof: 1
TextColor _underlineColor; // sizeof: 4, alignof: 1
UnderlineStyle _underlineStyle; // sizeof: 1, alignof: 1
BYTE __unused; // sizeof: 1, alignof: 1 (avoids padding)

#ifdef UNIT_TESTING
friend class TextBufferTests;
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// but after the resize, we'll want to make sure that the new buffer's
// current attributes (the ones used for printing new text) match the
// old buffer's.
const auto oldBufferAttributes = _mainBuffer->GetCurrentAttributes();
const auto& oldBufferAttributes = _mainBuffer->GetCurrentAttributes();
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
Expand Down
68 changes: 68 additions & 0 deletions src/renderer/vt/VtSequences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,19 @@ using namespace Microsoft::Console::Render;
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;5;{}m"), fIsForeground ? '3' : '4', index);
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to an
// indexed color from the 256-color table.
// - Uses sub parameters.
// Arguments:
// - index: color table index to emit as a VT sequence
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept
{
return _WriteFormatted(FMT_COMPILE("\x1b[58:5:{}m"), index);
}

// Method Description:
// - Formats and writes a sequence to change the current text attributes to an
// RGB color.
Expand All @@ -258,6 +271,22 @@ using namespace Microsoft::Console::Render;
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;2;{};{};{}m"), fIsForeground ? '3' : '4', r, g, b);
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to an
// RGB color.
// - Uses sub parameters.
// Arguments:
// - color: The color to emit a VT sequence for.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept
{
const auto r = GetRValue(color);
const auto g = GetGValue(color);
const auto b = GetBValue(color);
return _WriteFormatted(FMT_COMPILE("\x1b[58:2::{}:{}:{}m"), r, g, b);
}

// Method Description:
// - Formats and writes a sequence to change the current text attributes to the
// default foreground or background. Does not affect the intensity of text.
Expand All @@ -270,6 +299,16 @@ using namespace Microsoft::Console::Render;
return _Write(fIsForeground ? ("\x1b[39m") : ("\x1b[49m"));
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to the
// default color.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineDefaultColor() noexcept
{
return _Write("\x1b[59m");
}

// Method Description:
// - Formats and writes a sequence to change the terminal's window size.
// Arguments:
Expand Down Expand Up @@ -332,6 +371,35 @@ using namespace Microsoft::Console::Render;
return _Write(isFaint ? "\x1b[2m" : "\x1b[22m");
}

// Method Description:
// - Formats and writes a sequence to change the extended underline styling of the following text.
// - Uses backward compatible SGR 4 (without sub parameter) and SGR 21 for single and doubly underline.
// Arguments:
// - style: underline style to use.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetUnderlineExtended(const UnderlineStyle style) noexcept
{
switch (style)
{
// we don't expect style to be NoUnderline here, but we'll handle it anyway.
case UnderlineStyle::NoUnderline:
return _SetUnderlined(false);
case UnderlineStyle::SinglyUnderlined:
return _SetUnderlined(true);
case UnderlineStyle::DoublyUnderlined:
return _SetDoublyUnderlined(true);
case UnderlineStyle::CurlyUnderlined:
return _Write("\x1b[4:3m");
case UnderlineStyle::DottedUnderlined:
return _Write("\x1b[4:4m");
case UnderlineStyle::DashedUnderlined:
return _Write("\x1b[4:5m");
default:
tusharsnx marked this conversation as resolved.
Show resolved Hide resolved
return S_OK;
}
}

// Method Description:
// - Formats and writes a sequence to change the underline of the following text.
// Arguments:
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/vt/Xterm256Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe,
// we can then check if either should be turned back on again.
if (textAttributes.IsUnderlined() && !_lastTextAttributes.IsUnderlined())
{
RETURN_IF_FAILED(_SetUnderlined(true));
_lastTextAttributes.SetUnderlined(true);
auto style = textAttributes.GetUnderlineStyle();
RETURN_IF_FAILED(_SetUnderlineExtended(style));
_lastTextAttributes.SetUnderlineStyle(style);
}
if (textAttributes.IsDoublyUnderlined() && !_lastTextAttributes.IsDoublyUnderlined())
{
Expand Down
29 changes: 27 additions & 2 deletions src/renderer/vt/paint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,13 @@ using namespace Microsoft::Console::Types;
{
const auto fg = textAttributes.GetForeground();
const auto bg = textAttributes.GetBackground();
const auto ul = textAttributes.GetUnderlineColor();
auto lastFg = _lastTextAttributes.GetForeground();
auto lastBg = _lastTextAttributes.GetBackground();
auto lastUl = _lastTextAttributes.GetUnderlineColor();

// If both the FG and BG should be the defaults, emit a SGR reset.
if (fg.IsDefault() && bg.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault()))
// If the FG, BG and UL should be the defaults, emit a SGR reset.
if (fg.IsDefault() && bg.IsDefault() && ul.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault() && lastUl.IsDefault()))
{
// SGR Reset will clear all attributes (except hyperlink ID) - which means
// we cannot reset _lastTextAttributes by simply doing
Expand All @@ -270,9 +272,11 @@ using namespace Microsoft::Console::Types;
RETURN_IF_FAILED(_SetGraphicsDefault());
_lastTextAttributes.SetDefaultBackground();
_lastTextAttributes.SetDefaultForeground();
_lastTextAttributes.SetDefaultUnderlineColor();
_lastTextAttributes.SetDefaultRenditionAttributes();
lastFg = {};
lastBg = {};
lastUl = {};
}

if (fg != lastFg)
Expand Down Expand Up @@ -317,6 +321,27 @@ using namespace Microsoft::Console::Types;
_lastTextAttributes.SetBackground(bg);
}

if (ul != lastUl)
{
if (ul.IsDefault())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineDefaultColor());
}
else if (ul.IsIndex16()) // underline can't be 16 color
{
/* do nothing */
}
else if (ul.IsIndex256())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderline256Color(ul.GetIndex()));
}
else if (ul.IsRgb())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineRGBColor(ul.GetRGB()));
}
_lastTextAttributes.SetUnderlineColor(ul);
}

return S_OK;
}

Expand Down
Loading