Skip to content

Commit

Permalink
Support rendering of underline style and color (#16097)
Browse files Browse the repository at this point in the history
Add support for underline style and color in the renderer

> [!IMPORTANT]  
> The PR adds underline style and color feature to AtlasEngine (WT) and
GDIRenderer (Conhost) only.

After the underline style and color feature addition to Conpty, this PR
takes it further and add support for rendering them to the screen!

Out of five underline styles, we already supported rendering for 3 of
those types (Singly, Doubly, Dotted) in some form in our (Atlas)
renderer. The PR adds the remaining types, namely, Dashed and Curly
underlines support to the renderer.

- All renderer engines now receive both gridline and underline color,
and the latter is used for drawing the underlines. **When no underline
color is set, we use the foreground color.**
- Curly underline is rendered using `sin()` within the pixel shader. 
- To draw underlines for DECDWL and DECDHL, we send the line rendition
scale within `QuadInstance`'s texcoord attribute.
- In GDI renderer, dashed and dotted underline is drawn using `HPEN`
with a desired style. Curly line is a cubic Bezier that draws one wave
per cell.

## PR Checklist
- ✅ Set the underline color to underlines only, without affecting the
gridline color.
- ❌ Port to DX renderer. (Not planned as DX renderer soon to be replaced
by **AtlasEngine**)
- ✅ Port underline coloring and style to GDI renderer (Conhost).
- ✅ Wide/Tall `CurlyUnderline` variant for `DECDWL`/`DECDHL`.

Closes #7228
  • Loading branch information
tusharsnx authored Nov 10, 2023
1 parent eb16eeb commit e268c1c
Show file tree
Hide file tree
Showing 29 changed files with 376 additions and 130 deletions.
4 changes: 4 additions & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ CTRLVOLUME
Ctxt
CUF
cupxy
curlyline
CURRENTFONT
currentmode
CURRENTPAGE
Expand Down Expand Up @@ -579,6 +580,7 @@ elems
emacs
EMPTYBOX
enabledelayedexpansion
ENDCAP
endptr
endregion
ENTIREBUFFER
Expand Down Expand Up @@ -827,6 +829,7 @@ hostlib
HPA
hpcon
HPCON
hpen
hpj
HPR
HProvider
Expand Down Expand Up @@ -1011,6 +1014,7 @@ LOBYTE
localappdata
locsrc
Loewen
LOGBRUSH
LOGFONT
LOGFONTA
LOGFONTW
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ namespace
HRESULT InvalidateCircling(_Out_ bool* /*pForcePaint*/) noexcept { return S_OK; }
HRESULT PaintBackground() noexcept { return S_OK; }
HRESULT PaintBufferLine(std::span<const Cluster> /*clusters*/, til::point /*coord*/, bool /*fTrimLeft*/, bool /*lineWrapped*/) noexcept { return S_OK; }
HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*color*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; }
HRESULT PaintBufferGridLines(GridLineSet /*lines*/, COLORREF /*gridlineColor*/, COLORREF /*underlineColor*/, size_t /*cchLine*/, til::point /*coordTarget*/) noexcept { return S_OK; }
HRESULT PaintSelection(const til::rect& /*rect*/) noexcept { return S_OK; }
HRESULT PaintCursor(const CursorOptions& /*options*/) noexcept { return S_OK; }
HRESULT UpdateDrawingBrushes(const TextAttribute& /*textAttributes*/, const RenderSettings& /*renderSettings*/, gsl::not_null<IRenderData*> /*pData*/, bool /*usingSoftFont*/, bool /*isSettingDefaultBrushes*/) noexcept { return S_OK; }
Expand Down
7 changes: 4 additions & 3 deletions src/interactivity/onecore/BgfxEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ CATCH_RETURN()
CATCH_RETURN()
}

[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/,
COLORREF const /*color*/,
size_t const /*cchLine*/,
[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(const GridLineSet /*lines*/,
const COLORREF /*gridlineColor*/,
const COLORREF /*underlineColor*/,
const size_t /*cchLine*/,
const til::point /*coordTarget*/) noexcept
{
return S_OK;
Expand Down
2 changes: 1 addition & 1 deletion src/interactivity/onecore/BgfxEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace Microsoft::Console::Render
const til::point coord,
const bool trimLeft,
const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, til::point const coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override;

[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
Expand Down
7 changes: 4 additions & 3 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,16 +375,17 @@ try
}
CATCH_RETURN()

[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const til::point coordTarget) noexcept
[[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept
try
{
const auto shift = gsl::narrow_cast<u8>(_api.lineRendition != LineRendition::SingleWidth);
const auto x = std::max(0, coordTarget.x - (_p.s->viewportOffset.x >> shift));
const auto y = gsl::narrow_cast<u16>(clamp<til::CoordType>(coordTarget.y, 0, _p.s->viewportCellCount.y));
const auto from = gsl::narrow_cast<u16>(clamp<til::CoordType>(x << shift, 0, _p.s->viewportCellCount.x - 1));
const auto to = gsl::narrow_cast<u16>(clamp<size_t>((x + cchLine) << shift, from, _p.s->viewportCellCount.x));
const auto fg = gsl::narrow_cast<u32>(color) | 0xff000000;
_p.rows[y]->gridLineRanges.emplace_back(lines, fg, from, to);
const auto glColor = gsl::narrow_cast<u32>(gridlineColor) | 0xff000000;
const auto ulColor = gsl::narrow_cast<u32>(underlineColor) | 0xff000000;
_p.rows[y]->gridLineRanges.emplace_back(lines, glColor, ulColor, from, to);
return S_OK;
}
CATCH_RETURN()
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace Microsoft::Console::Render::Atlas
[[nodiscard]] HRESULT PrepareLineTransform(LineRendition lineRendition, til::CoordType targetRow, til::CoordType viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(std::span<const Cluster> clusters, til::point coord, bool fTrimLeft, bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet lines, COLORREF color, size_t cchLine, til::point coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF gridlineColor, const COLORREF underlineColor, const size_t cchLine, const til::point coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const til::rect& rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const RenderSettings& renderSettings, gsl::not_null<IRenderData*> pData, bool usingSoftFont, bool isSettingDefaultBrushes) noexcept override;
Expand Down
28 changes: 14 additions & 14 deletions src/renderer/atlas/BackendD2D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro

D2D1_POINT_2F point0{ 0, static_cast<f32>(textCellCenter) };
D2D1_POINT_2F point1{ 0, static_cast<f32>(textCellCenter + cellSize.y) };
const auto brush = _brushWithColor(r.color);
const auto brush = _brushWithColor(r.gridlineColor);
const f32 w = pos.height;
const f32 hw = w * 0.5f;

Expand All @@ -421,11 +421,11 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
_renderTarget->DrawLine(point0, point1, brush, w, nullptr);
}
};
const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle) {
const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) {
const auto from = r.from >> widthShift;
const auto to = r.to >> widthShift;

const auto brush = _brushWithColor(r.color);
const auto brush = _brushWithColor(color);
const f32 w = pos.height;
const f32 centerY = textCellCenter + pos.position + w * 0.5f;
const D2D1_POINT_2F point0{ static_cast<f32>(from * cellSize.x), centerY };
Expand All @@ -448,32 +448,32 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
}
if (r.lines.test(GridLines::Top))
{
appendHorizontalLine(r, p.s->font->gridTop, nullptr);
appendHorizontalLine(r, p.s->font->gridTop, nullptr, r.gridlineColor);
}
if (r.lines.test(GridLines::Bottom))
{
appendHorizontalLine(r, p.s->font->gridBottom, nullptr);
appendHorizontalLine(r, p.s->font->gridBottom, nullptr, r.gridlineColor);
}
if (r.lines.test(GridLines::Strikethrough))
{
appendHorizontalLine(r, p.s->font->strikethrough, nullptr, r.gridlineColor);
}

if (r.lines.test(GridLines::Underline))
{
appendHorizontalLine(r, p.s->font->underline, nullptr);
appendHorizontalLine(r, p.s->font->underline, nullptr, r.underlineColor);
}
if (r.lines.test(GridLines::HyperlinkUnderline))
else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline))
{
appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get());
appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor);
}
if (r.lines.test(GridLines::DoubleUnderline))
else if (r.lines.test(GridLines::DoubleUnderline))
{
for (const auto pos : p.s->font->doubleUnderline)
{
appendHorizontalLine(r, pos, nullptr);
appendHorizontalLine(r, pos, nullptr, r.underlineColor);
}
}
if (r.lines.test(GridLines::Strikethrough))
{
appendHorizontalLine(r, p.s->font->strikethrough, nullptr);
}
}
}

Expand Down
Loading

0 comments on commit e268c1c

Please sign in to comment.