From a501feea840ed54a3e0a6172c3e8041a2c9e7958 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Fri, 8 Dec 2023 21:39:59 +0530 Subject: [PATCH 1/7] fix curlyline rendering in conhost --- src/renderer/gdi/paint.cpp | 24 +++++++++-------- src/renderer/gdi/state.cpp | 54 ++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 9722703d105..2f41671fd4e 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -543,20 +543,24 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) }; const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { const auto curlyLineWidth = fontWidth; - const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f); - const auto controlPointHeight = gsl::narrow_cast(std::floor(3.5f * _lineMetrics.curlylinePeakHeight)); + const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); + const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); + // Each curlyLine requires 3 `POINT`s const auto cPoints = gsl::narrow(3 * cCurlyLines); std::vector points; points.reserve(cPoints); + + const auto end = x + cCurlyLines * curlyLineWidth; auto start = x; - for (auto i = 0u; i < cCurlyLines; i++) + while (start < end) { points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); points.emplace_back(start + curlyLineWidth, y); start += curlyLineWidth; } + RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints)); return S_OK; @@ -619,28 +623,26 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get()); RETURN_HR_IF_NULL(E_FAIL, prevPen.get()); - const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f); if (lines.test(GridLines::Underline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DoubleUnderline)) { - const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f); - RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells)); - return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells); + RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells)); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells); } else if (lines.test(GridLines::CurlyUnderline)) { - return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine); + return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine); } else if (lines.test(GridLines::DottedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } else if (lines.test(GridLines::DashedUnderline)) { - return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells); + return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells); } return S_OK; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 099c2f43f78..60d8b7798d3 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -11,15 +11,6 @@ using namespace Microsoft::Console::Render; -namespace -{ - // The max height of Curly line peak in `em` units. - constexpr auto MaxCurlyLinePeakHeightEm = 0.075f; - - // The min height of Curly line peak. - constexpr auto MinCurlyLinePeakHeight = 2.0f; -} - // Routine Description: // - Creates a new GDI-based rendering engine // - NOTE: Will throw if initialization failure. Caller must catch. @@ -406,29 +397,30 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - // Curly line doesn't render properly below 1px stroke width. Make it a straight line. - if (_lineMetrics.underlineWidth < 1) - { - _lineMetrics.curlylinePeakHeight = 0; - } - else + const int strokeHalfWidth = std::lround(_lineMetrics.underlineWidth / 2.0); + + // Since we use GDI pen for drawing, the underline offset should point to + // the center of the underline. + _lineMetrics.underlineOffset += strokeHalfWidth; + _lineMetrics.underlineOffset2 += strokeHalfWidth; + + // We want the underline to always be visible and remain within the cell + // bottom, so we clamp the offset to fit just inside. + _lineMetrics.underlineOffset = std::min(_lineMetrics.underlineOffset, maxUnderlineOffset); + _lineMetrics.underlineOffset2 = std::min(_lineMetrics.underlineOffset2, maxUnderlineOffset); + + // Curlyline uses the gap between cell bottom and singly underline position + // as the height of the wave's peak. The baseline for curly line is at the + // middle of singly underline. The gap could be too big, so we also apply a + // limit on the peak height. { - // Curlyline uses the gap between cell bottom and singly underline - // position as the height of the wave's peak. The baseline for curly - // line is at the middle of singly underline. The gap could be too big, - // so we also apply a limit on the peak height. - const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f; - const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth; - const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); - - // When it's too small to be curly, make it a straight line. - if (curlyLinePeakHeight < MinCurlyLinePeakHeight) - { - curlyLinePeakHeight = 0.0f; - } - _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::floor(curlyLinePeakHeight)); + const auto cellBottomGap = std::lround(Font.GetSize().height - _lineMetrics.underlineOffset - strokeHalfWidth); + + // get the max height for curly line peak. Max height is in `em` units. + constexpr auto maxCurlyLinePeakHeightEm = 0.075f; + const auto maxCurlyLinePeakHeight = std::lround(maxCurlyLinePeakHeightEm * fontSize); + + _lineMetrics.curlylinePeakHeight = std::clamp(cellBottomGap, 0L, maxCurlyLinePeakHeight); } // Now find the size of a 0 in this current font and save it for conversions done later. From ef8a907f80c553f74718d9806fcf194899298679 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 9 Dec 2023 00:38:30 +0530 Subject: [PATCH 2/7] remove minimum threshold for curlyline peak in atlas engine --- src/renderer/atlas/BackendD3D.cpp | 40 +++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 51494df7b23..bc34c23c105 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -306,36 +306,30 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; - // The max height of Curly line peak in `em` units. - const auto maxCurlyLinePeakHeightEm = 0.075f; - // We aim for atleast 1px height, but since we draw 1px smaller curly line, - // we aim for 2px height as a result. - const auto minCurlyLinePeakHeight = 2.0f; - // Curlyline uses the gap between cell bottom and singly underline position // as the height of the wave's peak. The baseline for curly-line is at the // middle of singly underline. The gap could be too big, so we also apply // a limit on the peak height. - const auto strokeHalfWidth = font.underline.height / 2.0f; - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); - - // When it's too small to be curly, make it straight. - if (curlyLinePeakHeight < minCurlyLinePeakHeight) { - curlyLinePeakHeight = 0; - } + const auto strokeHalfWidth = font.underline.height / 2.0f; + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. - _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + // The max height of Curly line peak in `em` units. + constexpr auto maxCurlyLinePeakHeightEm = 0.075f; + const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; - const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; - const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); - const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); - const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); - _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + + // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. + _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + + const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; + const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); + const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); + const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); + _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; + } DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); // Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways. From 67c87c9313a9b22f5a107649bc9f38dac03b5d86 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 9 Dec 2023 01:33:12 +0530 Subject: [PATCH 3/7] properly cast to the right type --- src/renderer/gdi/paint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 2f41671fd4e..bb7eb8156d3 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -551,7 +551,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) std::vector points; points.reserve(cPoints); - const auto end = x + cCurlyLines * curlyLineWidth; + const auto end = x + gsl::narrow_cast(cCurlyLines * curlyLineWidth); auto start = x; while (start < end) { From e2ca95fca8ad350a628cfefa236031d99151812b Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sat, 9 Dec 2023 12:06:45 +0530 Subject: [PATCH 4/7] address feedbacks --- src/renderer/gdi/paint.cpp | 5 ++--- src/renderer/gdi/state.cpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index bb7eb8156d3..7de2f248d03 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -541,7 +541,7 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); return S_OK; }; - const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) { + const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { const auto curlyLineWidth = fontWidth; const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f); const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight); @@ -551,9 +551,8 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) std::vector points; points.reserve(cPoints); - const auto end = x + gsl::narrow_cast(cCurlyLines * curlyLineWidth); auto start = x; - while (start < end) + for (size_t i = 0; i < cCurlyLines; i++) { points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight); points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight); diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index 60d8b7798d3..af532d46f05 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -397,7 +397,7 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - const int strokeHalfWidth = std::lround(_lineMetrics.underlineWidth / 2.0); + const int strokeHalfWidth = std::lround(_lineMetrics.underlineWidth / 2.0f); // Since we use GDI pen for drawing, the underline offset should point to // the center of the underline. From 9fa65f11a63f5953eab938496d6b3add95e5dd83 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sun, 10 Dec 2023 14:11:29 +0530 Subject: [PATCH 5/7] add explicit types to DrawStrokedLine() args --- src/renderer/gdi/paint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 7de2f248d03..222576390e2 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -536,9 +536,9 @@ bool GdiEngine::FontHasWesternScript(HDC hdc) const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) { return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) { + const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) { RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr)); - RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y)); + RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast(x + w), y)); return S_OK; }; const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) { From 7a077ae6eb3d00dafeba03737ece9f3eada87530 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Sun, 10 Dec 2023 14:17:08 +0530 Subject: [PATCH 6/7] fix underline-offset and half-stroke-width calc --- src/renderer/gdi/state.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index af532d46f05..bae74e82151 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -397,30 +397,24 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth; } - const int strokeHalfWidth = std::lround(_lineMetrics.underlineWidth / 2.0f); - // Since we use GDI pen for drawing, the underline offset should point to // the center of the underline. - _lineMetrics.underlineOffset += strokeHalfWidth; - _lineMetrics.underlineOffset2 += strokeHalfWidth; - - // We want the underline to always be visible and remain within the cell - // bottom, so we clamp the offset to fit just inside. - _lineMetrics.underlineOffset = std::min(_lineMetrics.underlineOffset, maxUnderlineOffset); - _lineMetrics.underlineOffset2 = std::min(_lineMetrics.underlineOffset2, maxUnderlineOffset); + const auto underlineHalfWidth = gsl::narrow_cast(std::floor(_lineMetrics.underlineWidth / 2.0f)); + _lineMetrics.underlineOffset += underlineHalfWidth; + _lineMetrics.underlineOffset2 += underlineHalfWidth; // Curlyline uses the gap between cell bottom and singly underline position // as the height of the wave's peak. The baseline for curly line is at the // middle of singly underline. The gap could be too big, so we also apply a // limit on the peak height. { - const auto cellBottomGap = std::lround(Font.GetSize().height - _lineMetrics.underlineOffset - strokeHalfWidth); + const auto cellBottomGap = std::max(0, Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth); // get the max height for curly line peak. Max height is in `em` units. constexpr auto maxCurlyLinePeakHeightEm = 0.075f; - const auto maxCurlyLinePeakHeight = std::lround(maxCurlyLinePeakHeightEm * fontSize); + const auto maxCurlyLinePeakHeight = gsl::narrow_cast(std::lround(maxCurlyLinePeakHeightEm * fontSize)); - _lineMetrics.curlylinePeakHeight = std::clamp(cellBottomGap, 0L, maxCurlyLinePeakHeight); + _lineMetrics.curlylinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); } // Now find the size of a 0 in this current font and save it for conversions done later. From 0b1ecf398b3d2ad1124eb95eeca55c72328eddc2 Mon Sep 17 00:00:00 2001 From: Tushar Singh Date: Tue, 12 Dec 2023 13:45:33 +0530 Subject: [PATCH 7/7] force curlyline at all font sizes --- src/renderer/atlas/BackendD3D.cpp | 38 +++++++++++++++++-------------- src/renderer/atlas/BackendD3D.h | 2 +- src/renderer/gdi/state.cpp | 29 ++++++++++++++--------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index bc34c23c105..e68fb2a7a57 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -306,26 +306,30 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) { const auto& font = *p.s->font; - // Curlyline uses the gap between cell bottom and singly underline position - // as the height of the wave's peak. The baseline for curly-line is at the - // middle of singly underline. The gap could be too big, so we also apply - // a limit on the peak height. + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. { - const auto strokeHalfWidth = font.underline.height / 2.0f; - const auto underlineMidY = font.underline.position + strokeHalfWidth; - const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth; - - // The max height of Curly line peak in `em` units. - constexpr auto maxCurlyLinePeakHeightEm = 0.075f; - const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize; + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize)); - auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + // calc the limit we need to apply + const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f); + const auto underlineMidY = font.underline.position + strokeHalfWidth; + const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height; - // We draw a smaller curly line (-1px) to avoid clipping due to the rounding. - _curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f); + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) + { + _curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight); + } - const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight; - const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth); + const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height; + const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height); const auto curlyUnderlinePosU16 = gsl::narrow_cast(lrintf(curlyUnderlinePos)); const auto curlyUnderlineWidthU16 = gsl::narrow_cast(lrintf(curlyUnderlineWidth)); _curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 }; @@ -570,7 +574,7 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast; data.underlineWidth = p.s->font->underline.height; data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x; - data.curlyLinePeakHeight = _curlyLineDrawPeakHeight; + data.curlyLinePeakHeight = _curlyLinePeakHeight; data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0); } diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 0befe8fe342..4c248ed3ff3 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -291,7 +291,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLineDrawPeakHeight = 0; + f32 _curlyLinePeakHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index bae74e82151..13fd0ea59fc 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -403,18 +403,25 @@ GdiEngine::~GdiEngine() _lineMetrics.underlineOffset += underlineHalfWidth; _lineMetrics.underlineOffset2 += underlineHalfWidth; - // Curlyline uses the gap between cell bottom and singly underline position - // as the height of the wave's peak. The baseline for curly line is at the - // middle of singly underline. The gap could be too big, so we also apply a - // limit on the peak height. + // Curlyline is drawn with a desired height relative to the font size. The + // baseline of curlyline is at the middle of singly underline. When there's + // limited space to draw a curlyline, we apply a limit on the peak height. { - const auto cellBottomGap = std::max(0, Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth); - - // get the max height for curly line peak. Max height is in `em` units. - constexpr auto maxCurlyLinePeakHeightEm = 0.075f; - const auto maxCurlyLinePeakHeight = gsl::narrow_cast(std::lround(maxCurlyLinePeakHeightEm * fontSize)); - - _lineMetrics.curlylinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight); + // initialize curlyline peak height to a desired value. Clamp it to at + // least 1. + constexpr auto curlyLinePeakHeightEm = 0.075f; + _lineMetrics.curlylinePeakHeight = gsl::narrow_cast(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize))); + + // calc the limit we need to apply + const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth; + + // if the limit is <= 0 (no height at all), stick with the desired height. + // This is how we force a curlyline even when there's no space, though it + // might be clipped at the bottom. + if (maxDrawableCurlyLinePeakHeight > 0.0f) + { + _lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight); + } } // Now find the size of a 0 in this current font and save it for conversions done later.