font/sprite: update to z2d 0.12.1, use native path insetting (#13489)

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:
Mitchell Hashimoto
2026-07-27 13:20:50 -07:00
committed by GitHub
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