summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQwerasd <qwerasd205@users.noreply.github.com>2025-01-06 19:00:13 -0500
committerQwerasd <qwerasd205@users.noreply.github.com>2025-01-06 20:13:45 -0500
commit298aeb7536d69b8aef236569ee86ecfddd45d991 (patch)
tree65396dae0e56590a964a6da4baa1d13148397d19
parent540fcc0b690901f185ca00465dafed2e9423b479 (diff)
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.
-rw-r--r--src/font/Collection.zig101
-rw-r--r--src/font/Metrics.zig29
-rw-r--r--src/font/SharedGrid.zig11
-rw-r--r--src/font/SharedGridSet.zig2
-rw-r--r--src/font/face.zig3
-rw-r--r--src/font/face/coretext.zig107
-rw-r--r--src/font/face/freetype.zig158
-rw-r--r--src/font/sprite/Face.zig2
8 files changed, 206 insertions, 207 deletions
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) {