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); +} diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index 881c32895..c78ac0972 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -52,7 +52,12 @@ const Minimums = struct { const cursor_height = 1; }; -const CalcOpts = struct { +/// Metrics extracted from a font face, based on +/// the metadata tables and glyph measurements. +pub const FaceMetrics = struct { + /// The minimum cell width that can contain any glyph in the ASCII range. + /// + /// Determined by measuring all printable glyphs in the ASCII range. cell_width: f64, /// The typographic ascent metric from the font. @@ -110,45 +115,45 @@ const CalcOpts = struct { /// do not round them before using them for this function. /// /// For any nullable options that are not provided, estimates will be used. -pub fn calc(opts: CalcOpts) Metrics { +pub fn calc(face: FaceMetrics) Metrics { // We use the ceiling of the provided cell width and height to ensure // that the cell is large enough for the provided size, since we cast // it to an integer later. - const cell_width = @ceil(opts.cell_width); - const cell_height = @ceil(opts.ascent - opts.descent + opts.line_gap); + const cell_width = @ceil(face.cell_width); + const cell_height = @ceil(face.ascent - face.descent + face.line_gap); // We split our line gap in two parts, and put half of it on the top // of the cell and the other half on the bottom, so that our text never // bumps up against either edge of the cell vertically. - const half_line_gap = opts.line_gap / 2; + const half_line_gap = face.line_gap / 2; // Unlike all our other metrics, `cell_baseline` is relative to the // BOTTOM of the cell. - const cell_baseline = @round(half_line_gap - opts.descent); + const cell_baseline = @round(half_line_gap - face.descent); // We calculate a top_to_baseline to make following calculations simpler. const top_to_baseline = cell_height - cell_baseline; // If we don't have a provided cap height, // we estimate it as 75% of the ascent. - const cap_height = opts.cap_height orelse opts.ascent * 0.75; + const cap_height = face.cap_height orelse face.ascent * 0.75; // If we don't have a provided ex height, // we estimate it as 75% of the cap height. - const ex_height = opts.ex_height orelse cap_height * 0.75; + const ex_height = face.ex_height orelse cap_height * 0.75; // If we don't have a provided underline thickness, // we estimate it as 15% of the ex height. - const underline_thickness = @max(1, @ceil(opts.underline_thickness orelse 0.15 * ex_height)); + const underline_thickness = @max(1, @ceil(face.underline_thickness orelse 0.15 * ex_height)); // If we don't have a provided strikethrough thickness // then we just use the underline thickness for it. - const strikethrough_thickness = @max(1, @ceil(opts.strikethrough_thickness orelse underline_thickness)); + const strikethrough_thickness = @max(1, @ceil(face.strikethrough_thickness orelse underline_thickness)); // If we don't have a provided underline position then // we place it 1 underline-thickness below the baseline. const underline_position = @round(top_to_baseline - - (opts.underline_position orelse + (face.underline_position orelse -underline_thickness)); // If we don't have a provided strikethrough position @@ -156,7 +161,7 @@ pub fn calc(opts: CalcOpts) Metrics { // ex height, so that it's perfectly centered on lower // case text. const strikethrough_position = @round(top_to_baseline - - (opts.strikethrough_position orelse + (face.strikethrough_position orelse ex_height * 0.5 + strikethrough_thickness * 0.5)); var result: Metrics = .{ diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 25069cde2..65c7ecd87 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -111,15 +111,10 @@ pub fn deinit(self: *SharedGrid, alloc: Allocator) void { } fn reloadMetrics(self: *SharedGrid) !void { - // Get our cell metrics based on a regular font ascii 'M'. Why 'M'? - // Doesn't matter, any normal ASCII will do we're just trying to make - // sure we use the regular font. - // We don't go through our caching layer because we want to minimize - // possible failures. const collection = &self.resolver.collection; - const index = collection.getIndex('M', .regular, .{ .any = {} }).?; - const face = try collection.getFace(index); - self.metrics = face.metrics; + try collection.updateMetrics(); + + self.metrics = collection.metrics.?; // Setup our sprite font. self.resolver.sprite = .{ .metrics = self.metrics }; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 16572e3f1..249a11f75 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -167,13 +167,13 @@ fn collection( const load_options: Collection.LoadOptions = .{ .library = self.font_lib, .size = size, - .metric_modifiers = key.metric_modifiers, .freetype_load_flags = key.freetype_load_flags, }; var c = Collection.init(); errdefer c.deinit(self.alloc); c.load_options = load_options; + c.metric_modifiers = key.metric_modifiers; // Search for fonts if (Discover != void) discover: { diff --git a/src/font/face.zig b/src/font/face.zig index dab029f5c..0102010de 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -38,7 +38,6 @@ pub const freetype_load_flags_default = if (FreetypeLoadFlags != void) .{} else /// Options for initializing a font face. pub const Options = struct { size: DesiredSize, - metric_modifiers: ?*const Metrics.ModifierSet = null, freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default, }; @@ -89,7 +88,7 @@ pub const RenderOptions = struct { /// the metrics of the primary font face. The grid metrics are used /// by the font face to better layout the glyph in situations where /// the font is not exactly the same size as the grid. - grid_metrics: ?Metrics = null, + grid_metrics: Metrics, /// The number of grid cells this glyph will take up. This can be used /// optionally by the rasterizer to better layout the glyph. diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 32077b8bb..6661295f3 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -18,9 +18,6 @@ pub const Face = struct { /// if we're using Harfbuzz. hb_font: if (harfbuzz_shaper) harfbuzz.Font else void, - /// Metrics for this font face. These are useful for renderers. - metrics: font.Metrics, - /// Set quirks.disableDefaultFontFeatures quirks_disable_default_font_features: bool = false, @@ -87,11 +84,6 @@ pub const Face = struct { /// the CTFont. This does NOT copy or retain the CTFont. pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face { const traits = ct_font.getSymbolicTraits(); - const metrics = metrics: { - var metrics = try calcMetrics(ct_font); - if (opts.metric_modifiers) |v| metrics.apply(v.*); - break :metrics metrics; - }; var hb_font = if (comptime harfbuzz_shaper) font: { var hb_font = try harfbuzz.coretext.createFont(ct_font); @@ -109,7 +101,6 @@ pub const Face = struct { var result: Face = .{ .font = ct_font, .hb_font = hb_font, - .metrics = metrics, .color = color, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -463,7 +454,7 @@ pub const Face = struct { }; atlas.set(region, buf); - const metrics = opts.grid_metrics orelse self.metrics; + const metrics = opts.grid_metrics; // This should be the distance from the bottom of // the cell to the top of the glyph's bounding box. @@ -506,14 +497,17 @@ pub const Face = struct { }; } - const CalcMetricsError = error{ + pub const GetMetricsError = error{ CopyTableError, InvalidHeadTable, InvalidPostTable, InvalidHheaTable, }; - pub fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.Metrics { + /// Get the `FaceMetrics` for this face. + pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics { + const ct_font = self.font; + // Read the 'head' table out of the font data. const head: opentype.Head = head: { // macOS bitmap-only fonts use a 'bhed' tag rather than 'head', but @@ -731,7 +725,7 @@ pub const Face = struct { break :cell_width max; }; - return font.Metrics.calc(.{ + return .{ .cell_width = cell_width, .ascent = ascent, .descent = descent, @@ -742,7 +736,7 @@ pub const Face = struct { .strikethrough_thickness = strikethrough_thickness, .cap_height = cap_height, .ex_height = ex_height, - }); + }; } /// Copy the font table data for the given tag. @@ -866,7 +860,12 @@ test { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph( + alloc, + &atlas, + face.glyphIndex(i).?, + .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) }, + ); } } @@ -926,7 +925,12 @@ test "in-memory" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph( + alloc, + &atlas, + face.glyphIndex(i).?, + .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) }, + ); } } @@ -948,7 +952,12 @@ test "variable" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph( + alloc, + &atlas, + face.glyphIndex(i).?, + .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) }, + ); } } @@ -974,7 +983,12 @@ test "variable set variation" { var i: u8 = 32; while (i < 127) : (i += 1) { try testing.expect(face.glyphIndex(i) != null); - _ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{}); + _ = try face.renderGlyph( + alloc, + &atlas, + face.glyphIndex(i).?, + .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) }, + ); } } @@ -1017,60 +1031,3 @@ test "glyphIndex colored vs text" { try testing.expect(face.isColorGlyph(glyph)); } } - -test "coretext: metrics" { - const testFont = font.embedded.inconsolata; - const alloc = std.testing.allocator; - - var atlas = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas.deinit(alloc); - - var ct_font = try Face.init( - undefined, - testFont, - .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, - ); - defer ct_font.deinit(); - - 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, - }, ct_font.metrics); - - // Resize should change metrics - try ct_font.setSize(.{ .size = .{ .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, - }, ct_font.metrics); -} diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index d919fd7b3..b56e94695 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -38,9 +38,6 @@ pub const Face = struct { /// Harfbuzz font corresponding to this face. hb_font: harfbuzz.Font, - /// Metrics for this font face. These are useful for renderers. - metrics: font.Metrics, - /// Freetype load flags for this font face. load_flags: font.face.FreetypeLoadFlags, @@ -86,7 +83,6 @@ pub const Face = struct { .lib = lib.lib, .face = face, .hb_font = hb_font, - .metrics = try calcMetrics(face, opts.metric_modifiers), .load_flags = opts.freetype_load_flags, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -186,7 +182,6 @@ pub const Face = struct { /// for clearing any glyph caches, font atlas data, etc. pub fn setSize(self: *Face, opts: font.face.Options) !void { try setSize_(self.face, opts.size); - self.metrics = try calcMetrics(self.face, opts.metric_modifiers); } fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void { @@ -224,6 +219,8 @@ pub const Face = struct { vs: []const font.face.Variation, opts: font.face.Options, ) !void { + _ = opts; + // If this font doesn't support variations, we can't do anything. if (!self.face.hasMultipleMasters() or vs.len == 0) return; @@ -257,9 +254,6 @@ pub const Face = struct { // Set them! try self.face.setVarDesignCoordinates(coords); - - // We need to recalculate font metrics which may have changed. - self.metrics = try calcMetrics(self.face, opts.metric_modifiers); } /// Returns the glyph index for the given Unicode code point. If this @@ -306,7 +300,7 @@ pub const Face = struct { glyph_index: u32, opts: font.face.RenderOptions, ) !Glyph { - const metrics = opts.grid_metrics orelse self.metrics; + const metrics = opts.grid_metrics; // If we have synthetic italic, then we apply a transformation matrix. // We have to undo this because synthetic italic works by increasing @@ -589,23 +583,14 @@ pub const Face = struct { return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64); } - const CalcMetricsError = error{ + pub const GetMetricsError = error{ CopyTableError, }; - /// 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 - /// in the grand scheme of things. - /// - /// An aside: the proper way to limit memory usage due to faces is to limit - /// the faces with DeferredFaces and reload on demand. A Face can't be converted - /// into a DeferredFace but a Face that comes from a DeferredFace can be - /// deinitialized anytime and reloaded with the deferred face. - fn calcMetrics( - face: freetype.Face, - modifiers: ?*const font.Metrics.ModifierSet, - ) CalcMetricsError!font.Metrics { + /// Get the `FaceMetrics` for this face. + pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics { + const face = self.face; + const size_metrics = face.handle.*.size.*.metrics; // This code relies on this assumption, and it should always be @@ -793,7 +778,7 @@ pub const Face = struct { }; }; - var result = font.Metrics.calc(.{ + return .{ .cell_width = cell_width, .ascent = ascent, @@ -808,13 +793,7 @@ pub const Face = struct { .cap_height = cap_height, .ex_height = ex_height, - }); - - if (modifiers) |m| result.apply(m.*); - - // std.log.warn("font metrics={}", .{result}); - - return result; + }; } /// Copy the font table data for the given tag. @@ -843,16 +822,31 @@ test { // Generate all visible ASCII var i: u8 = 32; while (i < 127) : (i += 1) { - _ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, .{}); + _ = try ft_font.renderGlyph( + alloc, + &atlas, + ft_font.glyphIndex(i).?, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); } // Test resizing { - const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{}); + const g1 = try ft_font.renderGlyph( + alloc, + &atlas, + ft_font.glyphIndex('A').?, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); try testing.expectEqual(@as(u32, 11), g1.height); try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } }); - const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{}); + const g2 = try ft_font.renderGlyph( + alloc, + &atlas, + ft_font.glyphIndex('A').?, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); try testing.expectEqual(@as(u32, 20), g2.height); } } @@ -874,7 +868,12 @@ test "color emoji" { ); defer ft_font.deinit(); - _ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{}); + _ = try ft_font.renderGlyph( + alloc, + &atlas, + ft_font.glyphIndex('🥸').?, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); // Make sure this glyph has color { @@ -885,8 +884,11 @@ test "color emoji" { // resize { - const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{ - .grid_metrics = .{ + const glyph = try ft_font.renderGlyph( + alloc, + &atlas, + ft_font.glyphIndex('🥸').?, + .{ .grid_metrics = .{ .cell_width = 10, .cell_height = 24, .cell_baseline = 0, @@ -898,72 +900,12 @@ test "color emoji" { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, - }, - }); + } }, + ); try testing.expectEqual(@as(u32, 24), glyph.height); } } -test "metrics" { - const testFont = font.embedded.inconsolata; - const alloc = testing.allocator; - - var lib = try Library.init(); - defer lib.deinit(); - - var atlas = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas.deinit(alloc); - - var ft_font = try Face.init( - lib, - testFont, - .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, - ); - defer ft_font.deinit(); - - try 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, - }, ft_font.metrics); - - // Resize should change metrics - try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } }); - try 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, - }, ft_font.metrics); -} - test "mono to rgba" { const alloc = testing.allocator; const testFont = font.embedded.emoji; @@ -974,11 +916,16 @@ test "mono to rgba" { var atlas = try font.Atlas.init(alloc, 512, .rgba); defer atlas.deinit(alloc); - var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); + var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } }); defer ft_font.deinit(); // glyph 3 is mono in Noto - _ = try ft_font.renderGlyph(alloc, &atlas, 3, .{}); + _ = try ft_font.renderGlyph( + alloc, + &atlas, + 3, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); } test "svg font table" { @@ -988,7 +935,7 @@ test "svg font table" { var lib = try font.Library.init(); defer lib.deinit(); - var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); + var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } }); defer face.deinit(); const table = (try face.copyTable(alloc, "SVG ")).?; @@ -1037,7 +984,12 @@ test "bitmap glyph" { defer ft_font.deinit(); // glyph 77 = 'i' - const glyph = try ft_font.renderGlyph(alloc, &atlas, 77, .{}); + const glyph = try ft_font.renderGlyph( + alloc, + &atlas, + 77, + .{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) }, + ); // should render crisp try testing.expectEqual(8, glyph.width); diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index 7c42fb394..cebf44429 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -52,7 +52,7 @@ pub fn renderGlyph( } } - const metrics = opts.grid_metrics orelse self.metrics; + const metrics = self.metrics; // We adjust our sprite width based on the cell width. const width = switch (opts.cell_width orelse 1) {