lib/filigree/src/font/lookups.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const gdef_table = @import("gdef.zig");
  3 const model = @import("model.zig");
  4 
  5 const FontError = model.FontError;
  6 const Gdef = gdef_table.Gdef;
  7 const LayoutSelection = model.LayoutSelection;
  8 const Tag = model.Tag;
  9 const VariationSetting = model.VariationSetting;
 10 
 11 pub const Kind = enum {
 12     gsub,
 13     gpos,
 14 };
 15 
 16 pub const VariationContext = struct {
 17     ptr: *const anyopaque,
 18     variation_axis_count: *const fn (*const anyopaque) u16,
 19     normalized_coordinate_at: *const fn (*const anyopaque, []const VariationSetting, usize) FontError!i16,
 20 
 21     pub fn variationAxisCount(self: VariationContext) u16 {
 22         return self.variation_axis_count(self.ptr);
 23     }
 24 
 25     pub fn normalizedCoordinateAt(self: VariationContext, settings: []const VariationSetting, axis_index: usize) FontError!i16 {
 26         return self.normalized_coordinate_at(self.ptr, settings, axis_index);
 27     }
 28 };
 29 
 30 pub const ignore_base_glyphs: u16 = 0x0002;
 31 pub const ignore_ligatures: u16 = 0x0004;
 32 pub const ignore_marks: u16 = 0x0008;
 33 pub const use_mark_filtering_set: u16 = 0x0010;
 34 pub const reserved_flags: u16 = 0x00e0;
 35 pub const mark_attachment_type: u16 = 0xff00;
 36 
 37 pub const LookupFilter = struct {
 38     flags: u16,
 39     mark_filtering_set: ?u16 = null,
 40 };
 41 
 42 pub fn matchingFeatureSubstitutionOffset(comptime kind: Kind, bytes: []const u8, feature_variations_offset: ?usize, face: anytype, selection: LayoutSelection) FontError!?usize {
 43     const offset = feature_variations_offset orelse return null;
 44     if (!hasBytes(kind, bytes, offset, 8)) return invalid(kind);
 45     if (try readU16(kind, bytes, offset) != 1 or try readU16(kind, bytes, offset + 2) != 0) return null;
 46     const record_count = try readU32(kind, bytes, offset + 4);
 47     if (@as(u64, record_count) * 8 > @as(u64, bytes.len - offset - 8)) return invalid(kind);
 48 
 49     for (0..record_count) |record_index| {
 50         const record_offset = offset + 8 + @as(usize, @intCast(record_index)) * 8;
 51         const condition_set_offset = try readU32(kind, bytes, record_offset);
 52         if (!try featureConditionSetMatches(kind, bytes, offset, condition_set_offset, face, selection)) continue;
 53 
 54         const substitution_relative = try readU32(kind, bytes, record_offset + 4);
 55         if (substitution_relative == 0) return null;
 56         const substitution_offset = try childOffset32(kind, bytes, offset, substitution_relative, 6);
 57         if (try readU16(kind, bytes, substitution_offset) == 1 and try readU16(kind, bytes, substitution_offset + 2) == 0) {
 58             return substitution_offset;
 59         }
 60     }
 61     return null;
 62 }
 63 
 64 fn featureConditionSetMatches(comptime kind: Kind, bytes: []const u8, feature_variations_offset: usize, relative_offset: u32, face: anytype, selection: LayoutSelection) FontError!bool {
 65     if (relative_offset == 0) return true;
 66     const offset = try childOffset32(kind, bytes, feature_variations_offset, relative_offset, 2);
 67     const condition_count = try readU16(kind, bytes, offset);
 68     if (@as(u64, condition_count) * 4 > @as(u64, bytes.len - offset - 2)) return invalid(kind);
 69     const condition_offsets = offset + 2;
 70     for (0..condition_count) |condition_index| {
 71         const condition_relative = try readU32(kind, bytes, condition_offsets + condition_index * 4);
 72         const condition_offset = try childOffset32(kind, bytes, offset, condition_relative, 2);
 73         if (!try featureConditionMatches(kind, bytes, condition_offset, face, selection)) return false;
 74     }
 75     return true;
 76 }
 77 
 78 fn featureConditionMatches(comptime kind: Kind, bytes: []const u8, condition_offset: usize, face: anytype, selection: LayoutSelection) FontError!bool {
 79     const format = try readU16(kind, bytes, condition_offset);
 80     if (format != 1) return false;
 81     if (!hasBytes(kind, bytes, condition_offset, 8)) return invalid(kind);
 82     const axis_index = try readU16(kind, bytes, condition_offset + 2);
 83     const min_value = try readI16(kind, bytes, condition_offset + 4);
 84     const max_value = try readI16(kind, bytes, condition_offset + 6);
 85     if (min_value > max_value) return false;
 86     const found_face = face orelse return false;
 87     if (axis_index >= found_face.variationAxisCount()) return false;
 88     const value = found_face.normalizedCoordinateAt(selection.variations, axis_index) catch return false;
 89     return value >= min_value and value <= max_value;
 90 }
 91 
 92 pub fn substitutedFeatureOffset(comptime kind: Kind, bytes: []const u8, substitution_offset: usize, feature_index: u16) FontError!?usize {
 93     if (try readU16(kind, bytes, substitution_offset) != 1 or try readU16(kind, bytes, substitution_offset + 2) != 0) return null;
 94     const substitution_count = try readU16(kind, bytes, substitution_offset + 4);
 95     if (@as(u64, substitution_count) * 6 > @as(u64, bytes.len - substitution_offset - 6)) return invalid(kind);
 96     const records = substitution_offset + 6;
 97     for (0..substitution_count) |record_index| {
 98         const record_offset = records + record_index * 6;
 99         const current_feature_index = try readU16(kind, bytes, record_offset);
100         if (current_feature_index > feature_index) return null;
101         if (current_feature_index != feature_index) continue;
102         const alternate_relative = try readU32(kind, bytes, record_offset + 2);
103         return try childOffset32(kind, bytes, substitution_offset, alternate_relative, 4);
104     }
105     return null;
106 }
107 
108 pub fn featureTableContainsLookup(comptime kind: Kind, bytes: []const u8, feature_offset: usize, lookup_index: u16) FontError!bool {
109     if (!hasBytes(kind, bytes, feature_offset, 4)) return invalid(kind);
110     const lookup_index_count = try readU16(kind, bytes, feature_offset + 2);
111     if (@as(u64, lookup_index_count) * 2 > @as(u64, bytes.len - feature_offset - 4)) return invalid(kind);
112     for (0..lookup_index_count) |i| {
113         if (try readU16(kind, bytes, feature_offset + 4 + i * 2) == lookup_index) return true;
114     }
115     return false;
116 }
117 
118 pub fn baseOffset(comptime kind: Kind, data: []const u8, relative_offset: u16) FontError!usize {
119     const relative: usize = relative_offset;
120     if (relative == 0 or relative >= data.len) return invalid(kind);
121     if (data.len - relative < 2) return invalid(kind);
122     return relative;
123 }
124 
125 pub fn childOffset(comptime kind: Kind, data: []const u8, base: usize, relative_offset: u16, min_len: usize) FontError!usize {
126     const relative: usize = relative_offset;
127     if (relative == 0 or base > data.len or relative > data.len - base) return invalid(kind);
128     const child = base + relative;
129     if (!hasBytes(kind, data, child, min_len)) return invalid(kind);
130     return child;
131 }
132 
133 pub fn childOffset32(comptime kind: Kind, data: []const u8, base: usize, relative_offset: u32, min_len: usize) FontError!usize {
134     if (relative_offset == 0 or relative_offset > std.math.maxInt(usize)) return invalid(kind);
135     const relative: usize = @intCast(relative_offset);
136     if (base > data.len or relative > data.len - base) return invalid(kind);
137     const child = base + relative;
138     if (!hasBytes(kind, data, child, min_len)) return invalid(kind);
139     return child;
140 }
141 
142 pub fn hasBytes(comptime kind: Kind, data: []const u8, offset_value: usize, len: usize) bool {
143     _ = kind;
144     return offset_value <= data.len and len <= data.len - offset_value;
145 }
146 
147 pub fn readU16(comptime kind: Kind, data: []const u8, offset_value: usize) FontError!u16 {
148     if (!hasBytes(kind, data, offset_value, 2)) return invalid(kind);
149     return std.mem.readInt(u16, data[offset_value..][0..2], .big);
150 }
151 
152 pub fn readI16(comptime kind: Kind, data: []const u8, offset_value: usize) FontError!i16 {
153     return @bitCast(try readU16(kind, data, offset_value));
154 }
155 
156 pub fn readU32(comptime kind: Kind, data: []const u8, offset_value: usize) FontError!u32 {
157     if (!hasBytes(kind, data, offset_value, 4)) return invalid(kind);
158     return std.mem.readInt(u32, data[offset_value..][0..4], .big);
159 }
160 
161 pub fn invalid(comptime kind: Kind) FontError {
162     return switch (kind) {
163         .gsub => error.InvalidGsub,
164         .gpos => error.InvalidGpos,
165     };
166 }
167 
168 pub fn lookupFilterSkipsGlyphs(filter: LookupFilter) bool {
169     return filter.flags != 0 or filter.mark_filtering_set != null;
170 }
171 
172 pub fn gsubLookupFilter(data: []const u8, lookup_offset: usize) FontError!LookupFilter {
173     const flags = try gsubReadU16(data, lookup_offset + 2);
174     if ((flags & reserved_flags) != 0) return error.InvalidGsub;
175     const subtable_count = try gsubReadU16(data, lookup_offset + 4);
176     const mark_filtering_set_offset = lookup_offset + 6 + @as(usize, subtable_count) * 2;
177     if ((flags & use_mark_filtering_set) == 0) {
178         return .{ .flags = flags };
179     }
180     if (!gsubHasBytes(data, mark_filtering_set_offset, 2)) return error.InvalidGsub;
181     return .{
182         .flags = flags,
183         .mark_filtering_set = try gsubReadU16(data, mark_filtering_set_offset),
184     };
185 }
186 
187 pub fn gposLookupFilter(data: []const u8, lookup_offset: usize) FontError!LookupFilter {
188     const flags = try gposReadU16(data, lookup_offset + 2);
189     if ((flags & reserved_flags) != 0) return error.InvalidGpos;
190     const subtable_count = try gposReadU16(data, lookup_offset + 4);
191     const mark_filtering_set_offset = lookup_offset + 6 + @as(usize, subtable_count) * 2;
192     if ((flags & use_mark_filtering_set) == 0) {
193         return .{ .flags = flags };
194     }
195     if (!gposHasBytes(data, mark_filtering_set_offset, 2)) return error.InvalidGpos;
196     return .{
197         .flags = flags,
198         .mark_filtering_set = try gposReadU16(data, mark_filtering_set_offset),
199     };
200 }
201 
202 pub fn lookupGlyphIgnored(data: []const u8, gdef: ?Gdef, filter: LookupFilter, glyph_id: u16) FontError!bool {
203     const needs_gdef =
204         (filter.flags & (ignore_base_glyphs | ignore_ligatures | ignore_marks | mark_attachment_type)) != 0 or
205         filter.mark_filtering_set != null;
206     const definition = gdef orelse return needs_gdef;
207     const class = try definition.glyphClass(data, glyph_id);
208 
209     if (class == .base and (filter.flags & ignore_base_glyphs) != 0) return true;
210     if (class == .ligature and (filter.flags & ignore_ligatures) != 0) return true;
211     if (class != .mark) return false;
212     if ((filter.flags & ignore_marks) != 0) return true;
213 
214     if (filter.mark_filtering_set) |set_index| {
215         return !try definition.markGlyphSetContains(data, set_index, glyph_id);
216     }
217 
218     const attachment_type = (filter.flags & mark_attachment_type) >> 8;
219     if (attachment_type == 0) return false;
220     return try definition.markAttachmentClass(data, glyph_id) != attachment_type;
221 }
222 
223 pub fn scriptTagListContains(script_tags: []const Tag, wanted: Tag) bool {
224     for (script_tags) |script_tag| {
225         if (script_tag == wanted) return true;
226     }
227     return false;
228 }
229 
230 pub fn gsubOffset(data: []const u8, relative_offset: u16) FontError!usize {
231     return baseOffset(.gsub, data, relative_offset);
232 }
233 
234 pub fn gsubChildOffset(data: []const u8, base: usize, relative_offset: u16, min_len: usize) FontError!usize {
235     return childOffset(.gsub, data, base, relative_offset, min_len);
236 }
237 
238 pub fn gsubChildOffset32(data: []const u8, base: usize, relative_offset: u32, min_len: usize) FontError!usize {
239     return childOffset32(.gsub, data, base, relative_offset, min_len);
240 }
241 
242 pub fn gsubHasBytes(data: []const u8, offset_value: usize, len: usize) bool {
243     return hasBytes(.gsub, data, offset_value, len);
244 }
245 
246 pub fn gsubReadU16(data: []const u8, offset_value: usize) FontError!u16 {
247     return readU16(.gsub, data, offset_value);
248 }
249 
250 pub fn gsubReadI16(data: []const u8, offset_value: usize) FontError!i16 {
251     return readI16(.gsub, data, offset_value);
252 }
253 
254 pub fn gsubReadU32(data: []const u8, offset_value: usize) FontError!u32 {
255     return readU32(.gsub, data, offset_value);
256 }
257 
258 pub fn gposOffset(data: []const u8, relative_offset: u16) FontError!usize {
259     return baseOffset(.gpos, data, relative_offset);
260 }
261 
262 pub fn gposChildOffset(data: []const u8, base: usize, relative_offset: u16, min_len: usize) FontError!usize {
263     return childOffset(.gpos, data, base, relative_offset, min_len);
264 }
265 
266 pub fn gposChildOffset32(data: []const u8, base: usize, relative_offset: u32, min_len: usize) FontError!usize {
267     return childOffset32(.gpos, data, base, relative_offset, min_len);
268 }
269 
270 pub fn gposHasBytes(data: []const u8, offset_value: usize, len: usize) bool {
271     return hasBytes(.gpos, data, offset_value, len);
272 }
273 
274 pub fn gposReadU16(data: []const u8, offset_value: usize) FontError!u16 {
275     return readU16(.gpos, data, offset_value);
276 }
277 
278 pub fn gposReadI16(data: []const u8, offset_value: usize) FontError!i16 {
279     return readI16(.gpos, data, offset_value);
280 }
281 
282 pub fn gposReadU32(data: []const u8, offset_value: usize) FontError!u32 {
283     return readU32(.gpos, data, offset_value);
284 }