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 }