From b7dc7672376bc2ade8ab9235ba895a826ea40c12 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 12 Dec 2024 19:42:35 -0800 Subject: [PATCH] face: add more RLS types and explicit error sets --- src/font/face/coretext.zig | 70 +++++++++++++++++++++++++++----------- src/font/face/freetype.zig | 15 +++++--- src/font/opentype/head.zig | 3 +- src/font/opentype/os2.zig | 5 ++- src/font/opentype/post.zig | 3 +- src/font/opentype/svg.zig | 5 ++- 6 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 263a5f915..8749f9092 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -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, }; }; diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index 2849e52de..c3d4a449b 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -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); diff --git a/src/font/opentype/head.zig b/src/font/opentype/head.zig index e5be7a352..b4ee3ffd4 100644 --- a/src/font/opentype/head.zig +++ b/src/font/opentype/head.zig @@ -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); } }; diff --git a/src/font/opentype/os2.zig b/src/font/opentype/os2.zig index 809962d45..a18538d5f 100644 --- a/src/font/opentype/os2.zig +++ b/src/font/opentype/os2.zig @@ -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(); diff --git a/src/font/opentype/post.zig b/src/font/opentype/post.zig index ca0891583..ff56a5013 100644 --- a/src/font/opentype/post.zig +++ b/src/font/opentype/post.zig @@ -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); } }; diff --git a/src/font/opentype/svg.zig b/src/font/opentype/svg.zig index ff431dee2..15edff5aa 100644 --- a/src/font/opentype/svg.zig +++ b/src/font/opentype/svg.zig @@ -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();