lib/filigree/src/font/metrics.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const binary = @import("binary.zig");
3 const directory = @import("directory.zig");
4 const model = @import("model.zig");
5
6 const FontError = model.FontError;
7 const Table = binary.Table;
8 const readI16 = binary.readI16;
9 const readU16 = binary.readU16;
10
11 pub const VerticalMetrics = struct {
12 table: ?Table,
13 count: u16,
14 };
15
16 pub const Vorg = struct {
17 table: Table,
18 default_y: i16,
19 record_count: u16,
20
21 pub fn init(data: []const u8, vorg_table: Table) FontError!Vorg {
22 if (vorg_table.len < 8) return error.InvalidFont;
23 const major = try readU16(data, vorg_table.offset);
24 const minor = try readU16(data, vorg_table.offset + 2);
25 if (major != 1 or minor != 0) return error.InvalidFont;
26 const record_count = try readU16(data, vorg_table.offset + 6);
27 if (@as(u64, record_count) * 4 > @as(u64, vorg_table.len - 8)) return error.InvalidFont;
28 const result = Vorg{
29 .table = vorg_table,
30 .default_y = try readI16(data, vorg_table.offset + 4),
31 .record_count = record_count,
32 };
33 var previous: ?u16 = null;
34 for (0..record_count) |record_index| {
35 const glyph_id = try readU16(data, result.recordOffset(record_index));
36 if (previous) |previous_glyph| {
37 if (glyph_id <= previous_glyph) return error.InvalidFont;
38 }
39 previous = glyph_id;
40 }
41 return result;
42 }
43
44 pub fn originY(self: Vorg, data: []const u8, glyph_id: u32) FontError!i16 {
45 if (glyph_id > std.math.maxInt(u16)) return self.default_y;
46 const wanted: u16 = @intCast(glyph_id);
47 var left: usize = 0;
48 var right: usize = self.record_count;
49 while (left < right) {
50 const mid = left + (right - left) / 2;
51 const offset = self.recordOffset(mid);
52 const current = try readU16(data, offset);
53 if (wanted < current) {
54 right = mid;
55 } else if (wanted > current) {
56 left = mid + 1;
57 } else {
58 return try readI16(data, offset + 2);
59 }
60 }
61 return self.default_y;
62 }
63
64 fn recordOffset(self: Vorg, record_index: usize) usize {
65 return self.table.offset + 8 + record_index * 4;
66 }
67 };
68
69 pub const HorizontalMetrics = struct {
70 ascender: i16,
71 descender: i16,
72 line_gap: i16,
73 };
74
75 pub fn verticalMetrics(data: []const u8, directory_offset: usize, num_glyphs: u16) VerticalMetrics {
76 const vhea = directory.optionalAt(data, directory_offset, "vhea") orelse return .{ .table = null, .count = 0 };
77 const vmtx = directory.optionalAt(data, directory_offset, "vmtx") orelse return .{ .table = null, .count = 0 };
78 if (vhea.len < 36) return .{ .table = null, .count = 0 };
79 const count = readU16(data, vhea.offset + 34) catch return .{ .table = null, .count = 0 };
80 if (count == 0 or count > num_glyphs) return .{ .table = null, .count = 0 };
81 if (@as(usize, count) > vmtx.len / 4) return .{ .table = null, .count = 0 };
82 return .{ .table = vmtx, .count = count };
83 }
84
85 pub fn horizontalTypoMetrics(data: []const u8, directory_offset: usize) ?HorizontalMetrics {
86 const os2 = directory.optionalAt(data, directory_offset, "OS/2") orelse return null;
87 if (os2.len < 74) return null;
88 return .{
89 .ascender = readI16(data, os2.offset + 68) catch return null,
90 .descender = readI16(data, os2.offset + 70) catch return null,
91 .line_gap = readI16(data, os2.offset + 72) catch return null,
92 };
93 }