From 298aeb7536d69b8aef236569ee86ecfddd45d991 Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Mon, 6 Jan 2025 19:00:13 -0500 Subject: refactor(font): move ownership of `Metrics` to `Collection` This sets the stage for dynamically adjusting the sizes of fallback fonts based on the primary font's face metrics. It also removes a lot of unnecessary work when loading fallback fonts, since we only actually use the metrics based on the parimary font. --- src/font/Collection.zig | 101 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 5 deletions(-) (limited to 'src/font/Collection.zig') diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 629f4e595..cb16528aa 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -35,6 +35,17 @@ const log = std.log.scoped(.font_collection); /// Instead, use the functions available on Collection. faces: StyleArray, +/// The metric modifiers to use for this collection. The memory +/// for this is owned by the user and is not freed by the collection. +/// +/// Call `Collection.updateMetrics` to recompute the +/// collection's metrics after making changes to these. +metric_modifiers: Metrics.ModifierSet = .{}, + +/// Metrics for this collection. Call `Collection.updateMetrics` to (re)compute +/// these after adding a primary font or making changes to `metric_modifiers`. +metrics: ?Metrics = null, + /// The load options for deferred faces in the face list. If this /// is not set, then deferred faces will not be loaded. Attempting to /// add a deferred face will result in an error. @@ -421,6 +432,28 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void { .alias => continue, }; } + + try self.updateMetrics(); +} + +const UpdateMetricsError = font.Face.GetMetricsError || error{ + CannotLoadPrimaryFont, +}; + +/// Update the cell metrics for this collection, based on +/// the primary font and the modifiers in `metric_modifiers`. +/// +/// This requires a primary font (index `0`) to be present. +pub fn updateMetrics(self: *Collection) UpdateMetricsError!void { + const primary_face = self.getFace(.{ .idx = 0 }) catch return error.CannotLoadPrimaryFont; + + const face_metrics = try primary_face.getMetrics(); + + var metrics = Metrics.calc(face_metrics); + + metrics.apply(self.metric_modifiers); + + self.metrics = metrics; } /// Packed array of all Style enum cases mapped to a growable list of faces. @@ -448,10 +481,6 @@ pub const LoadOptions = struct { /// The desired font size for all loaded faces. size: DesiredSize = .{ .points = 12 }, - /// The metric modifiers to use for all loaded faces. The memory - /// for this is owned by the user and is not freed by the collection. - metric_modifiers: Metrics.ModifierSet = .{}, - /// Freetype Load Flags to use when loading glyphs. This is a list of /// bitfield constants that controls operations to perform during glyph /// loading. Only a subset is exposed for configuration, for the whole set @@ -467,7 +496,6 @@ pub const LoadOptions = struct { pub fn faceOptions(self: *const LoadOptions) font.face.Options { return .{ .size = self.size, - .metric_modifiers = &self.metric_modifiers, .freetype_load_flags = self.freetype_load_flags, }; } @@ -864,3 +892,66 @@ test "hasCodepoint emoji default graphical" { try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} })); // TODO(fontmem): test explicit/implicit } + +test "metrics" { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = font.embedded.inconsolata; + + var lib = try Library.init(); + defer lib.deinit(); + + var c = init(); + defer c.deinit(alloc); + c.load_options = .{ .library = lib }; + + _ = try c.add(alloc, .regular, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + + try c.updateMetrics(); + + try std.testing.expectEqual(font.Metrics{ + .cell_width = 8, + // The cell height is 17 px because the calculation is + // + // ascender - descender + gap + // + // which, for inconsolata is + // + // 859 - -190 + 0 + // + // font units, at 1000 units per em that works out to 1.049 em, + // and 1em should be the point size * dpi scale, so 12 * (96/72) + // which is 16, and 16 * 1.049 = 16.784, which finally is rounded + // to 17. + .cell_height = 17, + .cell_baseline = 3, + .underline_position = 17, + .underline_thickness = 1, + .strikethrough_position = 10, + .strikethrough_thickness = 1, + .overline_position = 0, + .overline_thickness = 1, + .box_thickness = 1, + .cursor_height = 17, + }, c.metrics); + + // Resize should change metrics + try c.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 }); + try std.testing.expectEqual(font.Metrics{ + .cell_width = 16, + .cell_height = 34, + .cell_baseline = 6, + .underline_position = 34, + .underline_thickness = 2, + .strikethrough_position = 19, + .strikethrough_thickness = 2, + .overline_position = 0, + .overline_thickness = 2, + .box_thickness = 2, + .cursor_height = 34, + }, c.metrics); +} -- cgit v1.2.3