face: add more RLS types and explicit error sets

This commit is contained in:
Mitchell Hashimoto
2024-12-12 19:42:35 -08:00
parent 586a7e517e
commit b7dc767237
6 changed files with 71 additions and 30 deletions

View File

@ -530,7 +530,15 @@ pub const Face = struct {
};
}
fn calcMetrics(ct_font: *macos.text.Font) !font.face.Metrics {
const CalcMetricsError = error{
CopyTableError,
InvalidHeadTable,
InvalidPostTable,
InvalidOS2Table,
OS2VersionNotSupported,
};
fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.face.Metrics {
// Read the 'head' table out of the font data.
const head: opentype.Head = head: {
const tag = macos.text.FontTableTag.init("head");
@ -538,7 +546,12 @@ pub const Face = struct {
defer data.release();
const ptr = data.getPointer();
const len = data.getLength();
break :head try opentype.Head.init(ptr[0..len]);
break :head opentype.Head.init(ptr[0..len]) catch |err| {
return switch (err) {
error.EndOfStream,
=> error.InvalidHeadTable,
};
};
};
// Read the 'post' table out of the font data.
@ -548,7 +561,11 @@ pub const Face = struct {
defer data.release();
const ptr = data.getPointer();
const len = data.getLength();
break :post try opentype.Post.init(ptr[0..len]);
break :post opentype.Post.init(ptr[0..len]) catch |err| {
return switch (err) {
error.EndOfStream => error.InvalidOS2Table,
};
};
};
// Read the 'OS/2' table out of the font data.
@ -558,12 +575,17 @@ pub const Face = struct {
defer data.release();
const ptr = data.getPointer();
const len = data.getLength();
break :os2 try opentype.OS2.init(ptr[0..len]);
break :os2 opentype.OS2.init(ptr[0..len]) catch |err| {
return switch (err) {
error.EndOfStream => error.InvalidOS2Table,
error.OS2VersionNotSupported => error.OS2VersionNotSupported,
};
};
};
const units_per_em = head.unitsPerEm;
const px_per_em = ct_font.getSize();
const px_per_unit = px_per_em / @as(f64, @floatFromInt(units_per_em));
const units_per_em: f64 = @floatFromInt(head.unitsPerEm);
const px_per_em: f64 = ct_font.getSize();
const px_per_unit: f64 = px_per_em / units_per_em;
const ascent = @as(f64, @floatFromInt(os2.sTypoAscender)) * px_per_unit;
const descent = @as(f64, @floatFromInt(os2.sTypoDescender)) * px_per_unit;
@ -576,7 +598,7 @@ pub const Face = struct {
// If the underline position isn't 0 then we do use it,
// even if the thickness is't properly specified.
const underline_position = if (has_broken_underline and post.underlinePosition == 0)
const underline_position: ?f64 = if (has_broken_underline and post.underlinePosition == 0)
null
else
@as(f64, @floatFromInt(post.underlinePosition)) * px_per_unit;
@ -589,25 +611,25 @@ pub const Face = struct {
// Similar logic to the underline above.
const has_broken_strikethrough = os2.yStrikeoutSize == 0;
const strikethrough_position = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
const strikethrough_position: ?f64 = if (has_broken_strikethrough and os2.yStrikeoutPosition == 0)
null
else
@as(f64, @floatFromInt(os2.yStrikeoutPosition)) * px_per_unit;
const strikethrough_thickness = if (has_broken_strikethrough)
const strikethrough_thickness: ?f64 = if (has_broken_strikethrough)
null
else
@as(f64, @floatFromInt(os2.yStrikeoutSize)) * px_per_unit;
// We fall back to whatever CoreText does if
// the OS/2 table doesn't specify a cap height.
const cap_height = if (os2.sCapHeight) |sCapHeight|
const cap_height: f64 = if (os2.sCapHeight) |sCapHeight|
@as(f64, @floatFromInt(sCapHeight)) * px_per_unit
else
ct_font.getCapHeight();
// Ditto for ex height.
const ex_height = if (os2.sxHeight) |sxHeight|
const ex_height: f64 = if (os2.sxHeight) |sxHeight|
@as(f64, @floatFromInt(sxHeight)) * px_per_unit
else
ct_font.getXHeight();
@ -648,24 +670,24 @@ pub const Face = struct {
return font.face.Metrics.calc(.{
.cell_width = cell_width,
.ascent = ascent,
.descent = descent,
.line_gap = line_gap,
.underline_position = underline_position,
.underline_thickness = underline_thickness,
.strikethrough_position = strikethrough_position,
.strikethrough_thickness = strikethrough_thickness,
.cap_height = cap_height,
.ex_height = ex_height,
});
}
/// Copy the font table data for the given tag.
pub fn copyTable(self: Face, alloc: Allocator, tag: *const [4]u8) !?[]u8 {
pub fn copyTable(
self: Face,
alloc: Allocator,
tag: *const [4]u8,
) Allocator.Error!?[]u8 {
const data = self.font.copyTable(macos.text.FontTableTag.init(tag)) orelse
return null;
defer data.release();
@ -693,7 +715,9 @@ const ColorState = struct {
svg: ?opentype.SVG,
svg_data: ?*macos.foundation.Data,
pub fn init(f: *macos.text.Font) !ColorState {
pub const Error = error{InvalidSVGTable};
pub fn init(f: *macos.text.Font) Error!ColorState {
// sbix is true if the table exists in the font data at all.
// In the future we probably want to actually parse it and
// check for glyphs.
@ -714,8 +738,16 @@ const ColorState = struct {
errdefer data.release();
const ptr = data.getPointer();
const len = data.getLength();
const svg = opentype.SVG.init(ptr[0..len]) catch |err| {
return switch (err) {
error.EndOfStream,
error.SVGVersionNotSupported,
=> error.InvalidSVGTable,
};
};
break :svg .{
.svg = try opentype.SVG.init(ptr[0..len]),
.svg = svg,
.data = data,
};
};

View File

@ -598,6 +598,11 @@ pub const Face = struct {
return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64);
}
const CalcMetricsError = error{
CopyTableError,
MissingOS2Table,
};
/// Calculate the metrics associated with a face. This is not public because
/// the metrics are calculated for every face and cached since they're
/// frequently required for renderers and take up next to little memory space
@ -610,7 +615,7 @@ pub const Face = struct {
fn calcMetrics(
face: freetype.Face,
modifiers: ?*const font.face.Metrics.ModifierSet,
) !font.face.Metrics {
) CalcMetricsError!font.face.Metrics {
const size_metrics = face.handle.*.size.*.metrics;
// This code relies on this assumption, and it should always be
@ -618,18 +623,18 @@ pub const Face = struct {
assert(size_metrics.x_ppem == size_metrics.y_ppem);
// Read the 'head' table out of the font data.
const head = face.getSfntTable(.head) orelse return error.CannotGetTable;
const head = face.getSfntTable(.head) orelse return error.CopyTableError;
// Read the 'post' table out of the font data.
const post = face.getSfntTable(.post) orelse return error.CannotGetTable;
const post = face.getSfntTable(.post) orelse return error.CopyTableError;
// Read the 'OS/2' table out of the font data.
const os2 = face.getSfntTable(.os2) orelse return error.CannotGetTable;
const os2 = face.getSfntTable(.os2) orelse return error.CopyTableError;
// Some fonts don't actually have an OS/2 table, which
// we need in order to do the metrics calculations, in
// such cases FreeType sets the version to 0xFFFF
if (os2.version == 0xFFFF) return error.MissingTable;
if (os2.version == 0xFFFF) return error.MissingOS2Table;
const units_per_em = head.Units_Per_EM;
const px_per_em: f64 = @floatFromInt(size_metrics.y_ppem);

View File

@ -135,10 +135,9 @@ pub const Head = extern struct {
glyphDataFormat: sfnt.int16 align(1),
/// Parse the table from raw data.
pub fn init(data: []const u8) !Head {
pub fn init(data: []const u8) error{EndOfStream}!Head {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();
return try reader.readStructEndian(Head, .big);
}
};

View File

@ -349,7 +349,10 @@ pub const OS2 = struct {
usUpperOpticalPointSize: ?u16 = null,
/// Parse the table from raw data.
pub fn init(data: []const u8) !OS2 {
pub fn init(data: []const u8) error{
EndOfStream,
OS2VersionNotSupported,
}!OS2 {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();

View File

@ -47,10 +47,9 @@ pub const Post = extern struct {
maxMemType1: sfnt.uint32 align(1),
/// Parse the table from raw data.
pub fn init(data: []const u8) !Post {
pub fn init(data: []const u8) error{EndOfStream}!Post {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();
return try reader.readStructEndian(Post, .big);
}
};

View File

@ -22,7 +22,10 @@ pub const SVG = struct {
/// All records in the table.
records: []const [12]u8,
pub fn init(data: []const u8) !SVG {
pub fn init(data: []const u8) error{
EndOfStream,
SVGVersionNotSupported,
}!SVG {
var fbs = std.io.fixedBufferStream(data);
const reader = fbs.reader();