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 }