@@ -1378,3 +1378,155 @@ test "adjusted sizes" {
1378
1378
);
1379
1379
}
1380
1380
}
1381
+
1382
+ test "face metrics" {
1383
+ // The web canvas backend doesn't calculate face metrics, only cell metrics
1384
+ if (options .backend != .web_canvas ) return error .SkipZigTest ;
1385
+
1386
+ const testing = std .testing ;
1387
+ const alloc = testing .allocator ;
1388
+ const narrowFont = font .embedded .cozette ;
1389
+ const wideFont = font .embedded .geist_mono ;
1390
+
1391
+ var lib = try Library .init (alloc );
1392
+ defer lib .deinit ();
1393
+
1394
+ var c = init ();
1395
+ defer c .deinit (alloc );
1396
+ const size : DesiredSize = .{ .points = 12 , .xdpi = 96 , .ydpi = 96 };
1397
+ c .load_options = .{ .library = lib , .size = size };
1398
+
1399
+ const narrowIndex = try c .add (alloc , try .init (
1400
+ lib ,
1401
+ narrowFont ,
1402
+ .{ .size = size },
1403
+ ), .{
1404
+ .style = .regular ,
1405
+ .fallback = false ,
1406
+ .size_adjustment = .none ,
1407
+ });
1408
+ const wideIndex = try c .add (alloc , try .init (
1409
+ lib ,
1410
+ wideFont ,
1411
+ .{ .size = size },
1412
+ ), .{
1413
+ .style = .regular ,
1414
+ .fallback = false ,
1415
+ .size_adjustment = .none ,
1416
+ });
1417
+
1418
+ const narrowMetrics : font.Metrics.FaceMetrics = (try c .getFace (narrowIndex )).getMetrics ();
1419
+ const wideMetrics : font.Metrics.FaceMetrics = (try c .getFace (wideIndex )).getMetrics ();
1420
+
1421
+ // Verify provided/measured metrics. Measured
1422
+ // values are backend-dependent due to hinting.
1423
+ const narrowMetricsExpected = font.Metrics.FaceMetrics {
1424
+ .px_per_em = 16.0 ,
1425
+ .cell_width = switch (options .backend ) {
1426
+ .freetype ,
1427
+ .fontconfig_freetype ,
1428
+ .coretext_freetype ,
1429
+ = > 8.0 ,
1430
+ .coretext ,
1431
+ .coretext_harfbuzz ,
1432
+ .coretext_noshape ,
1433
+ = > 7.3828125 ,
1434
+ .web_canvas = > unreachable ,
1435
+ },
1436
+ .ascent = 12.3046875 ,
1437
+ .descent = -3.6953125 ,
1438
+ .line_gap = 0.0 ,
1439
+ .underline_position = -1.2265625 ,
1440
+ .underline_thickness = 1.2265625 ,
1441
+ .strikethrough_position = 6.15625 ,
1442
+ .strikethrough_thickness = 1.234375 ,
1443
+ .cap_height = 9.84375 ,
1444
+ .ex_height = 7.3828125 ,
1445
+ .ascii_height = switch (options .backend ) {
1446
+ .freetype ,
1447
+ .fontconfig_freetype ,
1448
+ .coretext_freetype ,
1449
+ = > 18.0625 ,
1450
+ .coretext ,
1451
+ .coretext_harfbuzz ,
1452
+ .coretext_noshape ,
1453
+ = > 16.0 ,
1454
+ .web_canvas = > unreachable ,
1455
+ },
1456
+ };
1457
+ const wideMetricsExpected = font.Metrics.FaceMetrics {
1458
+ .px_per_em = 16.0 ,
1459
+ .cell_width = switch (options .backend ) {
1460
+ .freetype ,
1461
+ .fontconfig_freetype ,
1462
+ .coretext_freetype ,
1463
+ = > 10.0 ,
1464
+ .coretext ,
1465
+ .coretext_harfbuzz ,
1466
+ .coretext_noshape ,
1467
+ = > 9.6 ,
1468
+ .web_canvas = > unreachable ,
1469
+ },
1470
+ .ascent = 14.72 ,
1471
+ .descent = -3.52 ,
1472
+ .line_gap = 1.6 ,
1473
+ .underline_position = -1.6 ,
1474
+ .underline_thickness = 0.8 ,
1475
+ .strikethrough_position = 4.24 ,
1476
+ .strikethrough_thickness = 0.8 ,
1477
+ .cap_height = 11.36 ,
1478
+ .ex_height = 8.48 ,
1479
+ .ascii_height = switch (options .backend ) {
1480
+ .freetype ,
1481
+ .fontconfig_freetype ,
1482
+ .coretext_freetype ,
1483
+ = > 16.0 ,
1484
+ .coretext ,
1485
+ .coretext_harfbuzz ,
1486
+ .coretext_noshape ,
1487
+ = > 15.472000000000001 ,
1488
+ .web_canvas = > unreachable ,
1489
+ },
1490
+ };
1491
+
1492
+ inline for (
1493
+ .{ narrowMetricsExpected , wideMetricsExpected },
1494
+ .{ narrowMetrics , wideMetrics },
1495
+ ) | metricsExpected , metricsActual | {
1496
+ inline for (@typeInfo (font .Metrics .FaceMetrics ).@"struct" .fields ) | field | {
1497
+ const expected = @field (metricsExpected , field .name );
1498
+ const actual = @field (metricsActual , field .name );
1499
+ // Unwrap optional fields
1500
+ const expectedValue , const actualValue = unwrap : switch (@typeInfo (field .type )) {
1501
+ .optional = > {
1502
+ if (expected ) | expectedValue | if (actual ) | actualValue | {
1503
+ break :unwrap .{ expectedValue , actualValue };
1504
+ };
1505
+ // Null values can be compared directly
1506
+ try std .testing .expectEqual (expected , actual );
1507
+ continue ;
1508
+ },
1509
+ else = > break :unwrap .{ expected , actual },
1510
+ };
1511
+ // All non-null values are floats
1512
+ const eps = std .math .floatEps (@TypeOf (actualValue - expectedValue ));
1513
+ try std .testing .expectApproxEqRel (
1514
+ expectedValue ,
1515
+ actualValue ,
1516
+ std .math .sqrt (eps ),
1517
+ );
1518
+ }
1519
+ }
1520
+
1521
+ // Verify estimated metrics. icWidth() should equal the smaller of
1522
+ // 2 * cell_width and ascii_height. For a narrow (wide) font, the
1523
+ // smaller quantity is the former (latter).
1524
+ try std .testing .expectEqual (
1525
+ 2 * narrowMetrics .cell_width ,
1526
+ narrowMetrics .icWidth (),
1527
+ );
1528
+ try std .testing .expectEqual (
1529
+ wideMetrics .ascii_height ,
1530
+ wideMetrics .icWidth (),
1531
+ );
1532
+ }
0 commit comments