lib/filigree/src/fixture/binary.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub fn appendTag(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), comptime value: []const u8) !void {
4 if (value.len != 4) @compileError("OpenType tags are four bytes");
5 try out.appendSlice(allocator, value);
6 }
7
8 pub fn appendU16(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: u16) !void {
9 const start = out.items.len;
10 try out.appendNTimes(allocator, 0, 2);
11 writeU16(out.items, start, value);
12 }
13
14 pub fn appendU24(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: u32) !void {
15 try out.append(allocator, @intCast((value >> 16) & 0xff));
16 try out.append(allocator, @intCast((value >> 8) & 0xff));
17 try out.append(allocator, @intCast(value & 0xff));
18 }
19
20 pub fn appendI16(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: i16) !void {
21 const start = out.items.len;
22 try out.appendNTimes(allocator, 0, 2);
23 writeI16(out.items, start, value);
24 }
25
26 pub fn appendU32(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: u32) !void {
27 const start = out.items.len;
28 try out.appendNTimes(allocator, 0, 4);
29 writeU32(out.items, start, value);
30 }
31
32 pub fn appendFixed16(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: i32) !void {
33 try appendU32(allocator, out, @intCast(value << 16));
34 }
35
36 pub fn appendF2Dot14(allocator: std.mem.Allocator, out: *std.ArrayListUnmanaged(u8), value: i16) !void {
37 try appendI16(allocator, out, value);
38 }
39
40 pub fn writeU16(bytes: []u8, offset: usize, value: u16) void {
41 std.mem.writeInt(u16, bytes[offset..][0..2], value, .big);
42 }
43
44 pub fn writeI16(bytes: []u8, offset: usize, value: i16) void {
45 std.mem.writeInt(i16, bytes[offset..][0..2], value, .big);
46 }
47
48 pub fn writeU32(bytes: []u8, offset: usize, value: u32) void {
49 std.mem.writeInt(u32, bytes[offset..][0..4], value, .big);
50 }
51
52 pub fn align4(value: usize) usize {
53 return (value + 3) & ~@as(usize, 3);
54 }
55
56 pub fn checksum(bytes: []const u8) u32 {
57 var sum: u32 = 0;
58 var offset: usize = 0;
59 while (offset < bytes.len) : (offset += 4) {
60 var word = [_]u8{ 0, 0, 0, 0 };
61 const n = @min(4, bytes.len - offset);
62 @memcpy(word[0..n], bytes[offset..][0..n]);
63 sum +%= std.mem.readInt(u32, &word, .big);
64 }
65 return sum;
66 }
67
68 pub fn tableOffset(bytes: []const u8, comptime name: []const u8) !usize {
69 if (bytes.len < 12) return error.InvalidFont;
70 const wanted = tag(name);
71 const table_count = try readU16(bytes, 4);
72 if (@as(usize, table_count) > (bytes.len - 12) / 16) return error.InvalidFont;
73 for (0..table_count) |i| {
74 const record = 12 + i * 16;
75 if (try readU32(bytes, record) != wanted) continue;
76 const offset = try readU32(bytes, record + 8);
77 const start: usize = @intCast(offset);
78 if (start >= bytes.len) return error.InvalidFont;
79 return start;
80 }
81 return error.MissingTable;
82 }
83
84 pub fn replaceLayoutScript(bytes: []u8, comptime table_name: []const u8, comptime script_name: []const u8) !void {
85 if (script_name.len != 4) @compileError("OpenType tags are four bytes");
86 const table_start = try tableOffset(bytes, table_name);
87 if (table_start > bytes.len or bytes.len - table_start < 12) return error.InvalidFont;
88 const script_list_relative = try readU16(bytes, table_start + 4);
89 const script_list_offset = table_start + @as(usize, script_list_relative);
90 if (script_list_offset > bytes.len or bytes.len - script_list_offset < 8) return error.InvalidFont;
91 @memcpy(bytes[script_list_offset + 2 ..][0..4], script_name);
92 }
93
94 pub fn setLayoutLookupFlag(bytes: []u8, comptime table_name: []const u8, lookup_index: u16, lookup_flag: u16) !void {
95 const table_start = try tableOffset(bytes, table_name);
96 if (table_start > bytes.len or bytes.len - table_start < 10) return error.InvalidFont;
97 const lookup_list_relative = try readU16(bytes, table_start + 8);
98 const lookup_list_offset = table_start + @as(usize, lookup_list_relative);
99 if (lookup_list_offset > bytes.len or bytes.len - lookup_list_offset < 2) return error.InvalidFont;
100 const lookup_count = try readU16(bytes, lookup_list_offset);
101 if (lookup_index >= lookup_count) return error.InvalidFont;
102 const lookup_record = lookup_list_offset + 2 + @as(usize, lookup_index) * 2;
103 if (lookup_record > bytes.len or bytes.len - lookup_record < 2) return error.InvalidFont;
104 const lookup_relative = try readU16(bytes, lookup_record);
105 const lookup_offset = lookup_list_offset + @as(usize, lookup_relative);
106 if (lookup_offset > bytes.len or bytes.len - lookup_offset < 4) return error.InvalidFont;
107 writeU16(bytes, lookup_offset + 2, lookup_flag);
108 }
109
110 pub fn assembleCollection(allocator: std.mem.Allocator, fonts: []const []const u8) ![]u8 {
111 if (fonts.len == 0) return error.EmptyCollection;
112 if (fonts.len > std.math.maxInt(u32)) return error.CollectionTooLarge;
113 if (fonts.len > (std.math.maxInt(usize) - 12) / 4) return error.CollectionTooLarge;
114
115 var total_len = align4(12 + fonts.len * 4);
116 for (fonts) |font| {
117 total_len = align4(total_len);
118 if (font.len > std.math.maxInt(usize) - total_len) return error.CollectionTooLarge;
119 total_len += font.len;
120 }
121 if (total_len > std.math.maxInt(u32)) return error.CollectionTooLarge;
122
123 const bytes = try allocator.alloc(u8, total_len);
124 @memset(bytes, 0);
125 writeU32(bytes, 0, tag("ttcf"));
126 writeU32(bytes, 4, 0x00010000);
127 writeU32(bytes, 8, @intCast(fonts.len));
128
129 var offset = align4(12 + fonts.len * 4);
130 for (fonts, 0..) |font, font_index| {
131 offset = align4(offset);
132 if (offset > std.math.maxInt(u32)) return error.CollectionTooLarge;
133 try copyFontAt(bytes, offset, font);
134 writeU32(bytes, 12 + font_index * 4, @intCast(offset));
135 offset += font.len;
136 }
137
138 return bytes;
139 }
140
141 fn copyFontAt(bytes: []u8, offset: usize, font: []const u8) !void {
142 if (font.len < 12 or offset > bytes.len or font.len > bytes.len - offset) return error.InvalidCollectionFont;
143 @memcpy(bytes[offset..][0..font.len], font);
144 const version = readU32(font, 0) catch return error.InvalidCollectionFont;
145 if (version != 0x00010000 and version != tag("true") and version != tag("OTTO")) return error.InvalidCollectionFont;
146 const table_count = readU16(font, 4) catch return error.InvalidCollectionFont;
147 if (@as(usize, table_count) > (font.len - 12) / 16) return error.InvalidCollectionFont;
148 for (0..table_count) |table_index| {
149 const record = 12 + table_index * 16;
150 const table_offset = readU32(font, record + 8) catch return error.InvalidCollectionFont;
151 const table_offset_usize: usize = @intCast(table_offset);
152 if (offset > std.math.maxInt(u32) - table_offset_usize) return error.CollectionTooLarge;
153 writeU32(bytes, offset + record + 8, @intCast(offset + table_offset_usize));
154 }
155 }
156
157 fn readU16(bytes: []const u8, offset: usize) !u16 {
158 if (offset > bytes.len or bytes.len - offset < 2) return error.EndOfStream;
159 return std.mem.readInt(u16, bytes[offset..][0..2], .big);
160 }
161
162 fn readU32(bytes: []const u8, offset: usize) !u32 {
163 if (offset > bytes.len or bytes.len - offset < 4) return error.EndOfStream;
164 return std.mem.readInt(u32, bytes[offset..][0..4], .big);
165 }
166
167 fn tag(comptime name: []const u8) u32 {
168 if (name.len != 4) @compileError("OpenType tags are four bytes");
169 return (@as(u32, name[0]) << 24) |
170 (@as(u32, name[1]) << 16) |
171 (@as(u32, name[2]) << 8) |
172 @as(u32, name[3]);
173 }