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.
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
BIN
src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png
vendored
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
BIN
src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png
vendored
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png
vendored
|
Before Width: | Height: | Size: 1009 B After Width: | Height: | Size: 1.0 KiB |
BIN
src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png
vendored
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png
vendored
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png
vendored
|
Before Width: | Height: | Size: 832 B After Width: | Height: | Size: 834 B |