terminal/apc/glyph: clone glossary entries

Glossary entries own decoded glyph payloads, so callers could not copy an
entry without sharing outline storage. Add clone methods for glossary entries
and glyf outlines that allocate fresh contour and point slices while copying
the scalar registration metadata directly.

This keeps the ownership contract symmetric with deinit and lets cloned
entries be released independently from their source.
This commit is contained in:
Mitchell Hashimoto
2026-06-05 20:00:54 -07:00
parent f146db5535
commit 502078a276
2 changed files with 54 additions and 0 deletions

View File

@@ -50,6 +50,23 @@ pub const Glyf = struct {
return self.points[start..end];
}
/// Deep copy this outline using `alloc`.
///
/// The returned outline owns its contour and point storage and must be
/// released with `deinit`.
pub fn clone(self: *const Outline, alloc: Allocator) Allocator.Error!Outline {
const contours = try alloc.dupe(sfnt.uint16, self.contours);
errdefer alloc.free(contours);
const points = try alloc.dupe(Point, self.points);
errdefer alloc.free(points);
return .{
.contours = contours,
.points = points,
};
}
/// Free all memory owned by this outline. Pass in the same
/// allocator used for decoding.
pub fn deinit(self: *Outline, alloc: Allocator) void {

View File

@@ -193,6 +193,23 @@ pub const Entry = struct {
self.* = undefined;
}
/// Deep copy this glossary entry using `alloc`.
///
/// The returned entry owns decoded glyph memory and must be released with
/// `deinit`.
pub fn clone(self: *const Entry, alloc: Allocator) Allocator.Error!Entry {
const glyph: Glyph = switch (self.glyph) {
.glyf => |*outline| .{ .glyf = try outline.clone(alloc) },
};
return .{
.glyph = glyph,
.design = self.design,
.width = self.width,
.constraint = self.constraint,
};
}
/// Return the renderer constraint for a register request.
///
/// Glyph Protocol §8.5 defines sizing, alignment, and padding in terms of
@@ -320,6 +337,26 @@ test "Entry init decodes glyf payload and applies register fields" {
try testing.expectEqual(@as(usize, 1), entry.glyph.glyf.contours.len);
}
test "Entry clone deep copies decoded glyf payload" {
const testing = std.testing;
const alloc = testing.allocator;
var entry = try testRegisterEntry(alloc, 0xE000);
defer entry.deinit(alloc);
var cloned = try entry.clone(alloc);
defer cloned.deinit(alloc);
try testing.expectEqual(entry.design, cloned.design);
try testing.expectEqual(entry.width, cloned.width);
try testing.expectEqual(entry.constraint, cloned.constraint);
try testing.expectEqualSlices(u16, entry.glyph.glyf.contours, cloned.glyph.glyf.contours);
try testing.expectEqualSlices(Glyf.Outline.Point, entry.glyph.glyf.points, cloned.glyph.glyf.points);
try testing.expect(entry.glyph.glyf.contours.ptr != cloned.glyph.glyf.contours.ptr);
try testing.expect(entry.glyph.glyf.points.ptr != cloned.glyph.glyf.points.ptr);
}
test "Entry init rejects invalid register payload" {
const testing = std.testing;
const alloc = testing.allocator;