tiny.filigree.font.gsub
Defined in font.
API (8)
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Gsub.initGsub.lookupCountGsub.lookupIsReverseGsub.substitutionAtGsub.substitutionAtWithGdefGsub.usesArabicJoiningFormsGsub.usesAutomaticFractions
Source
Source: lib/filigree/src/font/gsub.zig
zig
const std = @import("std");const binary = @import("binary.zig");const gdef_table = @import("gdef.zig");const layout = @import("../layout/root.zig");const lookups = @import("lookups.zig");const model = @import("model.zig");const FontError = model.FontError;const FeatureRange = model.FeatureRange;const Gdef = gdef_table.Gdef;const GsubSubstitution = model.GsubSubstitution;const LayoutSelection = model.LayoutSelection;const Table = binary.Table;const Tag = model.Tag;const defaultScriptTag = model.defaultScriptTag;const featureEnabled = model.featureEnabled;const tagLiteral = model.tag;const featureTableContainsLookup = lookups.featureTableContainsLookup;const gsubChildOffset = lookups.gsubChildOffset;const gsubChildOffset32 = lookups.gsubChildOffset32;const gsubLookupFilter = lookups.gsubLookupFilter;const gsubOffset = lookups.gsubOffset;const hasGsubBytes = lookups.gsubHasBytes;const lookupGlyphIgnored = lookups.lookupGlyphIgnored;const lookupFilterSkipsGlyphs = lookups.lookupFilterSkipsGlyphs;const matchingFeatureSubstitutionOffset = lookups.matchingFeatureSubstitutionOffset;const readGsubI16 = lookups.gsubReadI16;const readGsubU16 = lookups.gsubReadU16;const readGsubU32 = lookups.gsubReadU32;const scriptTagListContains = lookups.scriptTagListContains;const substitutedFeatureOffset = lookups.substitutedFeatureOffset;const tag = model.tag;pub const Gsub = struct { table: Table, script_list_offset: usize, feature_list_offset: usize, lookup_list_offset: usize, feature_variations_offset: ?usize, pub const init = initImpl; pub const lookupCount = lookupCountImpl; pub const substitutionAt = substitutionAtImpl; pub const substitutionAtWithGdef = substitutionAtWithGdefImpl; const substitutionAtLookup = substitutionAtLookupImpl; const contextualSubstitution = contextualSubstitutionImpl; const contextualSubstitutionFormat1 = contextualSubstitutionFormat1Impl; const contextualSubstitutionFormat2 = contextualSubstitutionFormat2Impl; const contextualSubstitutionFormat3 = contextualSubstitutionFormat3Impl; const contextualSubstitutionRuleSet = contextualSubstitutionRuleSetImpl; const contextualSubstitutionClassSet = contextualSubstitutionClassSetImpl; const contextualSubstitutionClassRule = contextualSubstitutionClassRuleImpl; const contextualSubstitutionRule = contextualSubstitutionRuleImpl; const chainedContextualSubstitution = chainedContextualSubstitutionImpl; const chainedContextualSubstitutionFormat1 = chainedContextualSubstitutionFormat1Impl; const chainedContextualSubstitutionFormat2 = chainedContextualSubstitutionFormat2Impl; const chainedContextualSubstitutionFormat3 = chainedContextualSubstitutionFormat3Impl; const chainedContextualSubstitutionRuleSet = chainedContextualSubstitutionRuleSetImpl; const chainedContextualSubstitutionRule = chainedContextualSubstitutionRuleImpl; const chainedContextualSubstitutionClassSet = chainedContextualSubstitutionClassSetImpl; const chainedContextualSubstitutionClassRule = chainedContextualSubstitutionClassRuleImpl; const contextualLookupRecordSubstitution = contextualLookupRecordSubstitutionImpl; pub const lookupIsReverse = lookupIsReverseImpl; const lookupIsActive = lookupIsActiveImpl; const featureDefaultOn = featureDefaultOnImpl; const featureAllowsLookup = featureAllowsLookupImpl; const lookupResolvesOnlyToType = lookupResolvesOnlyToTypeImpl; const langSysHasFeature = langSysHasFeatureImpl; pub const usesAutomaticFractions = usesAutomaticFractionsImpl; pub const usesArabicJoiningForms = usesArabicJoiningFormsImpl; const featureTag = featureTagImpl; const featureContainsLookup = featureContainsLookupImpl; const featureOffset = featureOffsetImpl; const featureRecord = featureRecordImpl; const lookupOffset = lookupOffsetImpl;};fn initImpl(data: []const u8, gsub_table: Table) FontError!Gsub { const bytes = data[gsub_table.offset..][0..gsub_table.len]; if (bytes.len < 10) return error.InvalidGsub; const major = try readGsubU16(bytes, 0); const minor = try readGsubU16(bytes, 2); if (major != 1 or (minor != 0 and minor != 1)) return error.InvalidGsub; if (minor == 1 and bytes.len < 14) return error.InvalidGsub; const script_list_offset = try gsubOffset(bytes, try readGsubU16(bytes, 4)); const feature_list_offset = try gsubOffset(bytes, try readGsubU16(bytes, 6)); const lookup_list_offset = try gsubOffset(bytes, try readGsubU16(bytes, 8)); const feature_variations_offset = if (minor == 1) blk: { const relative = try readGsubU32(bytes, 10); if (relative == 0) break :blk null; break :blk try gsubChildOffset32(bytes, 0, relative, 8); } else null; return .{ .table = gsub_table, .script_list_offset = script_list_offset, .feature_list_offset = feature_list_offset, .lookup_list_offset = lookup_list_offset, .feature_variations_offset = feature_variations_offset, };}fn lookupCountImpl(self: Gsub, data: []const u8) FontError!u16 { const bytes = data[self.table.offset..][0..self.table.len]; return try readGsubU16(bytes, self.lookup_list_offset);}fn substitutionAtImpl(self: Gsub, data: []const u8, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { return try self.substitutionAtWithGdef(data, null, selection, null, lookup_index, glyph_ids, glyph_index, null);}fn substitutionAtWithGdefImpl(self: Gsub, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GsubSubstitution { if (glyph_index >= glyph_ids.len) return null; return try self.substitutionAtLookup(data, face, selection, gdef, lookup_index, glyph_ids, glyph_index, source, true, true);}fn substitutionAtLookupImpl(self: Gsub, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange, require_active: bool, allow_context: bool) FontError!?GsubSubstitution { if (glyph_index >= glyph_ids.len) return null; const bytes = data[self.table.offset..][0..self.table.len]; if (require_active and !try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null; const lookup_offset = try self.lookupOffset(bytes, lookup_index); const lookup_type = try readGsubU16(bytes, lookup_offset); const filter = try gsubLookupFilter(bytes, lookup_offset); if (lookup_type != 1 and lookup_type != 2 and lookup_type != 3 and lookup_type != 4 and lookup_type != 5 and lookup_type != 6 and lookup_type != 7 and lookup_type != 8) return null; if (glyph_ids[glyph_index] <= std.math.maxInt(u16)) { if (try lookupGlyphIgnored(data, gdef, filter, @intCast(glyph_ids[glyph_index]))) return null; } else if (lookupFilterSkipsGlyphs(filter)) { return null; } const subtable_count = try readGsubU16(bytes, lookup_offset + 4); if (!hasGsubBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGsub; for (0..subtable_count) |i| { const subtable_offset = try gsubChildOffset(bytes, lookup_offset, try readGsubU16(bytes, lookup_offset + 6 + i * 2), 2); const resolved = try resolveGsubSubtable(bytes, lookup_type, subtable_offset); switch (resolved.lookup_type) { 1 => { if (try singleSubstitution(bytes, resolved.subtable_offset, glyph_ids[glyph_index])) |substitute| { return .{ .glyph_id = substitute, .component_count = 1 }; } }, 2 => { if (try multipleSubstitution(bytes, self.table.offset, resolved.subtable_offset, glyph_ids[glyph_index])) |substitute| return substitute; }, 3 => { if (try alternateSubstitution(bytes, resolved.subtable_offset, glyph_ids[glyph_index])) |substitute| { return .{ .glyph_id = substitute, .component_count = 1 }; } }, 4 => { if (try ligatureSubstitution(bytes, resolved.subtable_offset, glyph_ids, glyph_index)) |substitute| return substitute; }, 5 => { if (!allow_context) return null; if (lookupFilterSkipsGlyphs(filter)) return null; if (try self.contextualSubstitution(data, bytes, selection, resolved.subtable_offset, glyph_ids, glyph_index)) |substitute| return substitute; }, 6 => { if (!allow_context) return null; if (lookupFilterSkipsGlyphs(filter)) return null; if (try self.chainedContextualSubstitution(data, bytes, selection, resolved.subtable_offset, glyph_ids, glyph_index)) |substitute| return substitute; }, 8 => { if (lookupFilterSkipsGlyphs(filter)) return null; if (try reverseChainingSingleSubstitution(bytes, resolved.subtable_offset, glyph_ids, glyph_index)) |substitute| { return .{ .glyph_id = substitute, .component_count = 1 }; } }, else => return null, } } return null;}fn contextualSubstitutionImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null; if (!hasGsubBytes(bytes, subtable_offset, 2)) return error.InvalidGsub; return switch (try readGsubU16(bytes, subtable_offset)) { 1 => self.contextualSubstitutionFormat1(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), 2 => self.contextualSubstitutionFormat2(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), 3 => self.contextualSubstitutionFormat3(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), else => error.InvalidGsub, };}fn contextualSubstitutionFormat1Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 8)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 2), 2); const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub) orelse return null; const rule_set_count = try readGsubU16(bytes, subtable_offset + 4); if (coverage_index >= rule_set_count) return error.InvalidGsub; const rule_set_offsets = subtable_offset + 6; if (!hasGsubBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGsub; const rule_set_relative = try readGsubU16(bytes, rule_set_offsets + @as(usize, coverage_index) * 2); if (rule_set_relative == 0) return null; const rule_set_offset = try gsubChildOffset(bytes, subtable_offset, rule_set_relative, 2); return try self.contextualSubstitutionRuleSet(data, bytes, selection, rule_set_offset, glyph_ids, glyph_index);}fn contextualSubstitutionFormat2Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 8)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 2), 2); const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGsub; if ((coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub) == null) return null; const class_def_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 4), 2); const class_def = layout.ClassDef.init(bytes, class_def_offset) catch return error.InvalidGsub; const rule_set_count = try readGsubU16(bytes, subtable_offset + 6); const rule_set_offsets = subtable_offset + 8; if (!hasGsubBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGsub; const first_class = class_def.class(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub; if (first_class >= rule_set_count) return null; const rule_set_relative = try readGsubU16(bytes, rule_set_offsets + @as(usize, first_class) * 2); if (rule_set_relative == 0) return null; const rule_set_offset = try gsubChildOffset(bytes, subtable_offset, rule_set_relative, 2); return try self.contextualSubstitutionClassSet(data, bytes, selection, class_def, rule_set_offset, glyph_ids, glyph_index);}fn contextualSubstitutionFormat3Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 6)) return error.InvalidGsub; const glyph_count = try readGsubU16(bytes, subtable_offset + 2); const lookup_count = try readGsubU16(bytes, subtable_offset + 4); if (glyph_count == 0) return error.InvalidGsub; if (@as(usize, glyph_count) > glyph_ids.len - glyph_index) return null; const coverage_offsets = subtable_offset + 6; if (!hasGsubBytes(bytes, coverage_offsets, @as(usize, glyph_count) * 2)) return error.InvalidGsub; for (0..glyph_count) |sequence_index| { const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, coverage_offsets + sequence_index * 2), 2); if (!try gsubGlyphInCoverage(bytes, coverage_offset, glyph_ids[glyph_index + sequence_index])) return null; } const lookup_records_offset = coverage_offsets + @as(usize, glyph_count) * 2; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualSubstitutionRuleSetImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const rule_count = try readGsubU16(bytes, rule_set_offset); const rule_offsets = rule_set_offset + 2; if (!hasGsubBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGsub; for (0..rule_count) |rule_index| { const rule_relative = try readGsubU16(bytes, rule_offsets + rule_index * 2); if (rule_relative == 0) continue; const rule_offset = try gsubChildOffset(bytes, rule_set_offset, rule_relative, 4); if (try self.contextualSubstitutionRule(data, bytes, selection, rule_offset, glyph_ids, glyph_index)) |substitute| return substitute; } return null;}fn contextualSubstitutionClassSetImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, class_def: layout.ClassDef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const rule_count = try readGsubU16(bytes, rule_set_offset); const rule_offsets = rule_set_offset + 2; if (!hasGsubBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGsub; for (0..rule_count) |rule_index| { const rule_relative = try readGsubU16(bytes, rule_offsets + rule_index * 2); if (rule_relative == 0) continue; const rule_offset = try gsubChildOffset(bytes, rule_set_offset, rule_relative, 4); if (try self.contextualSubstitutionClassRule(data, bytes, selection, class_def, rule_offset, glyph_ids, glyph_index)) |substitute| return substitute; } return null;}fn contextualSubstitutionClassRuleImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, class_def: layout.ClassDef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const glyph_count = try readGsubU16(bytes, rule_offset); const lookup_count = try readGsubU16(bytes, rule_offset + 2); if (glyph_count == 0) return error.InvalidGsub; if (@as(usize, glyph_count) > glyph_ids.len - glyph_index) return null; const input_sequence_offset = rule_offset + 4; const input_sequence_len = @as(usize, glyph_count - 1) * 2; if (!hasGsubBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGsub; for (1..glyph_count) |sequence_index| { const glyph = glyph_ids[glyph_index + sequence_index]; if (glyph > std.math.maxInt(u16)) return null; const expected_class = try readGsubU16(bytes, input_sequence_offset + (sequence_index - 1) * 2); const actual_class = class_def.class(bytes, glyph) catch return error.InvalidGsub; if (actual_class != expected_class) return null; } const lookup_records_offset = input_sequence_offset + input_sequence_len; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualSubstitutionRuleImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const glyph_count = try readGsubU16(bytes, rule_offset); const lookup_count = try readGsubU16(bytes, rule_offset + 2); if (glyph_count == 0) return error.InvalidGsub; if (@as(usize, glyph_count) > glyph_ids.len - glyph_index) return null; const input_sequence_offset = rule_offset + 4; const input_sequence_len = @as(usize, glyph_count - 1) * 2; if (!hasGsubBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGsub; for (1..glyph_count) |sequence_index| { const glyph = glyph_ids[glyph_index + sequence_index]; if (glyph > std.math.maxInt(u16) or glyph != try readGsubU16(bytes, input_sequence_offset + (sequence_index - 1) * 2)) return null; } const lookup_records_offset = input_sequence_offset + input_sequence_len; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn chainedContextualSubstitutionImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (glyph_index >= glyph_ids.len) return null; if (!hasGsubBytes(bytes, subtable_offset, 2)) return error.InvalidGsub; return switch (try readGsubU16(bytes, subtable_offset)) { 1 => self.chainedContextualSubstitutionFormat1(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), 2 => self.chainedContextualSubstitutionFormat2(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), 3 => self.chainedContextualSubstitutionFormat3(data, bytes, selection, subtable_offset, glyph_ids, glyph_index), else => error.InvalidGsub, };}fn chainedContextualSubstitutionFormat1Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 8)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 2), 2); const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub) orelse return null; const rule_set_count = try readGsubU16(bytes, subtable_offset + 4); if (coverage_index >= rule_set_count) return error.InvalidGsub; const rule_set_offsets = subtable_offset + 6; if (!hasGsubBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGsub; const rule_set_relative = try readGsubU16(bytes, rule_set_offsets + @as(usize, coverage_index) * 2); if (rule_set_relative == 0) return null; const rule_set_offset = try gsubChildOffset(bytes, subtable_offset, rule_set_relative, 2); return try self.chainedContextualSubstitutionRuleSet(data, bytes, selection, rule_set_offset, glyph_ids, glyph_index);}fn chainedContextualSubstitutionFormat2Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 12)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 2), 2); const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGsub; if ((coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub) == null) return null; const backtrack_class_def_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 4), 2); const input_class_def_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 6), 2); const lookahead_class_def_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, subtable_offset + 8), 2); const backtrack_class_def = layout.ClassDef.init(bytes, backtrack_class_def_offset) catch return error.InvalidGsub; const input_class_def = layout.ClassDef.init(bytes, input_class_def_offset) catch return error.InvalidGsub; const lookahead_class_def = layout.ClassDef.init(bytes, lookahead_class_def_offset) catch return error.InvalidGsub; const rule_set_count = try readGsubU16(bytes, subtable_offset + 10); const rule_set_offsets = subtable_offset + 12; if (!hasGsubBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGsub; const first_class = input_class_def.class(bytes, glyph_ids[glyph_index]) catch return error.InvalidGsub; if (first_class >= rule_set_count) return null; const rule_set_relative = try readGsubU16(bytes, rule_set_offsets + @as(usize, first_class) * 2); if (rule_set_relative == 0) return null; const rule_set_offset = try gsubChildOffset(bytes, subtable_offset, rule_set_relative, 2); return try self.chainedContextualSubstitutionClassSet( data, bytes, selection, backtrack_class_def, input_class_def, lookahead_class_def, rule_set_offset, glyph_ids, glyph_index, );}fn chainedContextualSubstitutionFormat3Impl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (!hasGsubBytes(bytes, subtable_offset, 4)) return error.InvalidGsub; const backtrack_count = try readGsubU16(bytes, subtable_offset + 2); const backtrack_offsets = subtable_offset + 4; if (!hasGsubBytes(bytes, backtrack_offsets, @as(usize, backtrack_count) * 2)) return error.InvalidGsub; if (@as(usize, backtrack_count) > glyph_index) return null; for (0..backtrack_count) |backtrack_index| { const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, backtrack_offsets + backtrack_index * 2), 2); const match_index = glyph_index - backtrack_index - 1; if (!try gsubGlyphInCoverage(bytes, coverage_offset, glyph_ids[match_index])) return null; } const input_count_offset = backtrack_offsets + @as(usize, backtrack_count) * 2; const input_count = try readGsubU16(bytes, input_count_offset); if (input_count == 0) return error.InvalidGsub; const input_offsets = input_count_offset + 2; if (!hasGsubBytes(bytes, input_offsets, @as(usize, input_count) * 2)) return error.InvalidGsub; if (@as(usize, input_count) > glyph_ids.len - glyph_index) return null; for (0..input_count) |input_index| { const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, input_offsets + input_index * 2), 2); if (!try gsubGlyphInCoverage(bytes, coverage_offset, glyph_ids[glyph_index + input_index])) return null; } const lookahead_count_offset = input_offsets + @as(usize, input_count) * 2; const lookahead_count = try readGsubU16(bytes, lookahead_count_offset); const lookahead_offsets = lookahead_count_offset + 2; if (!hasGsubBytes(bytes, lookahead_offsets, @as(usize, lookahead_count) * 2)) return error.InvalidGsub; const lookahead_start = glyph_index + @as(usize, input_count); if (@as(usize, lookahead_count) > glyph_ids.len - lookahead_start) return null; for (0..lookahead_count) |lookahead_index| { const coverage_offset = try gsubChildOffset(bytes, subtable_offset, try readGsubU16(bytes, lookahead_offsets + lookahead_index * 2), 2); if (!try gsubGlyphInCoverage(bytes, coverage_offset, glyph_ids[lookahead_start + lookahead_index])) return null; } const lookup_count_offset = lookahead_offsets + @as(usize, lookahead_count) * 2; const lookup_count = try readGsubU16(bytes, lookup_count_offset); const lookup_records_offset = lookup_count_offset + 2; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, input_count);}fn chainedContextualSubstitutionRuleSetImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const rule_count = try readGsubU16(bytes, rule_set_offset); const rule_offsets = rule_set_offset + 2; if (!hasGsubBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGsub; for (0..rule_count) |rule_index| { const rule_relative = try readGsubU16(bytes, rule_offsets + rule_index * 2); if (rule_relative == 0) continue; const rule_offset = try gsubChildOffset(bytes, rule_set_offset, rule_relative, 2); if (try self.chainedContextualSubstitutionRule(data, bytes, selection, rule_offset, glyph_ids, glyph_index)) |substitute| return substitute; } return null;}fn chainedContextualSubstitutionRuleImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { const backtrack_count = try readGsubU16(bytes, rule_offset); const backtrack_sequence_offset = rule_offset + 2; if (!hasGsubBytes(bytes, backtrack_sequence_offset, @as(usize, backtrack_count) * 2)) return error.InvalidGsub; if (@as(usize, backtrack_count) > glyph_index) return null; for (0..backtrack_count) |backtrack_index| { const glyph = glyph_ids[glyph_index - backtrack_index - 1]; if (glyph > std.math.maxInt(u16) or glyph != try readGsubU16(bytes, backtrack_sequence_offset + backtrack_index * 2)) return null; } const input_count_offset = backtrack_sequence_offset + @as(usize, backtrack_count) * 2; const input_count = try readGsubU16(bytes, input_count_offset); if (input_count == 0) return error.InvalidGsub; if (@as(usize, input_count) > glyph_ids.len - glyph_index) return null; const input_sequence_offset = input_count_offset + 2; const input_sequence_len = @as(usize, input_count - 1) * 2; if (!hasGsubBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGsub; for (1..input_count) |input_index| { const glyph = glyph_ids[glyph_index + input_index]; if (glyph > std.math.maxInt(u16) or glyph != try readGsubU16(bytes, input_sequence_offset + (input_index - 1) * 2)) return null; } const lookahead_count_offset = input_sequence_offset + input_sequence_len; const lookahead_count = try readGsubU16(bytes, lookahead_count_offset); const lookahead_sequence_offset = lookahead_count_offset + 2; if (!hasGsubBytes(bytes, lookahead_sequence_offset, @as(usize, lookahead_count) * 2)) return error.InvalidGsub; const lookahead_start = glyph_index + @as(usize, input_count); if (@as(usize, lookahead_count) > glyph_ids.len - lookahead_start) return null; for (0..lookahead_count) |lookahead_index| { const glyph = glyph_ids[lookahead_start + lookahead_index]; if (glyph > std.math.maxInt(u16) or glyph != try readGsubU16(bytes, lookahead_sequence_offset + lookahead_index * 2)) return null; } const lookup_count_offset = lookahead_sequence_offset + @as(usize, lookahead_count) * 2; const lookup_count = try readGsubU16(bytes, lookup_count_offset); const lookup_records_offset = lookup_count_offset + 2; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, input_count);}fn chainedContextualSubstitutionClassSetImpl( self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, backtrack_class_def: layout.ClassDef, input_class_def: layout.ClassDef, lookahead_class_def: layout.ClassDef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize,) FontError!?GsubSubstitution { const rule_count = try readGsubU16(bytes, rule_set_offset); const rule_offsets = rule_set_offset + 2; if (!hasGsubBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGsub; for (0..rule_count) |rule_index| { const rule_relative = try readGsubU16(bytes, rule_offsets + rule_index * 2); if (rule_relative == 0) continue; const rule_offset = try gsubChildOffset(bytes, rule_set_offset, rule_relative, 2); if (try self.chainedContextualSubstitutionClassRule( data, bytes, selection, backtrack_class_def, input_class_def, lookahead_class_def, rule_offset, glyph_ids, glyph_index, )) |substitute| return substitute; } return null;}fn chainedContextualSubstitutionClassRuleImpl( self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, backtrack_class_def: layout.ClassDef, input_class_def: layout.ClassDef, lookahead_class_def: layout.ClassDef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize,) FontError!?GsubSubstitution { const backtrack_count = try readGsubU16(bytes, rule_offset); const backtrack_sequence_offset = rule_offset + 2; if (!hasGsubBytes(bytes, backtrack_sequence_offset, @as(usize, backtrack_count) * 2)) return error.InvalidGsub; if (@as(usize, backtrack_count) > glyph_index) return null; for (0..backtrack_count) |backtrack_index| { const glyph = glyph_ids[glyph_index - backtrack_index - 1]; if (glyph > std.math.maxInt(u16)) return null; const expected_class = try readGsubU16(bytes, backtrack_sequence_offset + backtrack_index * 2); const actual_class = backtrack_class_def.class(bytes, glyph) catch return error.InvalidGsub; if (actual_class != expected_class) return null; } const input_count_offset = backtrack_sequence_offset + @as(usize, backtrack_count) * 2; const input_count = try readGsubU16(bytes, input_count_offset); if (input_count == 0) return error.InvalidGsub; if (@as(usize, input_count) > glyph_ids.len - glyph_index) return null; const input_sequence_offset = input_count_offset + 2; const input_sequence_len = @as(usize, input_count - 1) * 2; if (!hasGsubBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGsub; for (1..input_count) |input_index| { const glyph = glyph_ids[glyph_index + input_index]; if (glyph > std.math.maxInt(u16)) return null; const expected_class = try readGsubU16(bytes, input_sequence_offset + (input_index - 1) * 2); const actual_class = input_class_def.class(bytes, glyph) catch return error.InvalidGsub; if (actual_class != expected_class) return null; } const lookahead_count_offset = input_sequence_offset + input_sequence_len; const lookahead_count = try readGsubU16(bytes, lookahead_count_offset); const lookahead_sequence_offset = lookahead_count_offset + 2; if (!hasGsubBytes(bytes, lookahead_sequence_offset, @as(usize, lookahead_count) * 2)) return error.InvalidGsub; const lookahead_start = glyph_index + @as(usize, input_count); if (@as(usize, lookahead_count) > glyph_ids.len - lookahead_start) return null; for (0..lookahead_count) |lookahead_index| { const glyph = glyph_ids[lookahead_start + lookahead_index]; if (glyph > std.math.maxInt(u16)) return null; const expected_class = try readGsubU16(bytes, lookahead_sequence_offset + lookahead_index * 2); const actual_class = lookahead_class_def.class(bytes, glyph) catch return error.InvalidGsub; if (actual_class != expected_class) return null; } const lookup_count_offset = lookahead_sequence_offset + @as(usize, lookahead_count) * 2; const lookup_count = try readGsubU16(bytes, lookup_count_offset); const lookup_records_offset = lookup_count_offset + 2; if (!hasGsubBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGsub; return try self.contextualLookupRecordSubstitution(data, bytes, selection, lookup_records_offset, lookup_count, glyph_ids, glyph_index, input_count);}fn contextualLookupRecordSubstitutionImpl(self: Gsub, data: []const u8, bytes: []const u8, selection: LayoutSelection, lookup_records_offset: usize, lookup_count: u16, glyph_ids: []const u32, glyph_index: usize, input_count: u16) FontError!?GsubSubstitution { for (0..lookup_count) |lookup_record_index| { const record_offset = lookup_records_offset + lookup_record_index * 4; const sequence_index = try readGsubU16(bytes, record_offset); const nested_lookup_index = try readGsubU16(bytes, record_offset + 2); if (sequence_index >= input_count) return error.InvalidGsub; const nested_glyph_index = glyph_index + @as(usize, sequence_index); var substitution = (try self.substitutionAtLookup(data, null, selection, null, nested_lookup_index, glyph_ids, nested_glyph_index, null, false, false)) orelse continue; if (@as(usize, substitution.component_count) > @as(usize, input_count) - @as(usize, sequence_index)) return error.InvalidGsub; substitution.target_offset = @intCast(sequence_index); substitution.skip_count = @as(usize, input_count) - @as(usize, substitution.component_count) + @as(usize, substitution.replacement_count); return substitution; } return null;}fn lookupIsReverseImpl(self: Gsub, data: []const u8, selection: LayoutSelection, lookup_index: u16) FontError!bool { _ = selection; const bytes = data[self.table.offset..][0..self.table.len]; const lookup_offset = try self.lookupOffset(bytes, lookup_index); const lookup_type = try readGsubU16(bytes, lookup_offset); if (lookup_type == 8) return true; if (lookup_type != 7) return false; const subtable_count = try readGsubU16(bytes, lookup_offset + 4); if (!hasGsubBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGsub; for (0..subtable_count) |i| { const subtable_offset = try gsubChildOffset(bytes, lookup_offset, try readGsubU16(bytes, lookup_offset + 6 + i * 2), 2); const resolved = try resolveGsubSubtable(bytes, lookup_type, subtable_offset); if (resolved.lookup_type == 8) return true; } return false;}fn lookupIsActiveImpl(self: Gsub, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, lookup_index: u16, source: ?FeatureRange) FontError!bool { const langsys_offset = (try gsubLangSysOffset(bytes, self.script_list_offset, selection)) orelse return false; const required_feature_index = try readGsubU16(bytes, langsys_offset + 2); if (required_feature_index != 0xffff) { const feature_index = required_feature_index; const feature_tag = try self.featureTag(bytes, feature_index); if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) { if (try self.featureAllowsLookup(bytes, feature_tag, lookup_index)) return true; } } const feature_count = try readGsubU16(bytes, langsys_offset + 4); if (!hasGsubBytes(bytes, langsys_offset + 6, @as(usize, feature_count) * 2)) return error.InvalidGsub; for (0..feature_count) |i| { const feature_index = try readGsubU16(bytes, langsys_offset + 6 + i * 2); const feature_tag = try self.featureTag(bytes, feature_index); if (!gsubFeatureEnabled(selection, feature_tag, try self.featureDefaultOn(bytes, selection, feature_tag), source)) continue; if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) { if (try self.featureAllowsLookup(bytes, feature_tag, lookup_index)) return true; } } return false;}fn featureDefaultOnImpl(self: Gsub, bytes: []const u8, selection: LayoutSelection, feature_tag: Tag) FontError!bool { return switch (feature_tag) { tagLiteral("rlig"), tagLiteral("ccmp"), tagLiteral("rvrn"), tagLiteral("locl") => true, tagLiteral("liga"), tagLiteral("clig"), tagLiteral("calt"), tagLiteral("rclt") => !selection.vertical, tagLiteral("ltra"), tagLiteral("ltrm") => !selection.right_to_left, tagLiteral("rtla"), tagLiteral("rtlm") => selection.right_to_left, tagLiteral("vrt2") => selection.vertical, tagLiteral("vert") => selection.vertical and !(try self.langSysHasFeature(bytes, selection, tagLiteral("vrt2"))), else => false, };}fn featureAllowsLookupImpl(self: Gsub, bytes: []const u8, feature_tag: Tag, lookup_index: u16) FontError!bool { if (feature_tag != tagLiteral("rvrn")) return true; return try self.lookupResolvesOnlyToType(bytes, lookup_index, 1);}fn lookupResolvesOnlyToTypeImpl(self: Gsub, bytes: []const u8, lookup_index: u16, wanted_lookup_type: u16) FontError!bool { const lookup_offset = try self.lookupOffset(bytes, lookup_index); const lookup_type = try readGsubU16(bytes, lookup_offset); if (lookup_type == wanted_lookup_type) return true; if (lookup_type != 7) return false; const subtable_count = try readGsubU16(bytes, lookup_offset + 4); if (!hasGsubBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGsub; var saw_subtable = false; for (0..subtable_count) |i| { const subtable_offset = try gsubChildOffset(bytes, lookup_offset, try readGsubU16(bytes, lookup_offset + 6 + i * 2), 2); const resolved = try resolveGsubSubtable(bytes, lookup_type, subtable_offset); if (resolved.lookup_type != wanted_lookup_type) return false; saw_subtable = true; } return saw_subtable;}fn langSysHasFeatureImpl(self: Gsub, bytes: []const u8, selection: LayoutSelection, wanted_tag: Tag) FontError!bool { const langsys_offset = (try gsubLangSysOffset(bytes, self.script_list_offset, selection)) orelse return false; const required_feature_index = try readGsubU16(bytes, langsys_offset + 2); if (required_feature_index != 0xffff and (try self.featureTag(bytes, required_feature_index)) == wanted_tag) return true; const feature_count = try readGsubU16(bytes, langsys_offset + 4); if (!hasGsubBytes(bytes, langsys_offset + 6, @as(usize, feature_count) * 2)) return error.InvalidGsub; for (0..feature_count) |i| { const feature_index = try readGsubU16(bytes, langsys_offset + 6 + i * 2); if ((try self.featureTag(bytes, feature_index)) == wanted_tag) return true; } return false;}fn usesAutomaticFractionsImpl(self: Gsub, data: []const u8, selection: LayoutSelection) FontError!bool { const table_bytes = data[self.table.offset..][0..self.table.len]; if (try self.langSysHasFeature(table_bytes, selection, tagLiteral("frac"))) return true; return (try self.langSysHasFeature(table_bytes, selection, tagLiteral("numr"))) and (try self.langSysHasFeature(table_bytes, selection, tagLiteral("dnom")));}fn usesArabicJoiningFormsImpl(self: Gsub, data: []const u8, selection: LayoutSelection) FontError!bool { const table_bytes = data[self.table.offset..][0..self.table.len]; const feature_tags = [_]Tag{ tagLiteral("isol"), tagLiteral("fina"), tagLiteral("medi"), tagLiteral("init"), }; for (feature_tags) |feature_tag| { if (try self.langSysHasFeature(table_bytes, selection, feature_tag)) return true; } return false;}fn featureTagImpl(self: Gsub, bytes: []const u8, feature_index: u16) FontError!Tag { const feature_record = try self.featureRecord(bytes, feature_index); return try readGsubU32(bytes, feature_record);}fn featureContainsLookupImpl(self: Gsub, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, feature_index: u16, lookup_index: u16) FontError!bool { const feature_offset = try self.featureOffset(bytes, face, selection, feature_index); return try featureTableContainsLookup(.gsub, bytes, feature_offset, lookup_index);}fn featureOffsetImpl(self: Gsub, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, feature_index: u16) FontError!usize { if (try matchingFeatureSubstitutionOffset(.gsub, bytes, self.feature_variations_offset, face, selection)) |substitution_offset| { if (try substitutedFeatureOffset(.gsub, bytes, substitution_offset, feature_index)) |alternate_offset| return alternate_offset; } const feature_record = try self.featureRecord(bytes, feature_index); return try gsubChildOffset(bytes, self.feature_list_offset, try readGsubU16(bytes, feature_record + 4), 4);}fn featureRecordImpl(self: Gsub, bytes: []const u8, feature_index: u16) FontError!usize { const feature_count = try readGsubU16(bytes, self.feature_list_offset); if (feature_index >= feature_count) return error.InvalidGsub; const feature_record = self.feature_list_offset + 2 + @as(usize, feature_index) * 6; if (!hasGsubBytes(bytes, feature_record, 6)) return error.InvalidGsub; return feature_record;}fn lookupOffsetImpl(self: Gsub, bytes: []const u8, lookup_index: u16) FontError!usize { const lookup_count = try readGsubU16(bytes, self.lookup_list_offset); if (lookup_index >= lookup_count) return error.InvalidGsub; const lookup_record = self.lookup_list_offset + 2 + @as(usize, lookup_index) * 2; return try gsubChildOffset(bytes, self.lookup_list_offset, try readGsubU16(bytes, lookup_record), 6);}fn gsubFeatureEnabled(selection: LayoutSelection, feature_tag: Tag, default_on: bool, source: ?FeatureRange) bool { if (feature_tag == tagLiteral("rclt") and default_on) return true; return featureEnabled(selection, feature_tag, default_on, source);}fn gsubLangSysOffset(data: []const u8, script_list_offset: usize, selection: LayoutSelection) FontError!?usize { if (selection.script_tags.len != 0) { for (selection.script_tags) |script_tag| { if (try gsubLangSysOffsetForScript(data, script_list_offset, script_tag, selection.language)) |langsys_offset| return langsys_offset; } if (!scriptTagListContains(selection.script_tags, defaultScriptTag)) { return try gsubLangSysOffsetForScript(data, script_list_offset, defaultScriptTag, selection.language); } return null; } if (try gsubScriptOffset(data, script_list_offset, selection.script)) |script_offset| { return try gsubLangSysOffsetInScript(data, script_offset, selection.language); } if (selection.script != defaultScriptTag) { if (try gsubScriptOffset(data, script_list_offset, defaultScriptTag)) |script_offset| { return try gsubLangSysOffsetInScript(data, script_offset, selection.language); } } return null;}fn gsubLangSysOffsetForScript(data: []const u8, script_list_offset: usize, script_tag: Tag, language: ?Tag) FontError!?usize { const script_offset = (try gsubScriptOffset(data, script_list_offset, script_tag)) orelse return null; return try gsubLangSysOffsetInScript(data, script_offset, language);}fn gsubScriptOffset(data: []const u8, script_list_offset: usize, script_tag: Tag) FontError!?usize { const script_count = try readGsubU16(data, script_list_offset); if (!hasGsubBytes(data, script_list_offset + 2, @as(usize, script_count) * 6)) return error.InvalidGsub; for (0..script_count) |i| { const record = script_list_offset + 2 + i * 6; if (try readGsubU32(data, record) != script_tag) continue; return try gsubChildOffset(data, script_list_offset, try readGsubU16(data, record + 4), 4); } return null;}fn gsubLangSysOffsetInScript(data: []const u8, script_offset: usize, language: ?Tag) FontError!?usize { if (language) |language_tag| { const langsys_count = try readGsubU16(data, script_offset + 2); const records_offset = script_offset + 4; if (!hasGsubBytes(data, records_offset, @as(usize, langsys_count) * 6)) return error.InvalidGsub; for (0..langsys_count) |i| { const record = records_offset + i * 6; if (try readGsubU32(data, record) != language_tag) continue; return try gsubChildOffset(data, script_offset, try readGsubU16(data, record + 4), 6); } } const default_langsys_offset = try readGsubU16(data, script_offset); if (default_langsys_offset == 0) return null; return try gsubChildOffset(data, script_offset, default_langsys_offset, 6);}fn gsubGlyphInCoverage(data: []const u8, coverage_offset: usize, glyph_id: u32) FontError!bool { const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; return (coverage.index(data, glyph_id) catch return error.InvalidGsub) != null;}const ResolvedGsubSubtable = struct { lookup_type: u16, subtable_offset: usize,};fn resolveGsubSubtable(data: []const u8, lookup_type: u16, subtable_offset: usize) FontError!ResolvedGsubSubtable { if (lookup_type != 7) return .{ .lookup_type = lookup_type, .subtable_offset = subtable_offset }; if (!hasGsubBytes(data, subtable_offset, 8)) return error.InvalidGsub; if (try readGsubU16(data, subtable_offset) != 1) return error.InvalidGsub; const extension_lookup_type = try readGsubU16(data, subtable_offset + 2); if (extension_lookup_type == 7) return error.InvalidGsub; const extension_offset = try readGsubU32(data, subtable_offset + 4); return .{ .lookup_type = extension_lookup_type, .subtable_offset = try gsubChildOffset32(data, subtable_offset, extension_offset, 2), };}fn singleSubstitution(data: []const u8, subtable_offset: usize, glyph_id: u32) FontError!?u32 { if (glyph_id > std.math.maxInt(u16)) return null; if (!hasGsubBytes(data, subtable_offset, 2)) return error.InvalidGsub; return switch (try readGsubU16(data, subtable_offset)) { 1 => singleSubstitutionFormat1(data, subtable_offset, @intCast(glyph_id)), 2 => singleSubstitutionFormat2(data, subtable_offset, @intCast(glyph_id)), else => error.InvalidGsub, };}fn singleSubstitutionFormat1(data: []const u8, subtable_offset: usize, glyph_id: u16) FontError!?u32 { if (!hasGsubBytes(data, subtable_offset, 6)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; if ((coverage.index(data, glyph_id) catch return error.InvalidGsub) == null) return null; const delta = try readGsubI16(data, subtable_offset + 4); return @intCast(@mod(@as(i32, glyph_id) + @as(i32, delta), 65536));}fn singleSubstitutionFormat2(data: []const u8, subtable_offset: usize, glyph_id: u16) FontError!?u32 { if (!hasGsubBytes(data, subtable_offset, 6)) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const glyph_count = try readGsubU16(data, subtable_offset + 4); const substitute_offset = subtable_offset + 6; if (!hasGsubBytes(data, substitute_offset, @as(usize, glyph_count) * 2)) return error.InvalidGsub; const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(data, glyph_id) catch return error.InvalidGsub) orelse return null; if (coverage_index >= glyph_count) return error.InvalidGsub; return try readGsubU16(data, substitute_offset + @as(usize, coverage_index) * 2);}fn multipleSubstitution(data: []const u8, table_offset: usize, subtable_offset: usize, glyph_id: u32) FontError!?GsubSubstitution { if (glyph_id > std.math.maxInt(u16)) return null; if (!hasGsubBytes(data, subtable_offset, 6)) return error.InvalidGsub; if (try readGsubU16(data, subtable_offset) != 1) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(data, glyph_id) catch return error.InvalidGsub) orelse return null; const sequence_count = try readGsubU16(data, subtable_offset + 4); if (coverage_index >= sequence_count) return error.InvalidGsub; const sequence_offsets = subtable_offset + 6; if (!hasGsubBytes(data, sequence_offsets, @as(usize, sequence_count) * 2)) return error.InvalidGsub; const sequence_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, sequence_offsets + @as(usize, coverage_index) * 2), 2); const glyph_count = try readGsubU16(data, sequence_offset); if (glyph_count == 0) return error.InvalidGsub; const substitute_offset = sequence_offset + 2; if (!hasGsubBytes(data, substitute_offset, @as(usize, glyph_count) * 2)) return error.InvalidGsub; if (table_offset > std.math.maxInt(usize) - substitute_offset) return error.InvalidGsub; return .{ .glyph_id = try readGsubU16(data, substitute_offset), .component_count = 1, .replacement_count = glyph_count, .replacement_glyphs_offset = table_offset + substitute_offset, };}fn alternateSubstitution(data: []const u8, subtable_offset: usize, glyph_id: u32) FontError!?u32 { if (glyph_id > std.math.maxInt(u16)) return null; if (!hasGsubBytes(data, subtable_offset, 6)) return error.InvalidGsub; if (try readGsubU16(data, subtable_offset) != 1) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(data, glyph_id) catch return error.InvalidGsub) orelse return null; const alternate_set_count = try readGsubU16(data, subtable_offset + 4); if (coverage_index >= alternate_set_count) return error.InvalidGsub; const alternate_set_offsets = subtable_offset + 6; if (!hasGsubBytes(data, alternate_set_offsets, @as(usize, alternate_set_count) * 2)) return error.InvalidGsub; const alternate_set_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, alternate_set_offsets + @as(usize, coverage_index) * 2), 2); const glyph_count = try readGsubU16(data, alternate_set_offset); if (glyph_count == 0) return error.InvalidGsub; if (!hasGsubBytes(data, alternate_set_offset + 2, @as(usize, glyph_count) * 2)) return error.InvalidGsub; return try readGsubU16(data, alternate_set_offset + 2);}fn ligatureSubstitution(data: []const u8, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution { if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null; if (!hasGsubBytes(data, subtable_offset, 8)) return error.InvalidGsub; if (try readGsubU16(data, subtable_offset) != 1) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(data, glyph_ids[glyph_index]) catch return error.InvalidGsub) orelse return null; const ligature_set_count = try readGsubU16(data, subtable_offset + 4); if (coverage_index >= ligature_set_count) return error.InvalidGsub; const ligature_set_offsets = subtable_offset + 6; if (!hasGsubBytes(data, ligature_set_offsets, @as(usize, ligature_set_count) * 2)) return error.InvalidGsub; const ligature_set_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, ligature_set_offsets + @as(usize, coverage_index) * 2), 2); const ligature_count = try readGsubU16(data, ligature_set_offset); if (!hasGsubBytes(data, ligature_set_offset + 2, @as(usize, ligature_count) * 2)) return error.InvalidGsub; for (0..ligature_count) |i| { const ligature_offset = try gsubChildOffset(data, ligature_set_offset, try readGsubU16(data, ligature_set_offset + 2 + i * 2), 4); const ligature_glyph = try readGsubU16(data, ligature_offset); const component_count = try readGsubU16(data, ligature_offset + 2); if (component_count < 2) return error.InvalidGsub; if (glyph_index + @as(usize, component_count) > glyph_ids.len) continue; const component_offset = ligature_offset + 4; if (!hasGsubBytes(data, component_offset, @as(usize, component_count - 1) * 2)) return error.InvalidGsub; var matched = true; for (1..component_count) |component_index| { const glyph = glyph_ids[glyph_index + component_index]; if (glyph > std.math.maxInt(u16) or glyph != try readGsubU16(data, component_offset + (component_index - 1) * 2)) { matched = false; break; } } if (matched) { return .{ .glyph_id = ligature_glyph, .component_count = component_count }; } } return null;}fn reverseChainingSingleSubstitution(data: []const u8, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?u32 { if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null; if (!hasGsubBytes(data, subtable_offset, 6)) return error.InvalidGsub; if (try readGsubU16(data, subtable_offset) != 1) return error.InvalidGsub; const coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, subtable_offset + 2), 2); const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; const coverage_index = (coverage.index(data, glyph_ids[glyph_index]) catch return error.InvalidGsub) orelse return null; const backtrack_count = try readGsubU16(data, subtable_offset + 4); const backtrack_offsets = subtable_offset + 6; if (!hasGsubBytes(data, backtrack_offsets, @as(usize, backtrack_count) * 2)) return error.InvalidGsub; if (glyph_index < @as(usize, backtrack_count)) return null; for (0..backtrack_count) |i| { const backtrack_coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, backtrack_offsets + i * 2), 2); if (!try coverageContainsGlyph(data, backtrack_coverage_offset, glyph_ids[glyph_index - 1 - i])) return null; } const lookahead_count_offset = backtrack_offsets + @as(usize, backtrack_count) * 2; if (!hasGsubBytes(data, lookahead_count_offset, 2)) return error.InvalidGsub; const lookahead_count = try readGsubU16(data, lookahead_count_offset); const lookahead_offsets = lookahead_count_offset + 2; if (!hasGsubBytes(data, lookahead_offsets, @as(usize, lookahead_count) * 2)) return error.InvalidGsub; if (@as(usize, lookahead_count) > glyph_ids.len - glyph_index - 1) return null; for (0..lookahead_count) |i| { const lookahead_coverage_offset = try gsubChildOffset(data, subtable_offset, try readGsubU16(data, lookahead_offsets + i * 2), 2); if (!try coverageContainsGlyph(data, lookahead_coverage_offset, glyph_ids[glyph_index + 1 + i])) return null; } const glyph_count_offset = lookahead_offsets + @as(usize, lookahead_count) * 2; if (!hasGsubBytes(data, glyph_count_offset, 2)) return error.InvalidGsub; const glyph_count = try readGsubU16(data, glyph_count_offset); if (coverage_index >= glyph_count) return error.InvalidGsub; const substitute_offset = glyph_count_offset + 2; if (!hasGsubBytes(data, substitute_offset, @as(usize, glyph_count) * 2)) return error.InvalidGsub; return try readGsubU16(data, substitute_offset + @as(usize, coverage_index) * 2);}fn coverageContainsGlyph(data: []const u8, coverage_offset: usize, glyph_id: u32) FontError!bool { if (glyph_id > std.math.maxInt(u16)) return false; const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGsub; return (coverage.index(data, glyph_id) catch return error.InvalidGsub) != null;}test "GSUB extension substitution dispatches wrapped single substitutions" { const data = [_]u8{ 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 'D', 'F', 'L', 'T', 0, 8, 0, 4, 0, 0, 0, 0, 0xff, 0xff, 0, 1, 0, 0, 0, 1, 'l', 'i', 'g', 'a', 0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 7, 0, 0, 0, 1, 0, 8, 0, 1, 0, 1, 0, 0, 0, 8, 0, 1, 0, 6, 0, 4, 0, 1, 0, 1, 0, 1, }; const gsub = try Gsub.init(&data, .{ .offset = 0, .len = data.len }); const glyphs = [_]u32{1}; const substitution = (try gsub.substitutionAt(&data, .{}, 0, &glyphs, 0)).?; try std.testing.expectEqual(@as(u32, 5), substitution.glyph_id); try std.testing.expectEqual(@as(u16, 1), substitution.component_count);}test "GSUB reverse chaining lookup dispatches contextual single substitutions" { const data = [_]u8{ 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 'D', 'F', 'L', 'T', 0, 8, 0, 4, 0, 0, 0, 0, 0xff, 0xff, 0, 1, 0, 0, 0, 1, 'r', 'l', 'i', 'g', 0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 8, 0, 0, 0, 1, 0, 8, 0, 1, 0, 14, 0, 0, 0, 1, 0, 20, 0, 1, 0, 'B' - 31, 0, 1, 0, 1, 0, 'A' - 31, 0, 1, 0, 1, 0, 'A' - 31, }; const gsub = try Gsub.init(&data, .{ .offset = 0, .len = data.len }); const glyphs = [_]u32{ 'A' - 31, 'A' - 31, 'A' - 31 }; const substitution = (try gsub.substitutionAt(&data, .{}, 0, &glyphs, 1)).?; try std.testing.expect(try gsub.lookupIsReverse(&data, .{}, 0)); try std.testing.expectEqual(@as(u32, 'B' - 31), substitution.glyph_id); try std.testing.expectEqual(@as(u16, 1), substitution.component_count); try std.testing.expectEqual(@as(?GsubSubstitution, null), try gsub.substitutionAt(&data, .{}, 0, &glyphs, 2));}test "GSUB offsets stay inside table data" { const data = @as([10]u8, @splat(0)); try std.testing.expectEqual(@as(usize, 8), try gsubOffset(&data, 8)); try std.testing.expectError(error.InvalidGsub, gsubOffset(&data, 0)); try std.testing.expectError(error.InvalidGsub, gsubOffset(&data, 9)); try std.testing.expectError(error.InvalidGsub, gsubOffset(&data, 10));}test "GSUB child offsets stay inside table data" { const data = @as([10]u8, @splat(0)); try std.testing.expectEqual(@as(usize, 6), try gsubChildOffset(&data, 2, 4, 2)); try std.testing.expectError(error.InvalidGsub, gsubChildOffset(&data, 2, 0, 2)); try std.testing.expectError(error.InvalidGsub, gsubChildOffset(&data, 10, 1, 2)); try std.testing.expectError(error.InvalidGsub, gsubChildOffset(&data, 8, 1, 2));}test "GSUB layout selection resolves language system tags" { const data = [_]u8{ 0, 1, 'l', 'a', 't', 'n', 0, 8, 0, 18, 0, 1, 'T', 'R', 'K', ' ', 0, 10, 0, 0, 0xff, 0xff, 0, 1, 0, 7, 0, 0, 0xff, 0xff, 0, 1, 0, 3, }; try std.testing.expectEqual(@as(?usize, 18), try gsubLangSysOffset(&data, 0, .{ .script = tag("latn"), .language = tag("TRK ") })); try std.testing.expectEqual(@as(?usize, 26), try gsubLangSysOffset(&data, 0, .{ .script = tag("latn"), .language = tag("DEU ") })); try std.testing.expectEqual(@as(?usize, null), try gsubLangSysOffset(&data, 0, .{ .script = tag("cyrl"), .language = tag("TRK ") }));}test "GSUB single substitution format 1 applies signed delta" { const data = [_]u8{ 0, 1, 0, 6, 0, 2, 0, 1, 0, 1, 0, 5, }; try std.testing.expectEqual(@as(?u32, 7), try singleSubstitution(&data, 0, 5)); try std.testing.expectEqual(@as(?u32, null), try singleSubstitution(&data, 0, 4));}test "GSUB single substitution format 1 wraps negative delta" { const data = [_]u8{ 0, 1, 0, 6, 0xff, 0xfe, 0, 1, 0, 1, 0, 1, }; try std.testing.expectEqual(@as(?u32, 65535), try singleSubstitution(&data, 0, 1));}test "GSUB single substitution format 2 uses coverage index" { const data = [_]u8{ 0, 2, 0, 10, 0, 2, 0, 70, 0, 80, 0, 1, 0, 2, 0, 5, 0, 9, }; try std.testing.expectEqual(@as(?u32, 70), try singleSubstitution(&data, 0, 5)); try std.testing.expectEqual(@as(?u32, 80), try singleSubstitution(&data, 0, 9)); try std.testing.expectEqual(@as(?u32, null), try singleSubstitution(&data, 0, 6));}test "GSUB multiple substitution format 1 uses sequence table" { const data = [_]u8{ 0, 1, 0, 14, 0, 1, 0, 8, 0, 2, 0, 'X' - 31, 0, 'Y' - 31, 0, 1, 0, 1, 0, 'A' - 31, }; const substitution = (try multipleSubstitution(&data, 0, 0, 'A' - 31)).?; try std.testing.expectEqual(@as(u32, 'X' - 31), substitution.glyph_id); try std.testing.expectEqual(@as(u16, 1), substitution.component_count); try std.testing.expectEqual(@as(u16, 2), substitution.replacement_count); try std.testing.expectEqual(@as(usize, 10), substitution.replacement_glyphs_offset); try std.testing.expectEqual(@as(?GsubSubstitution, null), try multipleSubstitution(&data, 0, 0, 'B' - 31));}test "GSUB multiple substitution rejects deletion sequences" { const data = [_]u8{ 0, 1, 0, 10, 0, 1, 0, 8, 0, 0, 0, 1, 0, 1, 0, 'A' - 31, }; try std.testing.expectError(error.InvalidGsub, multipleSubstitution(&data, 0, 0, 'A' - 31));}test "GSUB ligature substitution format 1 uses preferred ligature set order" { const data = [_]u8{ 0, 1, 0, 18, 0, 1, 0, 8, 0, 1, 0, 4, 0, 96, 0, 2, 0, 'i' - 31, 0, 1, 0, 1, 0, 'f' - 31, }; const glyphs = [_]u32{ 'f' - 31, 'i' - 31, '!' - 31 }; const substitution = (try ligatureSubstitution(&data, 0, &glyphs, 0)).?; try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id); try std.testing.expectEqual(@as(u16, 2), substitution.component_count); try std.testing.expectEqual(@as(?GsubSubstitution, null), try ligatureSubstitution(&data, 0, &glyphs, 1));}test "GSUB reverse chaining single substitution format 1 uses context coverage" { const data = [_]u8{ 0, 1, 0, 16, 0, 1, 0, 22, 0, 1, 0, 28, 0, 1, 0, 20, 0, 1, 0, 1, 0, 2, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 3, }; const glyphs = [_]u32{ 1, 2, 3 }; const mismatch = [_]u32{ 3, 2, 1 }; try std.testing.expectEqual(@as(?u32, 20), try reverseChainingSingleSubstitution(&data, 0, &glyphs, 1)); try std.testing.expectEqual(@as(?u32, null), try reverseChainingSingleSubstitution(&data, 0, &glyphs, 0)); try std.testing.expectEqual(@as(?u32, null), try reverseChainingSingleSubstitution(&data, 0, &mismatch, 1));}Source: lib/filigree/src/font/root.zig:9
zig
pub const gsub = @import("gsub.zig");Audit
| Definitions | 9 |
|---|---|
| Public names | 9 |
| Members | 5 |
| Version | 26.7.0 |
| Revision | daab053ee433 |