font/sprite: update to z2d 0.12.1, use native path insetting

This change updates z2d to 0.12.1 and changes the sprite font path
insetting functionality to use the new path offset abilities released in
the update.

In addition, there has been a slight change to the drawing of E0B5 and
its respective reflection; we now add a 1-pixel horizontal line segment
to each end to force them to be perpendicular. This is because
offsetting pre-expands the curves and ultimately causes the end segments
of the curve itself to have slight non-horizontal angles, which produce
small artifacts at the ends without the forced horizontal ends.
This commit is contained in:
Chris Marchesi
2026-07-27 09:09:12 -07:00
parent 739603b8a2
commit d0e72a3ab6
13 changed files with 26 additions and 88 deletions

View File

@@ -30,8 +30,8 @@
},
.z2d = .{
// vancluever/z2d
.url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
.hash = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
.url = "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz",
.hash = "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-",
.lazy = true,
},
.zig_objc = .{

6
build.zig.zon.json generated
View File

@@ -154,10 +154,10 @@
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
"z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR": {
"z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-": {
"name": "z2d",
"url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
"hash": "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA="
"url": "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz",
"hash": "sha256-Fjr1ORn0ozxU7QY09o2ZOZQHIJsXfVw0UCdZxTKmZJ0="
},
"zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW": {
"name": "zf",

6
build.zig.zon.nix generated
View File

@@ -377,11 +377,11 @@ in
};
}
{
name = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR";
name = "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-";
path = fetchZigArtifact {
name = "z2d";
url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz";
hash = "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA=";
url = "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz";
hash = "sha256-Fjr1ORn0ozxU7QY09o2ZOZQHIJsXfVw0UCdZxTKmZJ0=";
unpack = true;
};
}

2
build.zig.zon.txt generated
View File

@@ -30,9 +30,9 @@ https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaP
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz
https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz
https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz
https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz
https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz

View File

@@ -187,9 +187,9 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz",
"dest": "vendor/p/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
"sha256": "d48b117307f767ca379bf31bd171880e66dcafb89eca4a8f923222619992fcb4"
"url": "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz",
"dest": "vendor/p/z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-",
"sha256": "e55c9d0b156edaddfea32fb1c0e3597e1b6306b9a4ec1888e2f8008c598bced8"
},
{
"type": "archive",

View File

@@ -375,85 +375,17 @@ pub const Canvas = struct {
);
}
/// Do an inner stroke on a z2d path, right now this involves a pretty
/// heavy workaround that uses two extra surfaces; in the future, z2d
/// should add inner and outer strokes natively.
/// Do an inner stroke using a 1/2 stroke width inset of the supplied
/// `path`.
pub fn innerStrokePath(
self: *Canvas,
path: z2d.Path,
opts: z2d.painter.StrokeOptions,
color: Color,
) (z2d.painter.StrokeError || z2d.painter.FillError)!void {
// On one surface we fill the shape, this will be a mask we
// multiply with the double-width stroke so that only the
// part inside is used.
var fill_sfc: z2d.Surface = try .init(
.image_surface_alpha8,
self.alloc,
self.sfc.getWidth(),
self.sfc.getHeight(),
);
defer fill_sfc.deinit(self.alloc);
// On the other we'll do the double width stroke.
var stroke_sfc: z2d.Surface = try .init(
.image_surface_alpha8,
self.alloc,
self.sfc.getWidth(),
self.sfc.getHeight(),
);
defer stroke_sfc.deinit(self.alloc);
// Make a closed version of the path for our fill, so
// that we can support open paths for inner stroke.
var closed_path = path;
closed_path.nodes = try path.nodes.clone(self.alloc);
defer closed_path.deinit(self.alloc);
try closed_path.close(self.alloc);
// Fill the shape in white to the fill surface, we use
// white because this is a mask that we'll multiply with
// the stroke, we want everything inside to be the stroke
// color.
try z2d.painter.fill(
self.alloc,
&fill_sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = 255 } },
} },
closed_path.nodes.items,
.{},
);
// Stroke the shape with double the desired width.
var mut_opts = opts;
mut_opts.line_width *= 2;
try z2d.painter.stroke(
self.alloc,
&stroke_sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
} },
path.nodes.items,
mut_opts,
);
// We multiply the stroke sfc on to the fill surface.
// The z2d composite operation doesn't seem to work for
// this with alpha8 surfaces, so we have to do it manually.
for (
std.mem.sliceAsBytes(fill_sfc.image_surface_alpha8.buf),
std.mem.sliceAsBytes(stroke_sfc.image_surface_alpha8.buf),
) |*d, s| {
d.* = @intFromFloat(@round(
255.0 *
(@as(f64, @floatFromInt(s)) / 255.0) *
(@as(f64, @floatFromInt(d.*)) / 255.0),
));
}
// Then we composite the result on to the main surface.
self.sfc.composite(&fill_sfc, .src_over, 0, 0, .{});
) (z2d.Path.OffsetError || z2d.painter.StrokeError)!void {
var inset_path = try path.offset(self.alloc, -opts.line_width / 2.0);
defer inset_path.deinit(self.alloc);
return self.strokePath(inset_path, opts, color);
}
/// Fill a z2d path.

View File

@@ -292,8 +292,13 @@ pub fn drawE0B5(
const radius: f64 = @min(float_width, float_height / 2);
var path = canvas.staticPath(4);
// Note on the first/final lineTo moves: this is to enforce horizontal
// lines on both ends. This ensures that when the path is offset and
// stroked, the ends are butt-capped entirely perpendicularly; curveTo
// lerping would produce very slight slopes otherwise.
var path = canvas.staticPath(6);
path.moveTo(0, 0);
path.lineTo(1, 0);
path.curveTo(
radius * c,
0,
@@ -308,9 +313,10 @@ pub fn drawE0B5(
float_height - radius + radius * c,
radius * c,
float_height,
0,
1,
float_height,
);
path.lineTo(0, float_height);
try canvas.innerStrokePath(path.wrapped_path, .{
.line_width = @floatFromInt(metrics.box_thickness),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 834 B