lib/filigree/src/font/gdef.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const binary = @import("binary.zig");
3 const layout = @import("../layout/root.zig");
4 const model = @import("model.zig");
5 const variations = @import("variations.zig");
6
7 const FontError = model.FontError;
8 const GlyphClass = model.GlyphClass;
9 const LigatureCaret = model.LigatureCaret;
10 const Table = binary.Table;
11 const VariationSetting = model.VariationSetting;
12 const DeltaSetIndex = variations.DeltaSetIndex;
13 const ItemVariationStore = variations.ItemVariationStore;
14
15 pub const Gdef = struct {
16 table: Table,
17 glyph_class_def_offset: ?usize,
18 mark_attach_class_def_offset: ?usize,
19 mark_glyph_sets_def_offset: ?usize,
20 lig_caret_list_offset: ?usize,
21 variation_store: ?ItemVariationStore,
22
23 pub fn init(data: []const u8, gdef_table: Table, axis_count: ?u16) FontError!Gdef {
24 const bytes = data[gdef_table.offset..][0..gdef_table.len];
25 if (bytes.len < 12) return error.InvalidGdef;
26 const major = try readGdefU16(bytes, 0);
27 const minor = try readGdefU16(bytes, 2);
28 if (major != 1 or (minor != 0 and minor != 2 and minor != 3)) return error.InvalidGdef;
29 if (minor == 2 and bytes.len < 14) return error.InvalidGdef;
30 if (minor == 3 and bytes.len < 18) return error.InvalidGdef;
31
32 const glyph_class_def_relative = try readGdefU16(bytes, 4);
33 const glyph_class_def_offset = if (glyph_class_def_relative == 0)
34 null
35 else
36 try gdefChildOffset(bytes, 0, glyph_class_def_relative, 2);
37 if (glyph_class_def_offset) |offset| {
38 _ = layout.ClassDef.init(bytes, offset) catch return error.InvalidGdef;
39 }
40
41 const lig_caret_list_relative = try readGdefU16(bytes, 8);
42 const lig_caret_list_offset = if (lig_caret_list_relative == 0)
43 null
44 else
45 try gdefChildOffset(bytes, 0, lig_caret_list_relative, 4);
46 if (lig_caret_list_offset) |offset| try validateGdefLigCaretList(bytes, offset);
47
48 const mark_attach_class_def_relative = try readGdefU16(bytes, 10);
49 const mark_attach_class_def_offset = if (mark_attach_class_def_relative == 0)
50 null
51 else
52 try gdefChildOffset(bytes, 0, mark_attach_class_def_relative, 2);
53 if (mark_attach_class_def_offset) |offset| {
54 _ = layout.ClassDef.init(bytes, offset) catch return error.InvalidGdef;
55 }
56
57 const mark_glyph_sets_def_offset = if (minor >= 2) blk: {
58 const relative = try readGdefU16(bytes, 12);
59 if (relative == 0) break :blk null;
60 const offset = try gdefChildOffset(bytes, 0, relative, 4);
61 try validateGdefMarkGlyphSets(bytes, offset);
62 break :blk offset;
63 } else null;
64
65 const variation_store = if (minor >= 3) blk: {
66 const relative = try readGdefU32(bytes, 14);
67 if (relative == 0) break :blk null;
68 const offset = try gdefChildOffset32(bytes, 0, relative, 8);
69 const variation_axis_count = axis_count orelse break :blk null;
70 const store_table = Table{
71 .offset = gdef_table.offset + offset,
72 .len = gdef_table.len - offset,
73 };
74 break :blk ItemVariationStore.init(data, store_table, variation_axis_count) catch return error.InvalidGdef;
75 } else null;
76
77 return .{
78 .table = gdef_table,
79 .glyph_class_def_offset = glyph_class_def_offset,
80 .mark_attach_class_def_offset = mark_attach_class_def_offset,
81 .mark_glyph_sets_def_offset = mark_glyph_sets_def_offset,
82 .lig_caret_list_offset = lig_caret_list_offset,
83 .variation_store = variation_store,
84 };
85 }
86
87 pub fn glyphClass(self: Gdef, data: []const u8, glyph_id: u32) FontError!GlyphClass {
88 const glyph_class_def_offset = self.glyph_class_def_offset orelse return .unknown;
89 const bytes = data[self.table.offset..][0..self.table.len];
90 const class_def = layout.ClassDef.init(bytes, glyph_class_def_offset) catch return error.InvalidGdef;
91 return glyphClassFromValue(class_def.class(bytes, glyph_id) catch return error.InvalidGdef);
92 }
93
94 pub fn markAttachmentClass(self: Gdef, data: []const u8, glyph_id: u32) FontError!u16 {
95 const mark_attach_class_def_offset = self.mark_attach_class_def_offset orelse return 0;
96 const bytes = data[self.table.offset..][0..self.table.len];
97 const class_def = layout.ClassDef.init(bytes, mark_attach_class_def_offset) catch return error.InvalidGdef;
98 return class_def.class(bytes, glyph_id) catch return error.InvalidGdef;
99 }
100
101 pub fn markGlyphSetContains(self: Gdef, data: []const u8, set_index: u16, glyph_id: u32) FontError!bool {
102 const mark_glyph_sets_def_offset = self.mark_glyph_sets_def_offset orelse return false;
103 const bytes = data[self.table.offset..][0..self.table.len];
104 if (try readGdefU16(bytes, mark_glyph_sets_def_offset) != 1) return error.InvalidGdef;
105 const set_count = try readGdefU16(bytes, mark_glyph_sets_def_offset + 2);
106 if (set_index >= set_count) return error.InvalidGdef;
107 const coverage_offsets = mark_glyph_sets_def_offset + 4;
108 if (!hasGdefBytes(bytes, coverage_offsets, @as(usize, set_count) * 4)) return error.InvalidGdef;
109 const coverage_relative = try readGdefU32(bytes, coverage_offsets + @as(usize, set_index) * 4);
110 const coverage_offset = try gdefChildOffset32(bytes, mark_glyph_sets_def_offset, coverage_relative, 2);
111 const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGdef;
112 return (coverage.index(bytes, glyph_id) catch return error.InvalidGdef) != null;
113 }
114
115 pub fn ligatureCaretCount(self: Gdef, data: []const u8, glyph_id: u32) FontError!u16 {
116 const lig_caret_list_offset = self.lig_caret_list_offset orelse return 0;
117 const bytes = data[self.table.offset..][0..self.table.len];
118 const lig_glyph_offset = (try gdefLigGlyphOffset(bytes, lig_caret_list_offset, glyph_id)) orelse return 0;
119 return try readGdefU16(bytes, lig_glyph_offset);
120 }
121
122 pub fn ligatureCaret(self: Gdef, data: []const u8, glyph_id: u32, caret_index: usize) FontError!?LigatureCaret {
123 const lig_caret_list_offset = self.lig_caret_list_offset orelse return null;
124 const bytes = data[self.table.offset..][0..self.table.len];
125 const lig_glyph_offset = (try gdefLigGlyphOffset(bytes, lig_caret_list_offset, glyph_id)) orelse return null;
126 const caret_count = try readGdefU16(bytes, lig_glyph_offset);
127 if (caret_index >= caret_count) return null;
128 const caret_offsets = lig_glyph_offset + 2;
129 if (!hasGdefBytes(bytes, caret_offsets, @as(usize, caret_count) * 2)) return error.InvalidGdef;
130 const caret_value_offset = try gdefChildOffset(
131 bytes,
132 lig_glyph_offset,
133 try readGdefU16(bytes, caret_offsets + caret_index * 2),
134 2,
135 );
136 return try readGdefCaretValue(bytes, caret_value_offset);
137 }
138
139 pub fn variationDelta(self: Gdef, data: []const u8, face: anytype, settings: []const VariationSetting, index: DeltaSetIndex) FontError!i32 {
140 const store = self.variation_store orelse return 0;
141 return try store.delta(data, face, settings, index);
142 }
143 };
144
145 fn glyphClassFromValue(value: u16) GlyphClass {
146 return switch (value) {
147 1 => .base,
148 2 => .ligature,
149 3 => .mark,
150 4 => .component,
151 else => .unknown,
152 };
153 }
154
155 fn validateGdefLigCaretList(data: []const u8, lig_caret_list_offset: usize) FontError!void {
156 if (!hasGdefBytes(data, lig_caret_list_offset, 4)) return error.InvalidGdef;
157 const coverage_offset = try gdefChildOffset(data, lig_caret_list_offset, try readGdefU16(data, lig_caret_list_offset), 2);
158 _ = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGdef;
159
160 const lig_glyph_count = try readGdefU16(data, lig_caret_list_offset + 2);
161 const lig_glyph_offsets = lig_caret_list_offset + 4;
162 if (!hasGdefBytes(data, lig_glyph_offsets, @as(usize, lig_glyph_count) * 2)) return error.InvalidGdef;
163
164 for (0..lig_glyph_count) |lig_glyph_index| {
165 const lig_glyph_offset = try gdefChildOffset(
166 data,
167 lig_caret_list_offset,
168 try readGdefU16(data, lig_glyph_offsets + lig_glyph_index * 2),
169 2,
170 );
171 const caret_count = try readGdefU16(data, lig_glyph_offset);
172 const caret_offsets = lig_glyph_offset + 2;
173 if (!hasGdefBytes(data, caret_offsets, @as(usize, caret_count) * 2)) return error.InvalidGdef;
174 for (0..caret_count) |caret_index| {
175 const caret_value_offset = try gdefChildOffset(
176 data,
177 lig_glyph_offset,
178 try readGdefU16(data, caret_offsets + caret_index * 2),
179 2,
180 );
181 _ = try readGdefCaretValue(data, caret_value_offset);
182 }
183 }
184 }
185
186 fn validateGdefMarkGlyphSets(data: []const u8, mark_glyph_sets_offset: usize) FontError!void {
187 if (!hasGdefBytes(data, mark_glyph_sets_offset, 4)) return error.InvalidGdef;
188 if (try readGdefU16(data, mark_glyph_sets_offset) != 1) return error.InvalidGdef;
189 const set_count = try readGdefU16(data, mark_glyph_sets_offset + 2);
190 const coverage_offsets = mark_glyph_sets_offset + 4;
191 if (!hasGdefBytes(data, coverage_offsets, @as(usize, set_count) * 4)) return error.InvalidGdef;
192 for (0..set_count) |set_index| {
193 const coverage_offset = try gdefChildOffset32(
194 data,
195 mark_glyph_sets_offset,
196 try readGdefU32(data, coverage_offsets + set_index * 4),
197 2,
198 );
199 _ = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGdef;
200 }
201 }
202
203 fn gdefLigGlyphOffset(data: []const u8, lig_caret_list_offset: usize, glyph_id: u32) FontError!?usize {
204 if (!hasGdefBytes(data, lig_caret_list_offset, 4)) return error.InvalidGdef;
205 const coverage_offset = try gdefChildOffset(data, lig_caret_list_offset, try readGdefU16(data, lig_caret_list_offset), 2);
206 const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGdef;
207 const coverage_index = (coverage.index(data, glyph_id) catch return error.InvalidGdef) orelse return null;
208
209 const lig_glyph_count = try readGdefU16(data, lig_caret_list_offset + 2);
210 if (coverage_index >= lig_glyph_count) return error.InvalidGdef;
211 const lig_glyph_offsets = lig_caret_list_offset + 4;
212 if (!hasGdefBytes(data, lig_glyph_offsets, @as(usize, lig_glyph_count) * 2)) return error.InvalidGdef;
213 return try gdefChildOffset(
214 data,
215 lig_caret_list_offset,
216 try readGdefU16(data, lig_glyph_offsets + @as(usize, coverage_index) * 2),
217 2,
218 );
219 }
220
221 fn readGdefCaretValue(data: []const u8, caret_value_offset: usize) FontError!?LigatureCaret {
222 const format = try readGdefU16(data, caret_value_offset);
223 return switch (format) {
224 1 => blk: {
225 if (!hasGdefBytes(data, caret_value_offset, 4)) return error.InvalidGdef;
226 break :blk .{ .coordinate = try readGdefI16(data, caret_value_offset + 2) };
227 },
228 2 => blk: {
229 if (!hasGdefBytes(data, caret_value_offset, 4)) return error.InvalidGdef;
230 break :blk null;
231 },
232 3 => blk: {
233 if (!hasGdefBytes(data, caret_value_offset, 6)) return error.InvalidGdef;
234 const coordinate = try readGdefI16(data, caret_value_offset + 2);
235 const device_offset = try readGdefU16(data, caret_value_offset + 4);
236 if (device_offset != 0) _ = try gdefChildOffset(data, caret_value_offset, device_offset, 2);
237 break :blk .{ .coordinate = coordinate };
238 },
239 else => error.InvalidGdef,
240 };
241 }
242
243 fn gdefChildOffset(data: []const u8, base: usize, relative_offset: u16, min_len: usize) FontError!usize {
244 const relative: usize = relative_offset;
245 if (relative == 0 or base > data.len or relative > data.len - base) return error.InvalidGdef;
246 const offset = base + relative;
247 if (!hasGdefBytes(data, offset, min_len)) return error.InvalidGdef;
248 return offset;
249 }
250
251 fn gdefChildOffset32(data: []const u8, base: usize, relative_offset: u32, min_len: usize) FontError!usize {
252 if (relative_offset == 0 or relative_offset > std.math.maxInt(usize)) return error.InvalidGdef;
253 const relative: usize = @intCast(relative_offset);
254 if (base > data.len or relative > data.len - base) return error.InvalidGdef;
255 const offset = base + relative;
256 if (!hasGdefBytes(data, offset, min_len)) return error.InvalidGdef;
257 return offset;
258 }
259
260 fn hasGdefBytes(data: []const u8, offset: usize, len: usize) bool {
261 return offset <= data.len and len <= data.len - offset;
262 }
263
264 fn readGdefU16(data: []const u8, offset: usize) FontError!u16 {
265 if (!hasGdefBytes(data, offset, 2)) return error.InvalidGdef;
266 return std.mem.readInt(u16, data[offset..][0..2], .big);
267 }
268
269 fn readGdefI16(data: []const u8, offset: usize) FontError!i16 {
270 return @bitCast(try readGdefU16(data, offset));
271 }
272
273 fn readGdefU32(data: []const u8, offset: usize) FontError!u32 {
274 if (!hasGdefBytes(data, offset, 4)) return error.InvalidGdef;
275 return std.mem.readInt(u32, data[offset..][0..4], .big);
276 }
277
278 test "GDEF child offsets stay inside table data" {
279 const data = @as([10]u8, @splat(0));
280 try std.testing.expectEqual(@as(usize, 6), try gdefChildOffset(&data, 2, 4, 2));
281 try std.testing.expectError(error.InvalidGdef, gdefChildOffset(&data, 2, 0, 2));
282 try std.testing.expectError(error.InvalidGdef, gdefChildOffset(&data, 10, 1, 2));
283 try std.testing.expectError(error.InvalidGdef, gdefChildOffset(&data, 8, 1, 2));
284 }
285
286 test "GDEF glyph class values map to public classes" {
287 try std.testing.expectEqual(GlyphClass.unknown, glyphClassFromValue(0));
288 try std.testing.expectEqual(GlyphClass.base, glyphClassFromValue(1));
289 try std.testing.expectEqual(GlyphClass.ligature, glyphClassFromValue(2));
290 try std.testing.expectEqual(GlyphClass.mark, glyphClassFromValue(3));
291 try std.testing.expectEqual(GlyphClass.component, glyphClassFromValue(4));
292 try std.testing.expectEqual(GlyphClass.unknown, glyphClassFromValue(5));
293 }
294
295 test "GDEF caret values read supported coordinate formats" {
296 const format_1 = [_]u8{
297 0, 1,
298 0, 250,
299 };
300 const format_2 = [_]u8{
301 0, 2,
302 0, 7,
303 };
304 const format_3 = [_]u8{
305 0, 3,
306 0xff, 0x9c,
307 0, 0,
308 };
309
310 try std.testing.expectEqual(@as(i16, 250), (try readGdefCaretValue(&format_1, 0)).?.coordinate);
311 try std.testing.expect((try readGdefCaretValue(&format_2, 0)) == null);
312 try std.testing.expectEqual(@as(i16, -100), (try readGdefCaretValue(&format_3, 0)).?.coordinate);
313 try std.testing.expectError(error.InvalidGdef, readGdefCaretValue(&[_]u8{ 0, 4 }, 0));
314 }