font/sprite: rework dotted underline

Draw proper anti-aliased dots now instead of rectangles, thanks to z2d
this is very easy to do, and the results are very nice, no more weird
gaps in dotted underlines if your cell is the wrong number of pixels
across.
pull/9654/head
Qwerasd 2025-11-20 13:36:09 -07:00
parent 81a6c24186
commit 3280cf7d34
1 changed files with 40 additions and 30 deletions

View File

@ -82,46 +82,56 @@ pub fn underline_dotted(
) !void {
_ = cp;
var ctx = canvas.getContext();
defer ctx.deinit();
const float_width: f64 = @floatFromInt(width);
const float_height: f64 = @floatFromInt(height);
const float_pos: f64 = @floatFromInt(metrics.underline_position);
const float_thick: f64 = @floatFromInt(metrics.underline_thickness);
// The diameter will be sqrt2 * the usual underline thickness
// since otherwise dotted underlines look somewhat anemic.
const radius = std.math.sqrt1_2 * float_thick;
// We can go beyond the height of the cell a bit, but
// we want to be sure never to exceed the height of the
// canvas, which extends a quarter cell below the cell
// height.
const padding: f64 = @floatFromInt(canvas.padding_y);
const y = @min(
metrics.underline_position,
height +| canvas.padding_y -| metrics.underline_thickness,
// The center of the underline stem.
float_pos + 0.5 * float_thick,
// The lowest we can go on the canvas and not get clipped.
float_height + padding - @ceil(radius),
);
const dot_width = @max(
// Dots should be at least as thick as the underline.
metrics.underline_thickness,
// At least as thick as a quarter of the cell, since
// less than that starts to look a little bit silly.
metrics.cell_width / 4,
// And failing all else, be at least 1 pixel wide.
1,
);
const dot_count = @max(
// We should try to have enough dots that the
// space between them is the same as their size.
(width / dot_width) / 2,
const dot_count: f64 = @max(
@min(
// We should try to have enough dots that the
// space between them matches their diameter.
@ceil(float_width / (4 * radius)),
// And not enough that the space between
// each dot is less than their radius.
@floor(float_width / (3 * radius)),
// And definitely not enough that the space
// between them is less than a single pixel.
@floor(float_width / (2 * radius + 1)),
),
// And we must have at least one dot per cell.
1,
1.0,
);
const gap_width = std.math.divCeil(
u32,
width -| (dot_count * dot_width),
dot_count,
) catch return error.MathError;
var i: u32 = 0;
while (i < dot_count) : (i += 1) {
const x = i * (dot_width + gap_width);
canvas.rect(.{
.x = @intCast(x),
.y = @intCast(y),
.width = @intCast(dot_width),
.height = @intCast(metrics.underline_thickness),
}, .on);
// What we essentially do is divide the cell in to
// dot_count areas with a dot centered in each one.
var x: f64 = (float_width / dot_count) / 2;
for (0..@as(usize, @intFromFloat(dot_count))) |_| {
try ctx.arc(x, y, radius, 0.0, std.math.tau);
try ctx.closePath();
x += float_width / dot_count;
}
try ctx.fill();
}
pub fn underline_dashed(