Skip to content

Commit

Permalink
AtlasEngine: Improve dotted, dashed and curly underlines (#16719)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lhecker authored Feb 22, 2024
1 parent bf25595 commit 9c8058c
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 45 deletions.
38 changes: 13 additions & 25 deletions src/renderer/atlas/BackendD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<f32>(font.cellSize.y);
const auto strokeWidth = static_cast<f32>(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<f32>(font.doubleUnderline[1].position + font.doubleUnderline[1].height - font.doubleUnderline[0].position));
const auto top = std::min(static_cast<f32>(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<u16>(lrintf(curlyUnderlinePos));
const auto curlyUnderlineWidthU16 = gsl::narrow_cast<u16>(lrintf(curlyUnderlineWidth));
_curlyUnderline = { curlyUnderlinePosU16, curlyUnderlineWidthU16 };
_curlyLineHalfHeight = height * 0.5f;
_curlyUnderline.position = gsl::narrow_cast<u16>(lrintf(top));
_curlyUnderline.height = gsl::narrow_cast<u16>(lrintf(height));
}

DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put());
Expand Down Expand Up @@ -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);
}
}
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/atlas/BackendD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down Expand Up @@ -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;
Expand Down
25 changes: 9 additions & 16 deletions src/renderer/atlas/shader_ps.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -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<float4> background : register(t0);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 9c8058c

Please sign in to comment.