Repository: microsoft/terminal (language: cpp)

Issue:
Links not underlined with `experimental.useAtlasEngine` enabled
### Windows Terminal version

main 52235b0f

### Windows build number

10.0.19044.0

### Other Software

_No response_

### Steps to reproduce

- clone and build latest dev release, `52235b0f` used for testing.
- enable `"experimental.useAtlasEngine": true` in SUI or `settings.json`
- open new tab containing a hyperlink

### Expected Behavior

- hyperlinks to be underlined

### Actual Behavior

- hyperlink not underlined, `crtl + click` opens the link though.
- turning off `experimental.useAtlasEngine` shows links underlined.

Sample screenshot:
![wt_dev_link_underline](https://user-images.githubusercontent.com/3933920/144490165-008055f6-7eb7-42b0-ae22-beaf18e7abe1.png)





Discussion / hints:

Fix patch:
diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp
index c8e9ca67b2a..b7a267422c7 100644
--- a/src/renderer/atlas/AtlasEngine.api.cpp
+++ b/src/renderer/atlas/AtlasEngine.api.cpp
@@ -494,6 +494,7 @@ CATCH_RETURN()
 
 void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
 {
+    _api.hyperlinkHoveredId = hoveredId;
 }
 
 #pragma endregion
@@ -580,7 +581,7 @@ void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, Fo
         const auto strikethroughOffsetInPx = static_cast<double>(-metrics.strikethroughPosition) * designUnitsPerPx;
         const auto strikethroughThicknessInPx = static_cast<double>(metrics.strikethroughThickness) * designUnitsPerPx;
         const auto lineThickness = gsl::narrow<u16>(std::round(std::min(underlineThicknessInPx, strikethroughThicknessInPx)));
-        const auto underlinePos = gsl::narrow<u16>(std::round(baseline + underlineOffsetInPx - lineThickness / 2.0));
+        const auto underlinePos = gsl::narrow<u16>(std::ceil(baseline + underlineOffsetInPx - lineThickness / 2.0));
         const auto strikethroughPos = gsl::narrow<u16>(std::round(baseline + strikethroughOffsetInPx - lineThickness / 2.0));
 
         auto fontName = wil::make_process_heap_string(requestedFaceName);
diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp
index bff52ab13cc..2632a94fcc5 100644
--- a/src/renderer/atlas/AtlasEngine.cpp
+++ b/src/renderer/atlas/AtlasEngine.cpp
@@ -465,12 +465,13 @@ try
     const auto x = gsl::narrow_cast<u16>(clamp<int>(coord.X, 0, _api.cellCount.x));
     const auto y = gsl::narrow_cast<u16>(clamp<int>(coord.Y, 0, _api.cellCount.y));
 
-    if (_api.currentRow != y)
+    if (_api.lastPaintBufferLineCoord.y != y)
     {
         _flushBufferLine();
     }
 
-    _api.currentRow = y;
+    _api.lastPaintBufferLineCoord = { x, y };
+    _api.bufferLineWasHyperlinked = false;
 
     // Due to the current IRenderEngine interface (that wasn't refactored yet) we need to assemble
     // the current buffer line first as the remaining function operates on whole lines of text.
@@ -503,9 +504,21 @@ try
 CATCH_RETURN()
 
 [[nodiscard]] HRESULT AtlasEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept
+try
 {
+    if (!_api.bufferLineWasHyperlinked && lines.test(GridLines::Underline) && WI_IsFlagClear(_api.flags, CellFlags::Underline))
+    {
+        _api.bufferLineWasHyperlinked = true;
+
+        WI_UpdateFlagsInMask(_api.flags, CellFlags::Underline | CellFlags::UnderlineDotted | CellFlags::UnderlineDouble, CellFlags::Underline);
+
+        const BufferLineMetadata metadata{ _api.currentColor, _api.flags };
+        const size_t x = _api.lastPaintBufferLineCoord.x;
+        std::fill_n(_api.bufferLineMetadata.data() + x, _api.bufferLineMetadata.size() - x, metadata);
+    }
     return S_OK;
 }
+CATCH_RETURN()
 
 [[nodiscard]] HRESULT AtlasEngine::PaintSelection(SMALL_RECT rect) noexcept
 try
@@ -569,16 +582,24 @@ try
 
     if (!isSettingDefaultBrushes)
     {
+        const auto hyperlinkId = textAttributes.GetHyperlinkId();
+
         auto flags = CellFlags::None;
         WI_SetFlagIf(flags, CellFlags::BorderLeft, textAttributes.IsLeftVerticalDisplayed());
         WI_SetFlagIf(flags, CellFlags::BorderTop, textAttributes.IsTopHorizontalDisplayed());
         WI_SetFlagIf(flags, CellFlags::BorderRight, textAttributes.IsRightVerticalDisplayed());
         WI_SetFlagIf(flags, CellFlags::BorderBottom, textAttributes.IsBottomHorizontalDisplayed());
         WI_SetFlagIf(flags, CellFlags::Underline, textAttributes.IsUnderlined());
-        WI_SetFlagIf(flags, CellFlags::UnderlineDotted, textAttributes.IsHyperlink());
+        WI_SetFlagIf(flags, CellFlags::UnderlineDotted, hyperlinkId != 0);
         WI_SetFlagIf(flags, CellFlags::UnderlineDouble, textAttributes.IsDoublyUnderlined());
         WI_SetFlagIf(flags, CellFlags::Strikethrough, textAttributes.IsCrossedOut());
 
+        if (_api.hyperlinkHoveredId && _api.hyperlinkHoveredId == hyperlinkId)
+        {
+            WI_SetFlag(flags, CellFlags::Underline);
+            WI_ClearAllFlags(flags, CellFlags::UnderlineDotted | CellFlags::UnderlineDouble);
+        }
+
         const u32x2 newColors{ gsl::narrow_cast<u32>(fg | 0xff000000), gsl::narrow_cast<u32>(bg | _api.backgroundOpaqueMixin) };
         const AtlasKeyAttributes attributes{ 0, textAttributes.IsBold(), textAttributes.IsItalic(), 0 };
 
@@ -1445,7 +1466,7 @@ void AtlasEngine::_emplaceGlyph(IDWriteFontFace* fontFace, float scale, size_t b
 
     const auto valueData = value.data();
     const auto coords = &valueData->coords[0];
-    const auto data = _getCell(x1, _api.currentRow);
+    const auto data = _getCell(x1, _api.lastPaintBufferLineCoord.y);
 
     for (u32 i = 0; i < cellCount; ++i)
     {
diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h
index a9123128df4..8202d1e5be8 100644
--- a/src/renderer/atlas/AtlasEngine.h
+++ b/src/renderer/atlas/AtlasEngine.h
@@ -734,10 +734,13 @@ namespace Microsoft::Console::Render
             u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::Device
             u32x2 currentColor;
             AtlasKeyAttributes attributes{};
-            u16 currentRow = 0;
+            u16x2 lastPaintBufferLineCoord;
             CellFlags flags = CellFlags::None;
             // SetSelectionBackground()
             u32 selectionColor = 0x7fffffff;
+            // UpdateHyperlinkHoveredId()
+            u16 hyperlinkHoveredId = 0;
+            bool bufferLineWasHyperlinked = false;
 
             // dirtyRect is a computed value based on invalidatedRows.
             til::rect dirtyRect;


Test patch: