font: add glyf entry decoder to outline
Add Glyf.Outline for decoding the contours and points of a Glyf.pull/12893/head
parent
5758e14931
commit
d8f56b790e
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const sfnt = @import("sfnt.zig");
|
||||
|
||||
/// Glyph Data Table
|
||||
|
|
@ -15,6 +16,49 @@ const sfnt = @import("sfnt.zig");
|
|||
pub const Glyf = struct {
|
||||
data: []const u8,
|
||||
|
||||
/// A decoded glyph outline.
|
||||
///
|
||||
/// The `countours` slice is the list of end point indices and
|
||||
/// `points` owns all the points. Glyf guarantees that contour
|
||||
/// points are sequential so we can just store the end and calculate
|
||||
/// the points that way. Use the helpers to make it ergonomic.
|
||||
pub const Outline = struct {
|
||||
/// List of contour end points. Calculate the full list of
|
||||
/// points using points[prev...this+1]
|
||||
contours: []sfnt.uint16,
|
||||
|
||||
/// The backing storage of all points in the entry.
|
||||
points: []Point,
|
||||
|
||||
/// A single decoded point in a simple glyph contour.
|
||||
pub const Point = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
on_curve: bool,
|
||||
};
|
||||
|
||||
/// Return the point slice for the contour at `index`.
|
||||
///
|
||||
/// The returned slice references `points` and is invalidated when
|
||||
/// this outline is deinitialized.
|
||||
pub fn contour(self: Outline, index: usize) []Point {
|
||||
const start = if (index == 0)
|
||||
0
|
||||
else
|
||||
@as(usize, self.contours[index - 1]) + 1;
|
||||
const end = @as(usize, self.contours[index]) + 1;
|
||||
return self.points[start..end];
|
||||
}
|
||||
|
||||
/// Free all memory owned by this outline. Pass in the same
|
||||
/// allocator used for decoding.
|
||||
pub fn deinit(self: *Outline, alloc: Allocator) void {
|
||||
alloc.free(self.contours);
|
||||
alloc.free(self.points);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// https://learn.microsoft.com/en-us/typography/opentype/spec/glyf#table-organization
|
||||
pub const Entry = struct {
|
||||
header: Header,
|
||||
|
|
@ -241,6 +285,12 @@ pub const Glyf = struct {
|
|||
TooManyPoints,
|
||||
};
|
||||
|
||||
/// Errors that can be returned from `Entry.decode()`.
|
||||
pub const DecodeError = SizeError || Allocator.Error || error{
|
||||
/// Coordinate delta accumulation overflowed.
|
||||
CoordinateOverflow,
|
||||
};
|
||||
|
||||
/// Determines the size (in bytes) of this entry.
|
||||
///
|
||||
/// If the entry is valid, returns the number of bytes
|
||||
|
|
@ -393,6 +443,149 @@ pub const Glyf = struct {
|
|||
// No issues found, the glyf entry is valid, return its length.
|
||||
return @sizeOf(Header) + fbs.pos;
|
||||
}
|
||||
|
||||
/// Decode this simple glyph entry into an owned outline.
|
||||
///
|
||||
/// NOTE: Currently produces errors when given composite glyphs
|
||||
/// or any glyphs that have hinting instructions included.
|
||||
pub fn decode(self: Entry, alloc: Allocator) DecodeError!Glyf.Outline {
|
||||
// We only support simple glyphs.
|
||||
switch (self.entryType()) {
|
||||
.simple => {},
|
||||
.composite => return error.CompositeNotSupported,
|
||||
}
|
||||
|
||||
var fbs = std.io.fixedBufferStream(self.data);
|
||||
const reader = fbs.reader();
|
||||
|
||||
// A zero-contour glyph may be header-only. See size for the
|
||||
// reason for the hardcoded 2 here.
|
||||
const num_contours: usize = @intCast(self.header.numberOfContours);
|
||||
if (num_contours == 0 and self.data.len < 2) return .{
|
||||
.points = &.{},
|
||||
.contours = &.{},
|
||||
};
|
||||
|
||||
// We now know our full amount of contour ending points.
|
||||
const end_points = try alloc.alloc(sfnt.uint16, num_contours);
|
||||
errdefer alloc.free(end_points);
|
||||
|
||||
// If we have no contours, then the only possible remaining
|
||||
// field is instructionLength. Instructions are not supported.
|
||||
if (num_contours == 0) {
|
||||
const instructions_length = try reader.readInt(sfnt.uint16, .big);
|
||||
if (instructions_length > 0) return error.InstructionsNotSupported;
|
||||
return .{ .points = &.{}, .contours = end_points };
|
||||
}
|
||||
|
||||
// The number of points is determined by the final end point
|
||||
// entry since the entries have to be monotonic (something
|
||||
// we verify below).
|
||||
const point_count: usize = point_count: {
|
||||
var prev_end_point: isize = -1;
|
||||
|
||||
// Go through the end points array and update our end_points
|
||||
// with the valid index. The final endpoint tells us our point
|
||||
// count, since endpoints are stored as inclusive point indices.
|
||||
for (0..end_points.len) |i| {
|
||||
const index = try reader.readInt(sfnt.uint16, .big);
|
||||
if (index <= prev_end_point) return error.EndPointsOutOfOrder;
|
||||
prev_end_point = index;
|
||||
end_points[i] = index;
|
||||
}
|
||||
|
||||
// The final point tells us our point count.
|
||||
break :point_count @as(usize, end_points[end_points.len - 1]) + 1;
|
||||
};
|
||||
|
||||
// Instructions are not supported.
|
||||
const instructions_length = try reader.readInt(sfnt.uint16, .big);
|
||||
if (instructions_length > 0) return error.InstructionsNotSupported;
|
||||
|
||||
// Allocate our points right away even though the next entries
|
||||
// are flags. We want to do this so that if the allocator is
|
||||
// a bump allocator, the flags free will actually free it.
|
||||
const points = try alloc.alloc(Glyf.Outline.Point, point_count);
|
||||
errdefer alloc.free(points);
|
||||
|
||||
// This is EXTREMELY annoying but all the flags are separate
|
||||
// from the points so we have to do some allocation here since
|
||||
// its a dynamic amount and we need to save the values for later.
|
||||
//
|
||||
// Typical glyphs have small point counts, so use stack storage
|
||||
// first while still falling back to the caller's allocator for
|
||||
// unusually large outlines.
|
||||
var flags_stack = std.heap.stackFallback(4096, alloc);
|
||||
const flags_alloc = flags_stack.get();
|
||||
const flags = try flags_alloc.alloc(SimpleFlags, point_count);
|
||||
defer flags_alloc.free(flags);
|
||||
{
|
||||
var point_i: usize = 0;
|
||||
while (point_i < point_count) {
|
||||
const flag: SimpleFlags = @bitCast(try reader.readByte());
|
||||
flags[point_i] = flag;
|
||||
point_i += 1;
|
||||
|
||||
if (flag.repeat) {
|
||||
const repeat_count: usize = try reader.readByte();
|
||||
if (point_i + repeat_count > point_count) return error.TooManyPoints;
|
||||
|
||||
for (0..repeat_count) |_| {
|
||||
flags[point_i] = flag;
|
||||
point_i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through x coordinate deltas
|
||||
var x: i32 = 0;
|
||||
for (flags, points) |flag, *point| {
|
||||
const dx: i32 = if (flag.x_short) short: {
|
||||
break :short if (flag.x_repeat_or_sign)
|
||||
@as(i32, try reader.readByte())
|
||||
else
|
||||
-@as(i32, try reader.readByte());
|
||||
} else if (!flag.x_repeat_or_sign)
|
||||
@as(i32, try reader.readInt(sfnt.int16, .big))
|
||||
else
|
||||
0;
|
||||
|
||||
x = std.math.add(
|
||||
i32,
|
||||
x,
|
||||
dx,
|
||||
) catch return error.CoordinateOverflow;
|
||||
point.x = x;
|
||||
}
|
||||
|
||||
// Go through y coordinate deltas
|
||||
var y: i32 = 0;
|
||||
for (flags, points) |flag, *point| {
|
||||
const dy: i32 = if (flag.y_short) short: {
|
||||
break :short if (flag.y_repeat_or_sign)
|
||||
@as(i32, try reader.readByte())
|
||||
else
|
||||
-@as(i32, try reader.readByte());
|
||||
} else if (!flag.y_repeat_or_sign)
|
||||
@as(i32, try reader.readInt(sfnt.int16, .big))
|
||||
else
|
||||
0;
|
||||
|
||||
y = std.math.add(
|
||||
i32,
|
||||
y,
|
||||
dy,
|
||||
) catch return error.CoordinateOverflow;
|
||||
point.y = y;
|
||||
point.on_curve = flag.on_curve;
|
||||
}
|
||||
|
||||
return .{
|
||||
.points = points,
|
||||
.contours = end_points,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize the table from the provided data.
|
||||
|
|
@ -451,6 +644,33 @@ pub fn getGlyph(font: sfnt.SFNT, index: usize) !struct { usize, Glyf.Entry } {
|
|||
return .{ end_offset - start_offset, try glyf.entry(start_offset) };
|
||||
}
|
||||
|
||||
fn testAppendInt(
|
||||
buf: *std.ArrayList(u8),
|
||||
alloc: Allocator,
|
||||
comptime T: type,
|
||||
value: T,
|
||||
) !void {
|
||||
var bytes: [@sizeOf(T)]u8 = undefined;
|
||||
std.mem.writeInt(T, &bytes, value, .big);
|
||||
try buf.appendSlice(alloc, &bytes);
|
||||
}
|
||||
|
||||
fn testAppendHeader(
|
||||
buf: *std.ArrayList(u8),
|
||||
alloc: Allocator,
|
||||
number_of_contours: i16,
|
||||
x_min: i16,
|
||||
y_min: i16,
|
||||
x_max: i16,
|
||||
y_max: i16,
|
||||
) !void {
|
||||
try testAppendInt(buf, alloc, i16, number_of_contours);
|
||||
try testAppendInt(buf, alloc, i16, x_min);
|
||||
try testAppendInt(buf, alloc, i16, y_min);
|
||||
try testAppendInt(buf, alloc, i16, x_max);
|
||||
try testAppendInt(buf, alloc, i16, y_max);
|
||||
}
|
||||
|
||||
test "glyf" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
|
@ -475,12 +695,184 @@ test "glyf" {
|
|||
try testing.expect(glyph_A.entryType() == .simple);
|
||||
try testing.expect(len_A >= try glyph_A.size());
|
||||
|
||||
var outline_A = try glyph_A.decode(alloc);
|
||||
defer outline_A.deinit(alloc);
|
||||
try testing.expectEqual(@as(usize, @intCast(glyph_A.header.numberOfContours)), outline_A.contours.len);
|
||||
try testing.expect(outline_A.points.len > 0);
|
||||
|
||||
// Glyph "Ĩ" is at index 265.
|
||||
const len_Itilde, const glyph_Itilde = try getGlyph(font, 265);
|
||||
try testing.expect(glyph_Itilde.entryType() == .simple);
|
||||
try testing.expect(len_Itilde >= try glyph_Itilde.size());
|
||||
}
|
||||
|
||||
test "glyf: decode triangle" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 1, 100, 100, 900, 900);
|
||||
try testAppendInt(&buf, alloc, u16, 2); // endPtsOfContours[0]
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
try buf.append(alloc, 0x01); // on curve
|
||||
try buf.append(alloc, 0x01); // on curve
|
||||
try buf.append(alloc, 0x01); // on curve
|
||||
try testAppendInt(&buf, alloc, i16, 500);
|
||||
try testAppendInt(&buf, alloc, i16, -400);
|
||||
try testAppendInt(&buf, alloc, i16, 800);
|
||||
try testAppendInt(&buf, alloc, i16, 900);
|
||||
try testAppendInt(&buf, alloc, i16, -800);
|
||||
try testAppendInt(&buf, alloc, i16, 0);
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(@as(i16, 100), glyph.header.xMin);
|
||||
try testing.expectEqual(@as(i16, 900), glyph.header.xMax);
|
||||
try testing.expectEqual(@as(usize, 1), outline.contours.len);
|
||||
try testing.expectEqual(@as(usize, 3), outline.points.len);
|
||||
const contour = outline.contour(0);
|
||||
try testing.expectEqual(@as(usize, 3), contour.len);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 500, .y = 900, .on_curve = true }, contour[0]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 100, .y = 100, .on_curve = true }, contour[1]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 900, .y = 100, .on_curve = true }, contour[2]);
|
||||
}
|
||||
|
||||
test "glyf: decode multiple contours" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 2, 0, 0, 30, 10);
|
||||
try testAppendInt(&buf, alloc, u16, 1); // first contour ends at point 1
|
||||
try testAppendInt(&buf, alloc, u16, 3); // second contour ends at point 3
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
for (0..4) |_| try buf.append(alloc, 0x01); // on curve
|
||||
for ([_]i16{ 0, 10, 10, 10 }) |dx| try testAppendInt(&buf, alloc, i16, dx);
|
||||
for ([_]i16{ 0, 0, 10, 0 }) |dy| try testAppendInt(&buf, alloc, i16, dy);
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(@as(usize, 2), outline.contours.len);
|
||||
try testing.expectEqual(@as(usize, 4), outline.points.len);
|
||||
try testing.expectEqual(@as(u16, 1), outline.contours[0]);
|
||||
try testing.expectEqual(@as(u16, 3), outline.contours[1]);
|
||||
try testing.expectEqual(@as(usize, 2), outline.contour(0).len);
|
||||
try testing.expectEqual(@as(usize, 2), outline.contour(1).len);
|
||||
try testing.expectEqual(outline.points[0..2].ptr, outline.contour(0).ptr);
|
||||
try testing.expectEqual(outline.points[2..4].ptr, outline.contour(1).ptr);
|
||||
}
|
||||
|
||||
test "glyf: decode repeat and short vector flags" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 1, 0, -16, 16, 0);
|
||||
try testAppendInt(&buf, alloc, u16, 3); // four points
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
try buf.append(alloc, 0x01 | 0x02 | 0x04 | 0x08 | 0x10); // on, x short positive, y short negative, repeat
|
||||
try buf.append(alloc, 3); // repeat for the next three points
|
||||
for ([_]u8{ 1, 2, 4, 8 }) |dx| try buf.append(alloc, dx);
|
||||
for ([_]u8{ 1, 2, 4, 8 }) |dy| try buf.append(alloc, dy);
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(@as(usize, 4), outline.points.len);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 1, .y = -1, .on_curve = true }, outline.points[0]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 3, .y = -3, .on_curve = true }, outline.points[1]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 7, .y = -7, .on_curve = true }, outline.points[2]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 15, .y = -15, .on_curve = true }, outline.points[3]);
|
||||
}
|
||||
|
||||
test "glyf: decode off curve and same coordinate flags" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 1, 0, 0, 7, 9);
|
||||
try testAppendInt(&buf, alloc, u16, 1); // two points
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
try buf.append(alloc, 0x10 | 0x20); // off curve, x same, y same
|
||||
try buf.append(alloc, 0x01 | 0x02 | 0x04 | 0x10 | 0x20); // on curve, short positive x/y
|
||||
try buf.append(alloc, 7); // x delta
|
||||
try buf.append(alloc, 9); // y delta
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 0, .y = 0, .on_curve = false }, outline.points[0]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 7, .y = 9, .on_curve = true }, outline.points[1]);
|
||||
}
|
||||
|
||||
test "glyf: decode one-point contour" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 1, 0, 0, 0, 0);
|
||||
try testAppendInt(&buf, alloc, u16, 0); // endPtsOfContours[0]
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
try buf.append(alloc, 0x01 | 0x10 | 0x20); // on curve, x same, y same
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(@as(usize, 1), outline.points.len);
|
||||
try testing.expectEqual(@as(usize, 1), outline.contours.len);
|
||||
try testing.expectEqual(@as(u16, 0), outline.contours[0]);
|
||||
try testing.expectEqual(Glyf.Outline.Point{ .x = 0, .y = 0, .on_curve = true }, outline.contour(0)[0]);
|
||||
}
|
||||
|
||||
test "glyf: decode contour ending at max point index" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 1, 0, 0, 0, 0);
|
||||
try testAppendInt(&buf, alloc, u16, std.math.maxInt(u16)); // 65536 points
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
|
||||
const flag = 0x01 | 0x10 | 0x20; // on curve, x same, y same
|
||||
var remaining: usize = @as(usize, std.math.maxInt(u16)) + 1;
|
||||
while (remaining > 0) {
|
||||
const run = @min(remaining, 256);
|
||||
if (run == 1) {
|
||||
try buf.append(alloc, flag);
|
||||
} else {
|
||||
try buf.append(alloc, flag | 0x08); // repeat
|
||||
try buf.append(alloc, @intCast(run - 1));
|
||||
}
|
||||
remaining -= run;
|
||||
}
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
|
||||
try testing.expectEqual(@as(usize, 65536), outline.points.len);
|
||||
try testing.expectEqual(@as(usize, 65536), outline.contour(0).len);
|
||||
}
|
||||
|
||||
test "glyf: reject glyphs with instructions and composite glyphs" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
|
@ -496,6 +888,10 @@ test "glyf: reject glyphs with instructions and composite glyphs" {
|
|||
Glyf.Entry.SizeError.InstructionsNotSupported,
|
||||
glyph_notdef.size(),
|
||||
);
|
||||
try testing.expectError(
|
||||
Glyf.Entry.DecodeError.InstructionsNotSupported,
|
||||
glyph_notdef.decode(alloc),
|
||||
);
|
||||
|
||||
// Glyph "Á" is at index 2.
|
||||
const len_Aacute, const glyph_Aacute = try getGlyph(font, 2);
|
||||
|
|
@ -505,6 +901,10 @@ test "glyf: reject glyphs with instructions and composite glyphs" {
|
|||
Glyf.Entry.SizeError.CompositeNotSupported,
|
||||
glyph_Aacute.size(),
|
||||
);
|
||||
try testing.expectError(
|
||||
Glyf.Entry.DecodeError.CompositeNotSupported,
|
||||
glyph_Aacute.decode(alloc),
|
||||
);
|
||||
}
|
||||
|
||||
test "glyf: reject truncated" {
|
||||
|
|
@ -522,6 +922,7 @@ test "glyf: reject truncated" {
|
|||
// it before the full length (which is 228 bytes).
|
||||
glyph_nul.data = glyph_nul.data[0 .. 227 - @sizeOf(Glyf.Entry.Header)];
|
||||
try testing.expectError(Glyf.Entry.SizeError.EndOfStream, glyph_nul.size());
|
||||
try testing.expectError(Glyf.Entry.DecodeError.EndOfStream, glyph_nul.decode(alloc));
|
||||
}
|
||||
|
||||
test "glyf: reject endpoints out of order" {
|
||||
|
|
@ -544,6 +945,10 @@ test "glyf: reject endpoints out of order" {
|
|||
// copied, we can just const cast it back to mutable lol.
|
||||
std.mem.bytesAsSlice(u16, @as([]u8, @constCast(glyph_nul.data)))[3] = 0;
|
||||
try testing.expectError(Glyf.Entry.SizeError.EndPointsOutOfOrder, glyph_nul.size());
|
||||
try testing.expectError(
|
||||
Glyf.Entry.DecodeError.EndPointsOutOfOrder,
|
||||
glyph_nul.decode(alloc),
|
||||
);
|
||||
}
|
||||
|
||||
test "glyf: reject too many points" {
|
||||
|
|
@ -568,10 +973,12 @@ test "glyf: reject too many points" {
|
|||
@as([]u8, @constCast(glyph_nul.data))[107] |= 0x08;
|
||||
@as([]u8, @constCast(glyph_nul.data))[108] = 0xFF;
|
||||
try testing.expectError(Glyf.Entry.SizeError.TooManyPoints, glyph_nul.size());
|
||||
try testing.expectError(Glyf.Entry.DecodeError.TooManyPoints, glyph_nul.decode(alloc));
|
||||
}
|
||||
|
||||
test "glyf: zero-contour glyph can be header-only" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const header: Glyf.Entry.Header = .{
|
||||
.numberOfContours = 0,
|
||||
|
|
@ -582,4 +989,43 @@ test "glyf: zero-contour glyph can be header-only" {
|
|||
};
|
||||
const glyph = try Glyf.Entry.init(std.mem.asBytes(&header));
|
||||
try testing.expectEqual(@sizeOf(Glyf.Entry.Header), try glyph.size());
|
||||
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
try testing.expectEqual(@as(usize, 0), outline.points.len);
|
||||
try testing.expectEqual(@as(usize, 0), outline.contours.len);
|
||||
}
|
||||
|
||||
test "glyf: zero-contour glyph can include instruction length" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 0, 0, 0, 0, 0);
|
||||
try testAppendInt(&buf, alloc, u16, 0); // instructionLength
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
try testing.expectEqual(@sizeOf(Glyf.Entry.Header) + 2, try glyph.size());
|
||||
|
||||
var outline = try glyph.decode(alloc);
|
||||
defer outline.deinit(alloc);
|
||||
try testing.expectEqual(@as(usize, 0), outline.points.len);
|
||||
try testing.expectEqual(@as(usize, 0), outline.contours.len);
|
||||
}
|
||||
|
||||
test "glyf: zero-contour glyph rejects instructions" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try testAppendHeader(&buf, alloc, 0, 0, 0, 0, 0);
|
||||
try testAppendInt(&buf, alloc, u16, 1); // instructionLength
|
||||
|
||||
const glyph = try Glyf.Entry.init(buf.items);
|
||||
try testing.expectError(Glyf.Entry.SizeError.InstructionsNotSupported, glyph.size());
|
||||
try testing.expectError(Glyf.Entry.DecodeError.InstructionsNotSupported, glyph.decode(alloc));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue