From 9c8058c3260d39e37416154a181441839e0570f6 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 22 Feb 2024 12:53:02 +0100 Subject: [PATCH] AtlasEngine: Improve dotted, dashed and curly underlines (#16719) This changeset makes 3 improvements: * Dotted lines now use a 2:1 ratio between gaps and dots (from 1:1). This makes the dots a lot easier to spot at small font sizes. * Dashed lines use a 1:2 ratio and a cells-size independent stride. By being cell-size independent it works more consistently with a wider variety of fonts with weird cell aspect ratios. * Curly lines are now cell-size independent as well and have a height that equals the double-underline size. This ensures that the curve isn't cut off anymore and just like with dashed lines, that it works under weird aspect ratios. Closes #16712 ## Validation Steps Performed This was tested using RenderingTests using Cascadia Mono, Consolas, Courier New, Lucida Console and MS Gothic. --- src/renderer/atlas/BackendD3D.cpp | 38 +++++++++++-------------------- src/renderer/atlas/BackendD3D.h | 7 +++--- src/renderer/atlas/shader_ps.hlsl | 25 ++++++++------------ 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 69d597ed0bd..221cf3b5ba3 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -310,29 +310,18 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p) // 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. { - // 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)); - - // 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; - - // 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 cellHeight = static_cast(font.cellSize.y); + const auto strokeWidth = static_cast(font.thinLineWidth); + + // This gives it the same position and height as our double-underline. There's no particular reason for that, apart from + // it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc. + // We still need to ensure though that it doesn't clip out of the cellHeight at the bottom. + const auto height = std::max(3.0f, static_cast(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position)); + const auto top = std::min(static_cast(font.doubleUnderline[0].position), floorf(cellHeight - height - strokeWidth)); - 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 }; + _curlyLineHalfHeight = height * 0.5f; + _curlyUnderline.position = gsl::narrow_cast(lrintf(top)); + _curlyUnderline.height = gsl::narrow_cast(lrintf(height)); } DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put()); @@ -573,9 +562,8 @@ void BackendD3D::_recreateConstBuffer(const RenderingPayload& p) const DWrite_GetGammaRatios(_gamma, data.gammaRatios); 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 = _curlyLinePeakHeight; - data.curlyLineCellOffset = p.s->font->underline.position + p.s->font->underline.height / 2.0f; + data.thinLineWidth = p.s->font->thinLineWidth; + data.curlyLineHalfHeight = _curlyLineHalfHeight; 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 4c248ed3ff3..cdf9937cc61 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -42,9 +42,8 @@ namespace Microsoft::Console::Render::Atlas alignas(sizeof(f32x4)) f32 gammaRatios[4]{}; alignas(sizeof(f32)) f32 enhancedContrast = 0; alignas(sizeof(f32)) f32 underlineWidth = 0; - alignas(sizeof(f32)) f32 curlyLinePeakHeight = 0; - alignas(sizeof(f32)) f32 curlyLineWaveFreq = 0; - alignas(sizeof(f32)) f32 curlyLineCellOffset = 0; + alignas(sizeof(f32)) f32 thinLineWidth = 0; + alignas(sizeof(f32)) f32 curlyLineHalfHeight = 0; #pragma warning(suppress : 4324) // 'PSConstBuffer': structure was padded due to alignment specifier }; @@ -291,7 +290,7 @@ namespace Microsoft::Console::Render::Atlas // The bounding rect of _cursorRects in pixels. til::rect _cursorPosition; - f32 _curlyLinePeakHeight = 0.0f; + f32 _curlyLineHalfHeight = 0.0f; FontDecorationPosition _curlyUnderline; bool _requiresContinuousRedraw = false; diff --git a/src/renderer/atlas/shader_ps.hlsl b/src/renderer/atlas/shader_ps.hlsl index e19ba955fe5..d3c0bfac6c3 100644 --- a/src/renderer/atlas/shader_ps.hlsl +++ b/src/renderer/atlas/shader_ps.hlsl @@ -12,9 +12,8 @@ cbuffer ConstBuffer : register(b0) float4 gammaRatios; float enhancedContrast; float underlineWidth; - float curlyLinePeakHeight; - float curlyLineWaveFreq; - float curlyLineCellOffset; + float thinLineWidth; + float curlyLineHalfHeight; } Texture2D background : register(t0); @@ -76,31 +75,25 @@ Output main(PSData data) : SV_Target } case SHADING_TYPE_DOTTED_LINE: { - const bool on = frac(data.position.x / (2.0f * underlineWidth * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (3.0f * underlineWidth * data.renditionScale.x)) < (1.0f / 3.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_DASHED_LINE: { - const bool on = frac(data.position.x / (backgroundCellSize.x * data.renditionScale.x)) < 0.5f; + const bool on = frac(data.position.x / (6.0f * underlineWidth * data.renditionScale.x)) < (4.0f / 6.0f); color = on * premultiplyColor(data.color); weights = color.aaaa; break; } case SHADING_TYPE_CURLY_LINE: { - uint cellRow = floor(data.position.y / backgroundCellSize.y); - // Use the previous cell when drawing 'Double Height' curly line. - cellRow -= data.renditionScale.y - 1; - const float cellTop = cellRow * backgroundCellSize.y; - const float centerY = cellTop + curlyLineCellOffset * data.renditionScale.y; - const float strokeWidthHalf = underlineWidth * data.renditionScale.y / 2.0f; - const float amp = curlyLinePeakHeight * data.renditionScale.y; - const float freq = curlyLineWaveFreq / data.renditionScale.x; - - const float s = sin(data.position.x * freq); - const float d = abs(centerY - (s * amp) - data.position.y); + const float strokeWidthHalf = thinLineWidth * data.renditionScale.y * 0.5f; + const float amp = (curlyLineHalfHeight - strokeWidthHalf) * data.renditionScale.y; + const float freq = data.renditionScale.x / curlyLineHalfHeight * 1.57079632679489661923f; + const float s = sin(data.position.x * freq) * amp; + const float d = abs(curlyLineHalfHeight - data.texcoord.y - s); const float a = 1 - saturate(d - strokeWidthHalf); color = a * premultiplyColor(data.color); weights = color.aaaa;