diff --git a/src/font/Collection.zig b/src/font/Collection.zig index c06358cbf..5a66749d6 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -1371,6 +1371,9 @@ test "adjusted sizes" { } test "face metrics" { + // The web canvas backend doesn't calculate face metrics, only cell metrics + if (options.backend != .web_canvas) return error.SkipZigTest; + const testing = std.testing; const alloc = testing.allocator; const narrowFont = font.embedded.cozette; @@ -1403,80 +1406,108 @@ test "face metrics" { .size_adjustment = .none, }); - const narrowMetrics = (try c.getFace(narrowIndex)).getMetrics(); - const wideMetrics = (try c.getFace(wideIndex)).getMetrics(); + const narrowMetrics: font.Metrics.FaceMetrics = (try c.getFace(narrowIndex)).getMetrics(); + const wideMetrics: font.Metrics.FaceMetrics = (try c.getFace(wideIndex)).getMetrics(); // Verify provided/measured metrics. Measured // values are backend-dependent due to hinting. - if (options.backend != .web_canvas) { - try std.testing.expectEqual(font.Metrics.FaceMetrics{ - .px_per_em = 16.0, - .cell_width = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => 8.0, - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - => 7.3828125, - .web_canvas => unreachable, - }, - .ascent = 12.3046875, - .descent = -3.6953125, - .line_gap = 0.0, - .underline_position = -1.2265625, - .underline_thickness = 1.2265625, - .strikethrough_position = 6.15625, - .strikethrough_thickness = 1.234375, - .cap_height = 9.84375, - .ex_height = 7.3828125, - .ascii_height = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => 18.0625, - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - => 16.0, - .web_canvas => unreachable, - }, - }, narrowMetrics); - try std.testing.expectEqual(font.Metrics.FaceMetrics{ - .px_per_em = 16.0, - .cell_width = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => 10.0, - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - => 9.6, - .web_canvas => unreachable, - }, - .ascent = 14.72, - .descent = -3.52, - .line_gap = 1.6, - .underline_position = -1.6, - .underline_thickness = 0.8, - .strikethrough_position = 4.24, - .strikethrough_thickness = 0.8, - .cap_height = 11.36, - .ex_height = 8.48, - .ascii_height = switch (options.backend) { - .freetype, - .fontconfig_freetype, - .coretext_freetype, - => 16.0, - .coretext, - .coretext_harfbuzz, - .coretext_noshape, - => 15.472000000000001, - .web_canvas => unreachable, - }, - }, wideMetrics); + const narrowMetricsExpected = font.Metrics.FaceMetrics{ + .px_per_em = 16.0, + .cell_width = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => 8.0, + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + => 7.3828125, + .web_canvas => unreachable, + }, + .ascent = 12.3046875, + .descent = -3.6953125, + .line_gap = 0.0, + .underline_position = -1.2265625, + .underline_thickness = 1.2265625, + .strikethrough_position = 6.15625, + .strikethrough_thickness = 1.234375, + .cap_height = 9.84375, + .ex_height = 7.3828125, + .ascii_height = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => 18.0625, + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + => 16.0, + .web_canvas => unreachable, + }, + }; + const wideMetricsExpected = font.Metrics.FaceMetrics{ + .px_per_em = 16.0, + .cell_width = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => 10.0, + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + => 9.6, + .web_canvas => unreachable, + }, + .ascent = 14.72, + .descent = -3.52, + .line_gap = 1.6, + .underline_position = -1.6, + .underline_thickness = 0.8, + .strikethrough_position = 4.24, + .strikethrough_thickness = 0.8, + .cap_height = 11.36, + .ex_height = 8.48, + .ascii_height = switch (options.backend) { + .freetype, + .fontconfig_freetype, + .coretext_freetype, + => 16.0, + .coretext, + .coretext_harfbuzz, + .coretext_noshape, + => 15.472000000000001, + .web_canvas => unreachable, + }, + }; + + inline for ( + .{ narrowMetricsExpected, wideMetricsExpected }, + .{ narrowMetrics, wideMetrics }, + ) |metricsExpected, metricsActual| { + inline for (@typeInfo(font.Metrics.FaceMetrics).@"struct".fields) |field| { + const expected = @field(metricsExpected, field.name); + const actual = @field(metricsActual, field.name); + // Unwrap optional fields + const expectedValue, const actualValue = unwrap: switch (@typeInfo(field.type)) { + .optional => |Tinfo| { + if (expected) |expectedValue| { + const actualValue = actual orelse std.math.nan(Tinfo.child); + break :unwrap .{ expectedValue, actualValue }; + } + // Null values can be compared directly + try std.testing.expectEqual(expected, actual); + continue; + }, + else => break :unwrap .{ expected, actual }, + }; + // All non-null values are floats + const eps = std.math.floatEps(@TypeOf(actualValue - expectedValue)); + try std.testing.expectApproxEqRel( + expectedValue, + actualValue, + std.math.sqrt(eps), + ); + } } // Verify estimated metrics. icWidth() should equal the smaller of