Skip to content

Commit f5b45c2

Browse files
authored
Fix curlyline rendering in AtlasEngine and GDIRenderer (microsoft#16444)
Fixes Curlyline being drawn as single underline in some cases **Detailed Description** - Curlyline is drawn at all font sizes. - We might render a curlyline that is clipped in cases where we don't have enough space to draw a full curlyline. This is to give users a consistent view of Curlylines. Previously in those cases, it was drawn as a single underline. - Removed minimum threshold `minCurlyLinePeakHeight` for Curlyline drawing. - GDIRender changes: - Underline offset now points to the (vertical) mid position of the underline. Removes redundant `underlineMidY` calculation inside the draw call. Closes microsoft#16288
1 parent 17867af commit f5b45c2

File tree

4 files changed

+65
-73
lines changed

4 files changed

+65
-73
lines changed

src/renderer/atlas/BackendD3D.cpp

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -306,37 +306,35 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p)
306306
{
307307
const auto& font = *p.s->font;
308308

309-
// The max height of Curly line peak in `em` units.
310-
const auto maxCurlyLinePeakHeightEm = 0.075f;
311-
// We aim for atleast 1px height, but since we draw 1px smaller curly line,
312-
// we aim for 2px height as a result.
313-
const auto minCurlyLinePeakHeight = 2.0f;
314-
315-
// Curlyline uses the gap between cell bottom and singly underline position
316-
// as the height of the wave's peak. The baseline for curly-line is at the
317-
// middle of singly underline. The gap could be too big, so we also apply
318-
// a limit on the peak height.
319-
const auto strokeHalfWidth = font.underline.height / 2.0f;
320-
const auto underlineMidY = font.underline.position + strokeHalfWidth;
321-
const auto cellBottomGap = font.cellSize.y - underlineMidY - strokeHalfWidth;
322-
const auto maxCurlyLinePeakHeight = maxCurlyLinePeakHeightEm * font.fontSize;
323-
auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight);
309+
// Curlyline is drawn with a desired height relative to the font size. The
310+
// baseline of curlyline is at the middle of singly underline. When there's
311+
// limited space to draw a curlyline, we apply a limit on the peak height.
312+
{
313+
// initialize curlyline peak height to a desired value. Clamp it to at
314+
// least 1.
315+
constexpr auto curlyLinePeakHeightEm = 0.075f;
316+
_curlyLinePeakHeight = std::max(1.0f, std::roundf(curlyLinePeakHeightEm * font.fontSize));
317+
318+
// calc the limit we need to apply
319+
const auto strokeHalfWidth = std::floor(font.underline.height / 2.0f);
320+
const auto underlineMidY = font.underline.position + strokeHalfWidth;
321+
const auto maxDrawableCurlyLinePeakHeight = font.cellSize.y - underlineMidY - font.underline.height;
322+
323+
// if the limit is <= 0 (no height at all), stick with the desired height.
324+
// This is how we force a curlyline even when there's no space, though it
325+
// might be clipped at the bottom.
326+
if (maxDrawableCurlyLinePeakHeight > 0.0f)
327+
{
328+
_curlyLinePeakHeight = std::min(_curlyLinePeakHeight, maxDrawableCurlyLinePeakHeight);
329+
}
324330

325-
// When it's too small to be curly, make it straight.
326-
if (curlyLinePeakHeight < minCurlyLinePeakHeight)
327-
{
328-
curlyLinePeakHeight = 0;
331+
const auto curlyUnderlinePos = underlineMidY - _curlyLinePeakHeight - font.underline.height;
332+
const auto curlyUnderlineWidth = 2.0f * (_curlyLinePeakHeight + font.underline.height);
333+
const auto curlyUnderlinePosU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlinePos));
334+
const auto curlyUnderlineWidthU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlineWidth));
335+
_curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 };
329336
}
330337

331-
// We draw a smaller curly line (-1px) to avoid clipping due to the rounding.
332-
_curlyLineDrawPeakHeight = std::max(0.0f, curlyLinePeakHeight - 1.0f);
333-
334-
const auto curlyUnderlinePos = font.underline.position - curlyLinePeakHeight;
335-
const auto curlyUnderlineWidth = 2.0f * (curlyLinePeakHeight + strokeHalfWidth);
336-
const auto curlyUnderlinePosU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlinePos));
337-
const auto curlyUnderlineWidthU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlineWidth));
338-
_curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 };
339-
340338
DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put());
341339
// Clearing the atlas requires BeginDraw(), which is expensive. Defer this until we need Direct2D anyways.
342340
_fontChangedResetGlyphAtlas = true;
@@ -576,7 +574,7 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const
576574
data.enhancedContrast = p.s->font->antialiasingMode == AntialiasingMode::ClearType ? _cleartypeEnhancedContrast : _grayscaleEnhancedContrast;
577575
data.underlineWidth = p.s->font->underline.height;
578576
data.curlyLineWaveFreq = 2.0f * 3.14f / p.s->font->cellSize.x;
579-
data.curlyLinePeakHeight = _curlyLineDrawPeakHeight;
577+
data.curlyLinePeakHeight = _curlyLinePeakHeight;
580578
data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f;
581579
p.deviceContext->UpdateSubresource(_psConstantBuffer.get(), 0, nullptr, &data, 0, 0);
582580
}

src/renderer/atlas/BackendD3D.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ namespace Microsoft::Console::Render::Atlas
291291
// The bounding rect of _cursorRects in pixels.
292292
til::rect _cursorPosition;
293293

294-
f32 _curlyLineDrawPeakHeight = 0;
294+
f32 _curlyLinePeakHeight = 0.0f;
295295
FontDecorationPosition _curlyUnderline;
296296

297297
bool _requiresContinuousRedraw = false;

src/renderer/gdi/paint.cpp

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -536,27 +536,30 @@ bool GdiEngine::FontHasWesternScript(HDC hdc)
536536
const auto DrawLine = [=](const auto x, const auto y, const auto w, const auto h) {
537537
return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY);
538538
};
539-
const auto DrawStrokedLine = [&](const auto x, const auto y, const auto w) {
539+
const auto DrawStrokedLine = [&](const til::CoordType x, const til::CoordType y, const unsigned w) {
540540
RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr));
541-
RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, x + w, y));
541+
RETURN_HR_IF(E_FAIL, !LineTo(_hdcMemoryContext, gsl::narrow_cast<int>(x + w), y));
542542
return S_OK;
543543
};
544-
const auto DrawCurlyLine = [&](const auto x, const auto y, const auto cCurlyLines) {
544+
const auto DrawCurlyLine = [&](const til::CoordType x, const til::CoordType y, const size_t cCurlyLines) {
545545
const auto curlyLineWidth = fontWidth;
546-
const auto curlyLineHalfWidth = lrintf(curlyLineWidth / 2.0f);
547-
const auto controlPointHeight = gsl::narrow_cast<long>(std::floor(3.5f * _lineMetrics.curlylinePeakHeight));
546+
const auto curlyLineHalfWidth = std::lround(curlyLineWidth / 2.0f);
547+
const auto controlPointHeight = std::lround(3.5f * _lineMetrics.curlylinePeakHeight);
548+
548549
// Each curlyLine requires 3 `POINT`s
549550
const auto cPoints = gsl::narrow<DWORD>(3 * cCurlyLines);
550551
std::vector<POINT> points;
551552
points.reserve(cPoints);
553+
552554
auto start = x;
553-
for (auto i = 0u; i < cCurlyLines; i++)
555+
for (size_t i = 0; i < cCurlyLines; i++)
554556
{
555557
points.emplace_back(start + curlyLineHalfWidth, y - controlPointHeight);
556558
points.emplace_back(start + curlyLineHalfWidth, y + controlPointHeight);
557559
points.emplace_back(start + curlyLineWidth, y);
558560
start += curlyLineWidth;
559561
}
562+
560563
RETURN_HR_IF(E_FAIL, !MoveToEx(_hdcMemoryContext, x, y, nullptr));
561564
RETURN_HR_IF(E_FAIL, !PolyBezierTo(_hdcMemoryContext, points.data(), cPoints));
562565
return S_OK;
@@ -619,28 +622,26 @@ bool GdiEngine::FontHasWesternScript(HDC hdc)
619622
const auto prevPen = wil::SelectObject(_hdcMemoryContext, hpen.get());
620623
RETURN_HR_IF_NULL(E_FAIL, prevPen.get());
621624

622-
const auto underlineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset + _lineMetrics.underlineWidth / 2.0f);
623625
if (lines.test(GridLines::Underline))
624626
{
625-
return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells);
627+
return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells);
626628
}
627629
else if (lines.test(GridLines::DoubleUnderline))
628630
{
629-
const auto doubleUnderlineBottomLineMidY = std::lround(ptTarget.y + _lineMetrics.underlineOffset2 + _lineMetrics.underlineWidth / 2.0f);
630-
RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells));
631-
return DrawStrokedLine(ptTarget.x, doubleUnderlineBottomLineMidY, widthOfAllCells);
631+
RETURN_IF_FAILED(DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells));
632+
return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset2, widthOfAllCells);
632633
}
633634
else if (lines.test(GridLines::CurlyUnderline))
634635
{
635-
return DrawCurlyLine(ptTarget.x, underlineMidY, cchLine);
636+
return DrawCurlyLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, cchLine);
636637
}
637638
else if (lines.test(GridLines::DottedUnderline))
638639
{
639-
return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells);
640+
return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells);
640641
}
641642
else if (lines.test(GridLines::DashedUnderline))
642643
{
643-
return DrawStrokedLine(ptTarget.x, underlineMidY, widthOfAllCells);
644+
return DrawStrokedLine(ptTarget.x, ptTarget.y + _lineMetrics.underlineOffset, widthOfAllCells);
644645
}
645646

646647
return S_OK;

src/renderer/gdi/state.cpp

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,6 @@
1111

1212
using namespace Microsoft::Console::Render;
1313

14-
namespace
15-
{
16-
// The max height of Curly line peak in `em` units.
17-
constexpr auto MaxCurlyLinePeakHeightEm = 0.075f;
18-
19-
// The min height of Curly line peak.
20-
constexpr auto MinCurlyLinePeakHeight = 2.0f;
21-
}
22-
2314
// Routine Description:
2415
// - Creates a new GDI-based rendering engine
2516
// - NOTE: Will throw if initialization failure. Caller must catch.
@@ -406,29 +397,31 @@ GdiEngine::~GdiEngine()
406397
_lineMetrics.underlineOffset2 = _lineMetrics.underlineOffset - _lineMetrics.gridlineWidth;
407398
}
408399

409-
// Curly line doesn't render properly below 1px stroke width. Make it a straight line.
410-
if (_lineMetrics.underlineWidth < 1)
411-
{
412-
_lineMetrics.curlylinePeakHeight = 0;
413-
}
414-
else
400+
// Since we use GDI pen for drawing, the underline offset should point to
401+
// the center of the underline.
402+
const auto underlineHalfWidth = gsl::narrow_cast<int>(std::floor(_lineMetrics.underlineWidth / 2.0f));
403+
_lineMetrics.underlineOffset += underlineHalfWidth;
404+
_lineMetrics.underlineOffset2 += underlineHalfWidth;
405+
406+
// Curlyline is drawn with a desired height relative to the font size. The
407+
// baseline of curlyline is at the middle of singly underline. When there's
408+
// limited space to draw a curlyline, we apply a limit on the peak height.
415409
{
416-
// Curlyline uses the gap between cell bottom and singly underline
417-
// position as the height of the wave's peak. The baseline for curly
418-
// line is at the middle of singly underline. The gap could be too big,
419-
// so we also apply a limit on the peak height.
420-
const auto strokeHalfWidth = _lineMetrics.underlineWidth / 2.0f;
421-
const auto underlineMidY = _lineMetrics.underlineOffset + strokeHalfWidth;
422-
const auto cellBottomGap = Font.GetSize().height - underlineMidY - strokeHalfWidth;
423-
const auto maxCurlyLinePeakHeight = MaxCurlyLinePeakHeightEm * fontSize;
424-
auto curlyLinePeakHeight = std::min(cellBottomGap, maxCurlyLinePeakHeight);
425-
426-
// When it's too small to be curly, make it a straight line.
427-
if (curlyLinePeakHeight < MinCurlyLinePeakHeight)
410+
// initialize curlyline peak height to a desired value. Clamp it to at
411+
// least 1.
412+
constexpr auto curlyLinePeakHeightEm = 0.075f;
413+
_lineMetrics.curlylinePeakHeight = gsl::narrow_cast<int>(std::max(1L, std::lround(curlyLinePeakHeightEm * fontSize)));
414+
415+
// calc the limit we need to apply
416+
const auto maxDrawableCurlyLinePeakHeight = Font.GetSize().height - _lineMetrics.underlineOffset - _lineMetrics.underlineWidth;
417+
418+
// if the limit is <= 0 (no height at all), stick with the desired height.
419+
// This is how we force a curlyline even when there's no space, though it
420+
// might be clipped at the bottom.
421+
if (maxDrawableCurlyLinePeakHeight > 0.0f)
428422
{
429-
curlyLinePeakHeight = 0.0f;
423+
_lineMetrics.curlylinePeakHeight = std::min(_lineMetrics.curlylinePeakHeight, maxDrawableCurlyLinePeakHeight);
430424
}
431-
_lineMetrics.curlylinePeakHeight = gsl::narrow_cast<int>(std::floor(curlyLinePeakHeight));
432425
}
433426

434427
// Now find the size of a 0 in this current font and save it for conversions done later.

0 commit comments

Comments
 (0)