lib/filigree/src/font/directory.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const binary = @import("binary.zig");
3 const model = @import("model.zig");
4
5 const FontError = model.FontError;
6 const Table = binary.Table;
7 const Tag = model.Tag;
8 const readU16 = binary.readU16;
9 const readU32 = binary.readU32;
10 const tag = model.tag;
11
12 pub fn table(data: []const u8, comptime name: []const u8) FontError!Table {
13 return tableAt(data, 0, name);
14 }
15
16 pub fn tableAt(data: []const u8, directory_offset: usize, comptime name: []const u8) FontError!Table {
17 return tableAtTag(data, directory_offset, tag(name));
18 }
19
20 pub fn tableAtTag(data: []const u8, directory_offset: usize, wanted: Tag) FontError!Table {
21 if (directory_offset > data.len or data.len - directory_offset < 12) return error.InvalidFont;
22 const num_tables = try readU16(data, directory_offset + 4);
23 if (@as(usize, num_tables) > (data.len - directory_offset - 12) / 16) return error.InvalidFont;
24 for (0..num_tables) |i| {
25 const record = directory_offset + 12 + i * 16;
26 if (try readU32(data, record) != wanted) continue;
27 const offset = try readU32(data, record + 8);
28 const len = try readU32(data, record + 12);
29 const start: usize = @intCast(offset);
30 const size: usize = @intCast(len);
31 if (start > data.len or size > data.len - start) return error.InvalidFont;
32 return .{ .offset = start, .len = size };
33 }
34 return error.MissingTable;
35 }
36
37 pub fn optional(data: []const u8, comptime name: []const u8) ?Table {
38 return table(data, name) catch null;
39 }
40
41 pub fn optionalAt(data: []const u8, directory_offset: usize, comptime name: []const u8) ?Table {
42 return tableAt(data, directory_offset, name) catch null;
43 }
44
45 pub fn optionalAtTag(data: []const u8, directory_offset: usize, table_tag: Tag) ?Table {
46 return tableAtTag(data, directory_offset, table_tag) catch null;
47 }
48
49 pub fn fontDirectoryOffset(data: []const u8, face_index: u32) FontError!usize {
50 if (data.len < 12) return error.InvalidFont;
51 if (try readU32(data, 0) != tag("ttcf")) {
52 if (face_index != 0) return error.InvalidFont;
53 return 0;
54 }
55
56 const version = try readU32(data, 4);
57 if (version != 0x00010000 and version != 0x00020000) return error.InvalidFont;
58 const face_count = try readU32(data, 8);
59 if (face_count == 0 or face_index >= face_count) return error.InvalidFont;
60 const face_count_usize: usize = @intCast(face_count);
61 if (face_count_usize > (data.len - 12) / 4) return error.InvalidFont;
62 const record = 12 + @as(usize, @intCast(face_index)) * 4;
63 const offset = try readU32(data, record);
64 const directory_offset: usize = @intCast(offset);
65 if (directory_offset > data.len or data.len - directory_offset < 12) return error.InvalidFont;
66 return directory_offset;
67 }
68
69 test "table lookup rejects empty directory" {
70 try std.testing.expectError(error.InvalidFont, table(&.{}, "head"));
71 }