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 }