Skip to documentation
SLOP

tiny.filigree.font.gpos

Reference tiny.filigree font gpos

Defined in font.

API (23)

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callsfontgpos
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/filigree/src/font/gpos.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 GlyphClass = model.GlyphClass;const GposContextualPairAdjustment = model.GposContextualPairAdjustment;const GposCursiveAttachment = model.GposCursiveAttachment;const GposPairAdjustment = model.GposPairAdjustment;const GposPairAdjustmentBefore = model.GposPairAdjustmentBefore;const GposSingleAdjustment = model.GposSingleAdjustment;const GposValueAdjustment = model.GposValueAdjustment;const LayoutSelection = model.LayoutSelection;const LookupFilter = lookups.LookupFilter;const Table = binary.Table;const Tag = model.Tag;const VariationSetting = model.VariationSetting;const defaultScriptTag = model.defaultScriptTag;const featureEnabled = model.featureEnabled;const noGposVariationIndex = model.noGposVariationIndex;const tagLiteral = model.tag;const featureTableContainsLookup = lookups.featureTableContainsLookup;const gposChildOffset = lookups.gposChildOffset;const gposChildOffset32 = lookups.gposChildOffset32;const gposLookupFilter = lookups.gposLookupFilter;const gposOffset = lookups.gposOffset;const hasGposBytes = lookups.gposHasBytes;const lookupGlyphIgnored = lookups.lookupGlyphIgnored;const lookupFilterSkipsGlyphs = lookups.lookupFilterSkipsGlyphs;const matchingFeatureSubstitutionOffset = lookups.matchingFeatureSubstitutionOffset;const previousLookupGlyphIndex = previousLookupGlyphIndexLocal;const readGposI16 = lookups.gposReadI16;const readGposU16 = lookups.gposReadU16;const readGposU32 = lookups.gposReadU32;const scriptTagListContains = lookups.scriptTagListContains;const substitutedFeatureOffset = lookups.substitutedFeatureOffset;const tag = model.tag;const ChainedContextMatch = struct {    lookup_records_offset: usize,    lookup_count: u16,    input_count: u16,};fn featureMayBeEnabled(selection: LayoutSelection, feature_tag: Tag, default_on: bool) bool {    var globally_enabled = default_on;    var ranged_enabled_after_global = false;    for (selection.features) |setting| {        if (setting.tag != feature_tag) continue;        if (setting.source == null) {            globally_enabled = setting.value != 0;            ranged_enabled_after_global = false;            continue;        }        if (setting.value != 0) ranged_enabled_after_global = true;    }    return globally_enabled or ranged_enabled_after_global;}fn gposFeatureDefaultOn(selection: LayoutSelection, feature_tag: Tag) bool {    return switch (feature_tag) {        tagLiteral("kern") => !selection.vertical,        tagLiteral("vkrn") => selection.vertical,        tagLiteral("dist"), tagLiteral("curs") => !selection.vertical,        tagLiteral("mark"), tagLiteral("mkmk"), tagLiteral("abvm"), tagLiteral("blwm") => true,        else => false,    };}pub const Gpos = 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 lookupResolvedType = lookupResolvedTypeImpl;    pub const pairAdjustment = pairAdjustmentImpl;    pub const pairAdjustmentBefore = pairAdjustmentBeforeImpl;    pub const pairAdjustmentBeforeAtLookup = pairAdjustmentBeforeAtLookupImpl;    pub const singleAdjustment = singleAdjustmentImpl;    pub const singleAdjustmentAt = singleAdjustmentAtImpl;    pub const singleAdjustmentAtLookup = singleAdjustmentAtLookupImpl;    pub const cursiveAttachment = cursiveAttachmentImpl;    pub const cursiveAttachmentAtLookup = cursiveAttachmentAtLookupImpl;    pub const markToBaseAdjustment = markToBaseAdjustmentImpl;    pub const markToBaseAdjustmentAtLookup = markToBaseAdjustmentAtLookupImpl;    pub const markToLigatureAdjustment = markToLigatureAdjustmentImpl;    pub const markToLigatureAdjustmentAtLookup = markToLigatureAdjustmentAtLookupImpl;    pub const markToMarkAdjustment = markToMarkAdjustmentImpl;    pub const markToMarkAdjustmentAtLookup = markToMarkAdjustmentAtLookupImpl;    pub const hasActiveLookupType = hasActiveLookupTypeImpl;    pub const hasActiveSingleAdjustment = hasActiveSingleAdjustmentImpl;    pub const hasActiveContextualPairAdjustment = hasActiveContextualPairAdjustmentImpl;    const lookupMayBeActive = lookupMayBeActiveImpl;    const lookupHasResolvedType = lookupHasResolvedTypeImpl;    const lookupPairAdjustment = lookupPairAdjustmentImpl;    const lookupPairAdjustmentBefore = lookupPairAdjustmentBeforeImpl;    pub const contextualPairAdjustmentAt = contextualPairAdjustmentAtImpl;    pub const contextualPairAdjustmentAtLookup = contextualPairAdjustmentAtLookupImpl;    const lookupContextualPairAdjustmentAt = lookupContextualPairAdjustmentAtImpl;    const lookupNestedPairAdjustment = lookupNestedPairAdjustmentImpl;    const contextualPairAdjustment = contextualPairAdjustmentImpl;    const contextualPairAdjustmentFormat1 = contextualPairAdjustmentFormat1Impl;    const contextualPairAdjustmentFormat2 = contextualPairAdjustmentFormat2Impl;    const contextualPairAdjustmentFormat3 = contextualPairAdjustmentFormat3Impl;    const chainedContextualPairAdjustment = chainedContextualPairAdjustmentImpl;    const chainedContextualPairAdjustmentFormat1 = chainedContextualPairAdjustmentFormat1Impl;    const chainedContextualPairAdjustmentFormat2 = chainedContextualPairAdjustmentFormat2Impl;    const chainedContextualPairAdjustmentFormat3 = chainedContextualPairAdjustmentFormat3Impl;    const chainedContextualMatchFormat1 = chainedContextualMatchFormat1Impl;    const chainedContextualMatchFormat2 = chainedContextualMatchFormat2Impl;    const chainedContextualRuleSetMatch = chainedContextualRuleSetMatchImpl;    const chainedContextualRuleMatch = chainedContextualRuleMatchImpl;    const chainedContextualClassSetMatch = chainedContextualClassSetMatchImpl;    const chainedContextualClassRuleMatch = chainedContextualClassRuleMatchImpl;    const contextualPairAdjustmentRuleSet = contextualPairAdjustmentRuleSetImpl;    const contextualPairAdjustmentClassSet = contextualPairAdjustmentClassSetImpl;    const contextualPairAdjustmentClassRule = contextualPairAdjustmentClassRuleImpl;    const contextualPairAdjustmentRule = contextualPairAdjustmentRuleImpl;    const contextualPairAdjustmentRecords = contextualPairAdjustmentRecordsImpl;    const lookupSingleAdjustment = lookupSingleAdjustmentImpl;    const lookupSingleAdjustmentAt = lookupSingleAdjustmentAtImpl;    const contextualSingleAdjustment = contextualSingleAdjustmentImpl;    const contextualSingleAdjustmentFormat1 = contextualSingleAdjustmentFormat1Impl;    const contextualSingleAdjustmentFormat2 = contextualSingleAdjustmentFormat2Impl;    const contextualSingleAdjustmentFormat3 = contextualSingleAdjustmentFormat3Impl;    const chainedContextualSingleAdjustment = chainedContextualSingleAdjustmentImpl;    const chainedContextualSingleAdjustmentFormat1 = chainedContextualSingleAdjustmentFormat1Impl;    const chainedContextualSingleAdjustmentFormat2 = chainedContextualSingleAdjustmentFormat2Impl;    const chainedContextualSingleAdjustmentFormat3 = chainedContextualSingleAdjustmentFormat3Impl;    const contextualSingleAdjustmentRuleSet = contextualSingleAdjustmentRuleSetImpl;    const contextualSingleAdjustmentClassSet = contextualSingleAdjustmentClassSetImpl;    const contextualSingleAdjustmentClassRule = contextualSingleAdjustmentClassRuleImpl;    const contextualSingleAdjustmentRule = contextualSingleAdjustmentRuleImpl;    const contextualSingleAdjustmentRecords = contextualSingleAdjustmentRecordsImpl;    const lookupCursiveAttachment = lookupCursiveAttachmentImpl;    const lookupMarkToBaseAdjustment = lookupMarkToBaseAdjustmentImpl;    const lookupMarkToLigatureAdjustment = lookupMarkToLigatureAdjustmentImpl;    const lookupMarkToMarkAdjustment = lookupMarkToMarkAdjustmentImpl;    const lookupOffset = lookupOffsetImpl;    const lookupIsActive = lookupIsActiveImpl;    const featureTag = featureTagImpl;    const featureContainsLookup = featureContainsLookupImpl;    const featureOffset = featureOffsetImpl;    const featureRecord = featureRecordImpl;};fn initImpl(data: []const u8, gpos_table: Table) FontError!Gpos {    const bytes = data[gpos_table.offset..][0..gpos_table.len];    if (bytes.len < 10) return error.InvalidGpos;    const major = try readGposU16(bytes, 0);    const minor = try readGposU16(bytes, 2);    if (major != 1 or (minor != 0 and minor != 1)) return error.InvalidGpos;    if (minor == 1 and bytes.len < 14) return error.InvalidGpos;    const script_list_offset = try gposOffset(bytes, try readGposU16(bytes, 4));    const feature_list_offset = try gposOffset(bytes, try readGposU16(bytes, 6));    const lookup_list_offset = try gposOffset(bytes, try readGposU16(bytes, 8));    const feature_variations_offset = if (minor == 1) blk: {        const relative = try readGposU32(bytes, 10);        if (relative == 0) break :blk null;        break :blk try gposChildOffset32(bytes, 0, relative, 8);    } else null;    return .{        .table = gpos_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: Gpos, data: []const u8) FontError!u16 {    const bytes = data[self.table.offset..][0..self.table.len];    return try readGposU16(bytes, self.lookup_list_offset);}fn lookupResolvedTypeImpl(self: Gpos, data: []const u8, lookup_index: u16) FontError!u16 {    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    if (lookup_type != 9) return lookup_type;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 8);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        return resolved.lookup_type;    }    return 0;}fn pairAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, left_glyph: u32, right_glyph: u32) FontError!GposPairAdjustment {    var result = GposPairAdjustment{};    if (left_glyph > std.math.maxInt(u16) or right_glyph > std.math.maxInt(u16)) return result;    const left: u16 = @intCast(left_glyph);    const right: u16 = @intCast(right_glyph);    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, null)) continue;        if (try self.lookupPairAdjustment(data, bytes, gdef, index, left, right)) |adjustment| {            result.add(adjustment);        }    }    return result;}fn pairAdjustmentBeforeImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposPairAdjustmentBefore {    if (glyph_index == 0 or glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var result = GposPairAdjustment{};    var result_left_offset: usize = 0;    var found = false;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, source)) continue;        if (try self.lookupPairAdjustmentBefore(data, bytes, gdef, index, glyph_ids, glyph_index)) |adjustment| {            if (found and adjustment.left_offset != result_left_offset) {                if (!result.isZero()) return .{ .adjustment = result, .left_offset = result_left_offset };                result = adjustment.adjustment;                result_left_offset = adjustment.left_offset;                found = true;                continue;            }            result.add(adjustment.adjustment);            result_left_offset = adjustment.left_offset;            found = true;        }    }    if (!found) return null;    return .{ .adjustment = result, .left_offset = result_left_offset };}fn pairAdjustmentBeforeAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposPairAdjustmentBefore {    if (glyph_index == 0 or glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    return try self.lookupPairAdjustmentBefore(data, bytes, gdef, lookup_index, glyph_ids, glyph_index);}fn singleAdjustmentImpl(self: Gpos, data: []const u8, selection: LayoutSelection, gdef: ?Gdef, glyph_id: u32) FontError!GposValueAdjustment {    var glyphs = [_]u32{glyph_id};    const result = (try self.singleAdjustmentAt(data, null, selection, gdef, &glyphs, 0, null)) orelse return .{};    if (result.target_offset != 0) return .{};    return result.adjustment;}fn singleAdjustmentAtImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposSingleAdjustment {    var result = GposValueAdjustment{};    if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var found = false;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (try self.lookupSingleAdjustmentAt(data, bytes, face, selection, gdef, index, glyph_ids, glyph_index, source, true, true)) |adjustment| {            if (adjustment.target_offset != 0 or adjustment.skip_count != 0) {                if (found and !result.isZero()) return .{ .adjustment = result };                return adjustment;            }            result.add(adjustment.adjustment);            found = true;        }    }    if (!found) return null;    return .{ .adjustment = result };}fn singleAdjustmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposSingleAdjustment {    if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    return try self.lookupSingleAdjustmentAt(data, bytes, face, selection, gdef, lookup_index, glyph_ids, glyph_index, source, true, true);}fn cursiveAttachmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, exit_glyph: u32, entry_glyph: u32, source: ?FeatureRange) FontError!?GposCursiveAttachment {    if (exit_glyph > std.math.maxInt(u16) or entry_glyph > std.math.maxInt(u16)) return null;    const exit: u16 = @intCast(exit_glyph);    const entry: u16 = @intCast(entry_glyph);    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var result: ?GposCursiveAttachment = null;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, source)) continue;        if (try self.lookupCursiveAttachment(data, bytes, gdef, index, exit, entry)) |attachment| {            result = attachment;        }    }    return result;}fn cursiveAttachmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, exit_glyph: u32, entry_glyph: u32, source: ?FeatureRange) FontError!?GposCursiveAttachment {    if (exit_glyph > std.math.maxInt(u16) or entry_glyph > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    return try self.lookupCursiveAttachment(data, bytes, gdef, lookup_index, @intCast(exit_glyph), @intCast(entry_glyph));}fn markToBaseAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, base_glyph: u32, mark_glyph: u32, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (base_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const base: u16 = @intCast(base_glyph);    const mark: u16 = @intCast(mark_glyph);    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var result: ?GposValueAdjustment = null;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, source)) continue;        if (try self.lookupMarkToBaseAdjustment(data, bytes, gdef, index, base, mark)) |adjustment| {            result = adjustment;        }    }    return result;}fn markToBaseAdjustmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, base_glyph: u32, mark_glyph: u32, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (base_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    return try self.lookupMarkToBaseAdjustment(data, bytes, gdef, lookup_index, @intCast(base_glyph), @intCast(mark_glyph));}fn markToLigatureAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, ligature_glyph: u32, mark_glyph: u32, component_index: u16, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (ligature_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const ligature: u16 = @intCast(ligature_glyph);    const mark: u16 = @intCast(mark_glyph);    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var result: ?GposValueAdjustment = null;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, source)) continue;        if (try self.lookupMarkToLigatureAdjustment(data, bytes, gdef, index, ligature, mark, component_index)) |adjustment| {            result = adjustment;        }    }    return result;}fn markToLigatureAdjustmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, ligature_glyph: u32, mark_glyph: u32, component_index: u16, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (ligature_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    return try self.lookupMarkToLigatureAdjustment(data, bytes, gdef, lookup_index, @intCast(ligature_glyph), @intCast(mark_glyph), component_index);}fn markToMarkAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, base_mark_glyph: u32, mark_glyph: u32, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (base_mark_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const base_mark: u16 = @intCast(base_mark_glyph);    const mark: u16 = @intCast(mark_glyph);    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    var result: ?GposValueAdjustment = null;    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupIsActive(bytes, face, selection, index, source)) continue;        if (try self.lookupMarkToMarkAdjustment(data, bytes, gdef, index, base_mark, mark)) |adjustment| {            result = adjustment;        }    }    return result;}fn markToMarkAdjustmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, base_mark_glyph: u32, mark_glyph: u32, source: ?FeatureRange) FontError!?GposValueAdjustment {    if (base_mark_glyph > std.math.maxInt(u16) or mark_glyph > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    return try self.lookupMarkToMarkAdjustment(data, bytes, gdef, lookup_index, @intCast(base_mark_glyph), @intCast(mark_glyph));}fn hasActiveLookupTypeImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, wanted_lookup_type: u16) FontError!bool {    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (!try self.lookupMayBeActive(bytes, face, selection, index)) continue;        const lookup_offset = try self.lookupOffset(bytes, index);        if (try self.lookupHasResolvedType(bytes, lookup_offset, wanted_lookup_type)) return true;    }    return false;}noinline fn hasActiveSingleAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection) FontError!bool {    if (try self.hasActiveLookupType(data, face, selection, 1)) return true;    if (try self.hasActiveLookupType(data, face, selection, 7)) return true;    return try self.hasActiveLookupType(data, face, selection, 8);}noinline fn hasActiveContextualPairAdjustmentImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection) FontError!bool {    if (try self.hasActiveLookupType(data, face, selection, 7)) return true;    return try self.hasActiveLookupType(data, face, selection, 8);}fn lookupMayBeActiveImpl(self: Gpos, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, lookup_index: u16) FontError!bool {    const langsys_offset = (try gposLangSysOffset(bytes, self.script_list_offset, selection)) orelse return false;    const required_feature_index = try readGposU16(bytes, langsys_offset + 2);    if (required_feature_index != 0xffff) {        const feature_index = required_feature_index;        if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) return true;    }    const feature_count = try readGposU16(bytes, langsys_offset + 4);    if (!hasGposBytes(bytes, langsys_offset + 6, @as(usize, feature_count) * 2)) return error.InvalidGpos;    for (0..feature_count) |i| {        const feature_index = try readGposU16(bytes, langsys_offset + 6 + i * 2);        const feature_tag = try self.featureTag(bytes, feature_index);        if (!featureMayBeEnabled(selection, feature_tag, gposFeatureDefaultOn(selection, feature_tag))) continue;        if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) return true;    }    return false;}fn lookupHasResolvedTypeImpl(self: Gpos, bytes: []const u8, lookup_offset: usize, wanted_lookup_type: u16) FontError!bool {    _ = self;    const lookup_type = try readGposU16(bytes, lookup_offset);    if (lookup_type == wanted_lookup_type) return true;    if (lookup_type != 9) return false;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 8);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type == wanted_lookup_type) return true;    }    return false;}fn lookupPairAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, left_glyph: u16, right_glyph: u16) FontError!?GposPairAdjustment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 2 and lookup_type != 9) return null;    if (try lookupGlyphIgnored(data, gdef, filter, left_glyph)) return null;    if (try lookupGlyphIgnored(data, gdef, filter, right_glyph)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 2) continue;        if (try pairAdjustmentSubtable(bytes, resolved.subtable_offset, left_glyph, right_glyph)) |adjustment| return adjustment;    }    return null;}fn lookupPairAdjustmentBeforeImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize) FontError!?GposPairAdjustmentBefore {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 2 and lookup_type != 9) return null;    const right_glyph: u16 = @intCast(glyph_ids[glyph_index]);    if (try lookupGlyphIgnored(data, gdef, filter, right_glyph)) return null;    const left_index = (try previousLookupGlyphIndex(data, gdef, filter, glyph_ids, glyph_index)) orelse return null;    if (glyph_ids[left_index] > std.math.maxInt(u16)) return null;    const left_glyph: u16 = @intCast(glyph_ids[left_index]);    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 2) continue;        if (try pairAdjustmentSubtable(bytes, resolved.subtable_offset, left_glyph, right_glyph)) |adjustment| {            return .{ .adjustment = adjustment, .left_offset = glyph_index - left_index };        }    }    return null;}fn contextualPairAdjustmentAtImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposContextualPairAdjustment {    if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    for (0..lookup_count) |lookup_index| {        const index: u16 = @intCast(lookup_index);        if (try self.lookupContextualPairAdjustmentAt(data, bytes, face, selection, gdef, index, glyph_ids, glyph_index, source)) |adjustment| return adjustment;    }    return null;}fn contextualPairAdjustmentAtLookupImpl(self: Gpos, data: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposContextualPairAdjustment {    if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const bytes = data[self.table.offset..][0..self.table.len];    return try self.lookupContextualPairAdjustmentAt(data, bytes, face, selection, gdef, lookup_index, glyph_ids, glyph_index, source);}fn lookupContextualPairAdjustmentAtImpl(self: Gpos, data: []const u8, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GposContextualPairAdjustment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    if (!try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 7 and lookup_type != 8 and lookup_type != 9) return null;    if (filter.flags != 0 or filter.mark_filtering_set != null) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        switch (resolved.lookup_type) {            7 => {                if (try self.contextualPairAdjustment(data, bytes, gdef, resolved.subtable_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;            },            8 => {                if (try self.chainedContextualPairAdjustment(data, bytes, gdef, resolved.subtable_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;            },            else => continue,        }    }    return null;}fn lookupNestedPairAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize) FontError!?GposPairAdjustment {    if (glyph_index + 1 >= glyph_ids.len) return null;    if (glyph_ids[glyph_index] > std.math.maxInt(u16) or glyph_ids[glyph_index + 1] > std.math.maxInt(u16)) return null;    return try self.lookupPairAdjustment(data, bytes, gdef, lookup_index, @intCast(glyph_ids[glyph_index]), @intCast(glyph_ids[glyph_index + 1]));}fn contextualPairAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (glyph_index >= glyph_ids.len) return null;    if (!hasGposBytes(bytes, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(bytes, subtable_offset)) {        1 => self.contextualPairAdjustmentFormat1(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        2 => self.contextualPairAdjustmentFormat2(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        3 => self.contextualPairAdjustmentFormat3(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        else => error.InvalidGpos,    };}fn contextualPairAdjustmentFormat1Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    const coverage_index = (coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) orelse return null;    const rule_set_count = try readGposU16(bytes, subtable_offset + 4);    if (coverage_index >= rule_set_count) return error.InvalidGpos;    const rule_set_offsets = subtable_offset + 6;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, coverage_index) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.contextualPairAdjustmentRuleSet(data, bytes, gdef, rule_set_offset, glyph_ids, glyph_index);}fn contextualPairAdjustmentFormat2Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    if ((coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) == null) return null;    const class_def_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 4), 2);    const class_def = layout.ClassDef.init(bytes, class_def_offset) catch return error.InvalidGpos;    const rule_set_count = try readGposU16(bytes, subtable_offset + 6);    const rule_set_offsets = subtable_offset + 8;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const first_class = class_def.class(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos;    if (first_class >= rule_set_count) return null;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, first_class) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.contextualPairAdjustmentClassSet(data, bytes, gdef, class_def, rule_set_offset, glyph_ids, glyph_index);}fn contextualPairAdjustmentFormat3Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 6)) return error.InvalidGpos;    const glyph_count = try readGposU16(bytes, subtable_offset + 2);    const lookup_count = try readGposU16(bytes, subtable_offset + 4);    if (glyph_count == 0) return error.InvalidGpos;    if (@as(usize, glyph_count) > glyph_ids.len - glyph_index) return null;    const coverage_offsets = subtable_offset + 6;    if (!hasGposBytes(bytes, coverage_offsets, @as(usize, glyph_count) * 2)) return error.InvalidGpos;    for (0..glyph_count) |sequence_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, coverage_offsets + sequence_index * 2), 2);        if (!try gposGlyphInCoverage(bytes, coverage_offset, glyph_ids[glyph_index + sequence_index])) return null;    }    const lookup_records_offset = coverage_offsets + @as(usize, glyph_count) * 2;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn chainedContextualPairAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (glyph_index >= glyph_ids.len) return null;    if (!hasGposBytes(bytes, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(bytes, subtable_offset)) {        1 => self.chainedContextualPairAdjustmentFormat1(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        2 => self.chainedContextualPairAdjustmentFormat2(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        3 => self.chainedContextualPairAdjustmentFormat3(data, bytes, gdef, subtable_offset, glyph_ids, glyph_index),        else => error.InvalidGpos,    };}fn chainedContextualPairAdjustmentFormat1Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const match = (try self.chainedContextualMatchFormat1(bytes, subtable_offset, glyph_ids, glyph_index)) orelse return null;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, match.lookup_records_offset, match.lookup_count, glyph_ids, glyph_index, match.input_count);}fn chainedContextualPairAdjustmentFormat2Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const match = (try self.chainedContextualMatchFormat2(bytes, subtable_offset, glyph_ids, glyph_index)) orelse return null;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, match.lookup_records_offset, match.lookup_count, glyph_ids, glyph_index, match.input_count);}fn chainedContextualPairAdjustmentFormat3Impl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 4)) return error.InvalidGpos;    const backtrack_count = try readGposU16(bytes, subtable_offset + 2);    const backtrack_offsets = subtable_offset + 4;    if (!hasGposBytes(bytes, backtrack_offsets, @as(usize, backtrack_count) * 2)) return error.InvalidGpos;    if (@as(usize, backtrack_count) > glyph_index) return null;    for (0..backtrack_count) |backtrack_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, backtrack_offsets + backtrack_index * 2), 2);        const match_index = glyph_index - backtrack_index - 1;        if (!try gposGlyphInCoverage(bytes, coverage_offset, glyph_ids[match_index])) return null;    }    const input_count_offset = backtrack_offsets + @as(usize, backtrack_count) * 2;    const input_count = try readGposU16(bytes, input_count_offset);    if (input_count == 0) return error.InvalidGpos;    const input_offsets = input_count_offset + 2;    if (!hasGposBytes(bytes, input_offsets, @as(usize, input_count) * 2)) return error.InvalidGpos;    if (@as(usize, input_count) > glyph_ids.len - glyph_index) return null;    for (0..input_count) |input_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, input_offsets + input_index * 2), 2);        if (!try gposGlyphInCoverage(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 readGposU16(bytes, lookahead_count_offset);    const lookahead_offsets = lookahead_count_offset + 2;    if (!hasGposBytes(bytes, lookahead_offsets, @as(usize, lookahead_count) * 2)) return error.InvalidGpos;    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 gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, lookahead_offsets + lookahead_index * 2), 2);        if (!try gposGlyphInCoverage(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 readGposU16(bytes, lookup_count_offset);    const lookup_records_offset = lookup_count_offset + 2;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, input_count);}fn chainedContextualMatchFormat1Impl(self: Gpos, bytes: []const u8, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?ChainedContextMatch {    if (!hasGposBytes(bytes, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    const coverage_index = (coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) orelse return null;    const rule_set_count = try readGposU16(bytes, subtable_offset + 4);    if (coverage_index >= rule_set_count) return error.InvalidGpos;    const rule_set_offsets = subtable_offset + 6;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, coverage_index) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.chainedContextualRuleSetMatch(bytes, rule_set_offset, glyph_ids, glyph_index);}fn chainedContextualMatchFormat2Impl(self: Gpos, bytes: []const u8, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?ChainedContextMatch {    if (!hasGposBytes(bytes, subtable_offset, 12)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    if ((coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) == null) return null;    const backtrack_class_def_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 4), 2);    const input_class_def_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 6), 2);    const lookahead_class_def_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 8), 2);    const backtrack_class_def = layout.ClassDef.init(bytes, backtrack_class_def_offset) catch return error.InvalidGpos;    const input_class_def = layout.ClassDef.init(bytes, input_class_def_offset) catch return error.InvalidGpos;    const lookahead_class_def = layout.ClassDef.init(bytes, lookahead_class_def_offset) catch return error.InvalidGpos;    const rule_set_count = try readGposU16(bytes, subtable_offset + 10);    const rule_set_offsets = subtable_offset + 12;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const first_class = input_class_def.class(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos;    if (first_class >= rule_set_count) return null;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, first_class) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.chainedContextualClassSetMatch(bytes, backtrack_class_def, input_class_def, lookahead_class_def, rule_set_offset, glyph_ids, glyph_index);}fn chainedContextualRuleSetMatchImpl(self: Gpos, bytes: []const u8, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?ChainedContextMatch {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 2);        if (try self.chainedContextualRuleMatch(bytes, rule_offset, glyph_ids, glyph_index)) |match| return match;    }    return null;}fn chainedContextualRuleMatchImpl(self: Gpos, bytes: []const u8, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?ChainedContextMatch {    _ = self;    const backtrack_count = try readGposU16(bytes, rule_offset);    const backtrack_sequence_offset = rule_offset + 2;    if (!hasGposBytes(bytes, backtrack_sequence_offset, @as(usize, backtrack_count) * 2)) return error.InvalidGpos;    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 readGposU16(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 readGposU16(bytes, input_count_offset);    if (input_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    for (1..input_count) |input_index| {        const glyph = glyph_ids[glyph_index + input_index];        if (glyph > std.math.maxInt(u16) or glyph != try readGposU16(bytes, input_sequence_offset + (input_index - 1) * 2)) return null;    }    const lookahead_count_offset = input_sequence_offset + input_sequence_len;    const lookahead_count = try readGposU16(bytes, lookahead_count_offset);    const lookahead_sequence_offset = lookahead_count_offset + 2;    if (!hasGposBytes(bytes, lookahead_sequence_offset, @as(usize, lookahead_count) * 2)) return error.InvalidGpos;    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 readGposU16(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 readGposU16(bytes, lookup_count_offset);    const lookup_records_offset = lookup_count_offset + 2;    if (!hasGposBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGpos;    return .{        .lookup_records_offset = lookup_records_offset,        .lookup_count = lookup_count,        .input_count = input_count,    };}fn chainedContextualClassSetMatchImpl(    self: Gpos,    bytes: []const u8,    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!?ChainedContextMatch {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 2);        if (try self.chainedContextualClassRuleMatch(bytes, backtrack_class_def, input_class_def, lookahead_class_def, rule_offset, glyph_ids, glyph_index)) |match| return match;    }    return null;}fn chainedContextualClassRuleMatchImpl(    self: Gpos,    bytes: []const u8,    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!?ChainedContextMatch {    _ = self;    const backtrack_count = try readGposU16(bytes, rule_offset);    const backtrack_sequence_offset = rule_offset + 2;    if (!hasGposBytes(bytes, backtrack_sequence_offset, @as(usize, backtrack_count) * 2)) return error.InvalidGpos;    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 readGposU16(bytes, backtrack_sequence_offset + backtrack_index * 2);        const actual_class = backtrack_class_def.class(bytes, glyph) catch return error.InvalidGpos;        if (actual_class != expected_class) return null;    }    const input_count_offset = backtrack_sequence_offset + @as(usize, backtrack_count) * 2;    const input_count = try readGposU16(bytes, input_count_offset);    if (input_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    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 readGposU16(bytes, input_sequence_offset + (input_index - 1) * 2);        const actual_class = input_class_def.class(bytes, glyph) catch return error.InvalidGpos;        if (actual_class != expected_class) return null;    }    const lookahead_count_offset = input_sequence_offset + input_sequence_len;    const lookahead_count = try readGposU16(bytes, lookahead_count_offset);    const lookahead_sequence_offset = lookahead_count_offset + 2;    if (!hasGposBytes(bytes, lookahead_sequence_offset, @as(usize, lookahead_count) * 2)) return error.InvalidGpos;    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 readGposU16(bytes, lookahead_sequence_offset + lookahead_index * 2);        const actual_class = lookahead_class_def.class(bytes, glyph) catch return error.InvalidGpos;        if (actual_class != expected_class) return null;    }    const lookup_count_offset = lookahead_sequence_offset + @as(usize, lookahead_count) * 2;    const lookup_count = try readGposU16(bytes, lookup_count_offset);    const lookup_records_offset = lookup_count_offset + 2;    if (!hasGposBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGpos;    return .{        .lookup_records_offset = lookup_records_offset,        .lookup_count = lookup_count,        .input_count = input_count,    };}fn contextualPairAdjustmentRuleSetImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 4);        if (try self.contextualPairAdjustmentRule(data, bytes, gdef, rule_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;    }    return null;}fn contextualPairAdjustmentClassSetImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, class_def: layout.ClassDef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 4);        if (try self.contextualPairAdjustmentClassRule(data, bytes, gdef, class_def, rule_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;    }    return null;}fn contextualPairAdjustmentClassRuleImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, class_def: layout.ClassDef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const glyph_count = try readGposU16(bytes, rule_offset);    const lookup_count = try readGposU16(bytes, rule_offset + 2);    if (glyph_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    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 readGposU16(bytes, input_sequence_offset + (sequence_index - 1) * 2);        const actual_class = class_def.class(bytes, glyph) catch return error.InvalidGpos;        if (actual_class != expected_class) return null;    }    const lookup_records_offset = input_sequence_offset + input_sequence_len;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualPairAdjustmentRuleImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposContextualPairAdjustment {    const glyph_count = try readGposU16(bytes, rule_offset);    const lookup_count = try readGposU16(bytes, rule_offset + 2);    if (glyph_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    for (1..glyph_count) |sequence_index| {        const glyph = glyph_ids[glyph_index + sequence_index];        if (glyph > std.math.maxInt(u16) or glyph != try readGposU16(bytes, input_sequence_offset + (sequence_index - 1) * 2)) return null;    }    const lookup_records_offset = input_sequence_offset + input_sequence_len;    return try self.contextualPairAdjustmentRecords(data, bytes, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualPairAdjustmentRecordsImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_records_offset: usize, lookup_count: u16, glyph_ids: []const u32, glyph_index: usize, input_count: u16) FontError!?GposContextualPairAdjustment {    if (!hasGposBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGpos;    for (0..lookup_count) |lookup_record_index| {        const record_offset = lookup_records_offset + lookup_record_index * 4;        const sequence_index = try readGposU16(bytes, record_offset);        const nested_lookup_index = try readGposU16(bytes, record_offset + 2);        if (sequence_index >= input_count or sequence_index + 1 >= input_count) return error.InvalidGpos;        const nested_glyph_index = glyph_index + @as(usize, sequence_index);        const adjustment = (try self.lookupNestedPairAdjustment(data, bytes, gdef, nested_lookup_index, glyph_ids, nested_glyph_index)) orelse continue;        return .{            .adjustment = adjustment,            .target_offset = @intCast(sequence_index),            .skip_count = if (adjustment.right.isZero()) @as(usize, sequence_index) + 1 else input_count,        };    }    return null;}fn lookupSingleAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, glyph_id: u16) FontError!?GposValueAdjustment {    var glyphs = [_]u32{glyph_id};    const adjustment = (try self.lookupSingleAdjustmentAt(data, bytes, null, .{}, gdef, lookup_index, &glyphs, 0, null, false, false)) orelse return null;    if (adjustment.target_offset != 0) return null;    return adjustment.adjustment;}fn lookupSingleAdjustmentAtImpl(self: Gpos, data: []const u8, bytes: []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!?GposSingleAdjustment {    if (glyph_index >= glyph_ids.len or glyph_ids[glyph_index] > std.math.maxInt(u16)) return null;    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    if (require_active and !try self.lookupIsActive(bytes, face, selection, lookup_index, source)) return null;    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 1 and lookup_type != 7 and lookup_type != 8 and lookup_type != 9) return null;    const glyph_id: u16 = @intCast(glyph_ids[glyph_index]);    if (try lookupGlyphIgnored(data, gdef, filter, glyph_id)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        switch (resolved.lookup_type) {            1 => {                if (try singleAdjustmentSubtable(bytes, resolved.subtable_offset, glyph_id)) |adjustment| return .{ .adjustment = adjustment };            },            7 => {                if (!allow_context) return null;                if (filter.flags != 0 or filter.mark_filtering_set != null) return null;                if (try self.contextualSingleAdjustment(data, bytes, selection, gdef, resolved.subtable_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;            },            8 => {                if (!allow_context) return null;                if (filter.flags != 0 or filter.mark_filtering_set != null) return null;                if (try self.chainedContextualSingleAdjustment(data, bytes, selection, gdef, resolved.subtable_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;            },            else => continue,        }    }    return null;}fn contextualSingleAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (glyph_index >= glyph_ids.len) return null;    if (!hasGposBytes(bytes, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(bytes, subtable_offset)) {        1 => self.contextualSingleAdjustmentFormat1(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        2 => self.contextualSingleAdjustmentFormat2(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        3 => self.contextualSingleAdjustmentFormat3(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        else => error.InvalidGpos,    };}fn contextualSingleAdjustmentFormat1Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    const coverage_index = (coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) orelse return null;    const rule_set_count = try readGposU16(bytes, subtable_offset + 4);    if (coverage_index >= rule_set_count) return error.InvalidGpos;    const rule_set_offsets = subtable_offset + 6;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, coverage_index) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.contextualSingleAdjustmentRuleSet(data, bytes, selection, gdef, rule_set_offset, glyph_ids, glyph_index);}fn contextualSingleAdjustmentFormat2Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(bytes, coverage_offset) catch return error.InvalidGpos;    if ((coverage.index(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos) == null) return null;    const class_def_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, subtable_offset + 4), 2);    const class_def = layout.ClassDef.init(bytes, class_def_offset) catch return error.InvalidGpos;    const rule_set_count = try readGposU16(bytes, subtable_offset + 6);    const rule_set_offsets = subtable_offset + 8;    if (!hasGposBytes(bytes, rule_set_offsets, @as(usize, rule_set_count) * 2)) return error.InvalidGpos;    const first_class = class_def.class(bytes, glyph_ids[glyph_index]) catch return error.InvalidGpos;    if (first_class >= rule_set_count) return null;    const rule_set_relative = try readGposU16(bytes, rule_set_offsets + @as(usize, first_class) * 2);    if (rule_set_relative == 0) return null;    const rule_set_offset = try gposChildOffset(bytes, subtable_offset, rule_set_relative, 2);    return try self.contextualSingleAdjustmentClassSet(data, bytes, selection, gdef, class_def, rule_set_offset, glyph_ids, glyph_index);}fn contextualSingleAdjustmentFormat3Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 6)) return error.InvalidGpos;    const glyph_count = try readGposU16(bytes, subtable_offset + 2);    const lookup_count = try readGposU16(bytes, subtable_offset + 4);    if (glyph_count == 0) return error.InvalidGpos;    if (@as(usize, glyph_count) > glyph_ids.len - glyph_index) return null;    const coverage_offsets = subtable_offset + 6;    if (!hasGposBytes(bytes, coverage_offsets, @as(usize, glyph_count) * 2)) return error.InvalidGpos;    for (0..glyph_count) |sequence_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, coverage_offsets + sequence_index * 2), 2);        if (!try gposGlyphInCoverage(bytes, coverage_offset, glyph_ids[glyph_index + sequence_index])) return null;    }    const lookup_records_offset = coverage_offsets + @as(usize, glyph_count) * 2;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn chainedContextualSingleAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (glyph_index >= glyph_ids.len) return null;    if (!hasGposBytes(bytes, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(bytes, subtable_offset)) {        1 => self.chainedContextualSingleAdjustmentFormat1(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        2 => self.chainedContextualSingleAdjustmentFormat2(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        3 => self.chainedContextualSingleAdjustmentFormat3(data, bytes, selection, gdef, subtable_offset, glyph_ids, glyph_index),        else => error.InvalidGpos,    };}fn chainedContextualSingleAdjustmentFormat1Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const match = (try self.chainedContextualMatchFormat1(bytes, subtable_offset, glyph_ids, glyph_index)) orelse return null;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, match.lookup_records_offset, match.lookup_count, glyph_ids, glyph_index, match.input_count);}fn chainedContextualSingleAdjustmentFormat2Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const match = (try self.chainedContextualMatchFormat2(bytes, subtable_offset, glyph_ids, glyph_index)) orelse return null;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, match.lookup_records_offset, match.lookup_count, glyph_ids, glyph_index, match.input_count);}fn chainedContextualSingleAdjustmentFormat3Impl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, subtable_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    if (!hasGposBytes(bytes, subtable_offset, 4)) return error.InvalidGpos;    const backtrack_count = try readGposU16(bytes, subtable_offset + 2);    const backtrack_offsets = subtable_offset + 4;    if (!hasGposBytes(bytes, backtrack_offsets, @as(usize, backtrack_count) * 2)) return error.InvalidGpos;    if (@as(usize, backtrack_count) > glyph_index) return null;    for (0..backtrack_count) |backtrack_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, backtrack_offsets + backtrack_index * 2), 2);        const match_index = glyph_index - backtrack_index - 1;        if (!try gposGlyphInCoverage(bytes, coverage_offset, glyph_ids[match_index])) return null;    }    const input_count_offset = backtrack_offsets + @as(usize, backtrack_count) * 2;    const input_count = try readGposU16(bytes, input_count_offset);    if (input_count == 0) return error.InvalidGpos;    const input_offsets = input_count_offset + 2;    if (!hasGposBytes(bytes, input_offsets, @as(usize, input_count) * 2)) return error.InvalidGpos;    if (@as(usize, input_count) > glyph_ids.len - glyph_index) return null;    for (0..input_count) |input_index| {        const coverage_offset = try gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, input_offsets + input_index * 2), 2);        if (!try gposGlyphInCoverage(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 readGposU16(bytes, lookahead_count_offset);    const lookahead_offsets = lookahead_count_offset + 2;    if (!hasGposBytes(bytes, lookahead_offsets, @as(usize, lookahead_count) * 2)) return error.InvalidGpos;    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 gposChildOffset(bytes, subtable_offset, try readGposU16(bytes, lookahead_offsets + lookahead_index * 2), 2);        if (!try gposGlyphInCoverage(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 readGposU16(bytes, lookup_count_offset);    const lookup_records_offset = lookup_count_offset + 2;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, input_count);}fn contextualSingleAdjustmentRuleSetImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 4);        if (try self.contextualSingleAdjustmentRule(data, bytes, selection, gdef, rule_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;    }    return null;}fn contextualSingleAdjustmentClassSetImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, class_def: layout.ClassDef, rule_set_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const rule_count = try readGposU16(bytes, rule_set_offset);    const rule_offsets = rule_set_offset + 2;    if (!hasGposBytes(bytes, rule_offsets, @as(usize, rule_count) * 2)) return error.InvalidGpos;    for (0..rule_count) |rule_index| {        const rule_relative = try readGposU16(bytes, rule_offsets + rule_index * 2);        if (rule_relative == 0) continue;        const rule_offset = try gposChildOffset(bytes, rule_set_offset, rule_relative, 4);        if (try self.contextualSingleAdjustmentClassRule(data, bytes, selection, gdef, class_def, rule_offset, glyph_ids, glyph_index)) |adjustment| return adjustment;    }    return null;}fn contextualSingleAdjustmentClassRuleImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, class_def: layout.ClassDef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const glyph_count = try readGposU16(bytes, rule_offset);    const lookup_count = try readGposU16(bytes, rule_offset + 2);    if (glyph_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    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 readGposU16(bytes, input_sequence_offset + (sequence_index - 1) * 2);        const actual_class = class_def.class(bytes, glyph) catch return error.InvalidGpos;        if (actual_class != expected_class) return null;    }    const lookup_records_offset = input_sequence_offset + input_sequence_len;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualSingleAdjustmentRuleImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, rule_offset: usize, glyph_ids: []const u32, glyph_index: usize) FontError!?GposSingleAdjustment {    const glyph_count = try readGposU16(bytes, rule_offset);    const lookup_count = try readGposU16(bytes, rule_offset + 2);    if (glyph_count == 0) return error.InvalidGpos;    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 (!hasGposBytes(bytes, input_sequence_offset, input_sequence_len)) return error.InvalidGpos;    for (1..glyph_count) |sequence_index| {        const glyph = glyph_ids[glyph_index + sequence_index];        if (glyph > std.math.maxInt(u16) or glyph != try readGposU16(bytes, input_sequence_offset + (sequence_index - 1) * 2)) return null;    }    const lookup_records_offset = input_sequence_offset + input_sequence_len;    return try self.contextualSingleAdjustmentRecords(data, bytes, selection, gdef, lookup_records_offset, lookup_count, glyph_ids, glyph_index, glyph_count);}fn contextualSingleAdjustmentRecordsImpl(self: Gpos, data: []const u8, bytes: []const u8, selection: LayoutSelection, gdef: ?Gdef, lookup_records_offset: usize, lookup_count: u16, glyph_ids: []const u32, glyph_index: usize, input_count: u16) FontError!?GposSingleAdjustment {    if (!hasGposBytes(bytes, lookup_records_offset, @as(usize, lookup_count) * 4)) return error.InvalidGpos;    for (0..lookup_count) |lookup_record_index| {        const record_offset = lookup_records_offset + lookup_record_index * 4;        const sequence_index = try readGposU16(bytes, record_offset);        const nested_lookup_index = try readGposU16(bytes, record_offset + 2);        if (sequence_index >= input_count) return error.InvalidGpos;        const nested_glyph_index = glyph_index + @as(usize, sequence_index);        var adjustment = (try self.lookupSingleAdjustmentAt(data, bytes, null, selection, gdef, nested_lookup_index, glyph_ids, nested_glyph_index, null, false, false)) orelse continue;        adjustment.target_offset = @intCast(sequence_index);        adjustment.skip_count = input_count;        return adjustment;    }    return null;}fn lookupCursiveAttachmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, exit_glyph: u16, entry_glyph: u16) FontError!?GposCursiveAttachment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 3 and lookup_type != 9) return null;    if (try lookupGlyphIgnored(data, gdef, filter, exit_glyph)) return null;    if (try lookupGlyphIgnored(data, gdef, filter, entry_glyph)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 3) continue;        if (try cursiveAttachmentSubtable(bytes, resolved.subtable_offset, exit_glyph, entry_glyph)) |attachment| return attachment;    }    return null;}fn lookupMarkToBaseAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, base_glyph: u16, mark_glyph: u16) FontError!?GposValueAdjustment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 4 and lookup_type != 9) return null;    if (try lookupGlyphIgnored(data, gdef, filter, base_glyph)) return null;    if (try lookupGlyphIgnored(data, gdef, filter, mark_glyph)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 4) continue;        if (try markToBaseAdjustmentSubtable(bytes, resolved.subtable_offset, base_glyph, mark_glyph)) |adjustment| return adjustment;    }    return null;}fn lookupMarkToLigatureAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, ligature_glyph: u16, mark_glyph: u16, component_index: u16) FontError!?GposValueAdjustment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 5 and lookup_type != 9) return null;    if (try lookupGlyphIgnored(data, gdef, filter, ligature_glyph)) return null;    if (try lookupGlyphIgnored(data, gdef, filter, mark_glyph)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 5) continue;        if (try markToLigatureAdjustmentSubtable(bytes, resolved.subtable_offset, ligature_glyph, mark_glyph, component_index)) |adjustment| return adjustment;    }    return null;}fn lookupMarkToMarkAdjustmentImpl(self: Gpos, data: []const u8, bytes: []const u8, gdef: ?Gdef, lookup_index: u16, base_mark_glyph: u16, mark_glyph: u16) FontError!?GposValueAdjustment {    const lookup_offset = try self.lookupOffset(bytes, lookup_index);    const lookup_type = try readGposU16(bytes, lookup_offset);    const filter = try gposLookupFilter(bytes, lookup_offset);    if (lookup_type != 6 and lookup_type != 9) return null;    if (try lookupGlyphIgnored(data, gdef, filter, base_mark_glyph)) return null;    if (try lookupGlyphIgnored(data, gdef, filter, mark_glyph)) return null;    const subtable_count = try readGposU16(bytes, lookup_offset + 4);    if (!hasGposBytes(bytes, lookup_offset + 6, @as(usize, subtable_count) * 2)) return error.InvalidGpos;    for (0..subtable_count) |i| {        const subtable_offset = try gposChildOffset(bytes, lookup_offset, try readGposU16(bytes, lookup_offset + 6 + i * 2), 2);        const resolved = try resolveGposSubtable(bytes, lookup_type, subtable_offset);        if (resolved.lookup_type != 6) continue;        if (try markToMarkAdjustmentSubtable(bytes, resolved.subtable_offset, base_mark_glyph, mark_glyph)) |adjustment| return adjustment;    }    return null;}fn lookupOffsetImpl(self: Gpos, bytes: []const u8, lookup_index: u16) FontError!usize {    const lookup_count = try readGposU16(bytes, self.lookup_list_offset);    if (lookup_index >= lookup_count) return error.InvalidGpos;    const lookup_record = self.lookup_list_offset + 2 + @as(usize, lookup_index) * 2;    return try gposChildOffset(bytes, self.lookup_list_offset, try readGposU16(bytes, lookup_record), 6);}fn lookupIsActiveImpl(self: Gpos, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, lookup_index: u16, source: ?FeatureRange) FontError!bool {    const langsys_offset = (try gposLangSysOffset(bytes, self.script_list_offset, selection)) orelse return false;    const required_feature_index = try readGposU16(bytes, langsys_offset + 2);    if (required_feature_index != 0xffff) {        const feature_index = required_feature_index;        if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) return true;    }    const feature_count = try readGposU16(bytes, langsys_offset + 4);    if (!hasGposBytes(bytes, langsys_offset + 6, @as(usize, feature_count) * 2)) return error.InvalidGpos;    for (0..feature_count) |i| {        const feature_index = try readGposU16(bytes, langsys_offset + 6 + i * 2);        const feature_tag = try self.featureTag(bytes, feature_index);        if (!featureEnabled(selection, feature_tag, gposFeatureDefaultOn(selection, feature_tag), source)) continue;        if (try self.featureContainsLookup(bytes, face, selection, feature_index, lookup_index)) return true;    }    return false;}fn featureTagImpl(self: Gpos, bytes: []const u8, feature_index: u16) FontError!Tag {    const feature_record = try self.featureRecord(bytes, feature_index);    return try readGposU32(bytes, feature_record);}fn featureContainsLookupImpl(self: Gpos, 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(.gpos, bytes, feature_offset, lookup_index);}fn featureOffsetImpl(self: Gpos, bytes: []const u8, face: ?lookups.VariationContext, selection: LayoutSelection, feature_index: u16) FontError!usize {    if (try matchingFeatureSubstitutionOffset(.gpos, bytes, self.feature_variations_offset, face, selection)) |substitution_offset| {        if (try substitutedFeatureOffset(.gpos, bytes, substitution_offset, feature_index)) |alternate_offset| return alternate_offset;    }    const feature_record = try self.featureRecord(bytes, feature_index);    return try gposChildOffset(bytes, self.feature_list_offset, try readGposU16(bytes, feature_record + 4), 4);}fn featureRecordImpl(self: Gpos, bytes: []const u8, feature_index: u16) FontError!usize {    const feature_count = try readGposU16(bytes, self.feature_list_offset);    if (feature_index >= feature_count) return error.InvalidGpos;    const feature_record = self.feature_list_offset + 2 + @as(usize, feature_index) * 6;    if (!hasGposBytes(bytes, feature_record, 6)) return error.InvalidGpos;    return feature_record;}const ResolvedGposSubtable = struct {    lookup_type: u16,    subtable_offset: usize,};fn resolveGposSubtable(data: []const u8, lookup_type: u16, subtable_offset: usize) FontError!ResolvedGposSubtable {    if (lookup_type != 9) return .{ .lookup_type = lookup_type, .subtable_offset = subtable_offset };    if (!hasGposBytes(data, subtable_offset, 8)) return error.InvalidGpos;    if (try readGposU16(data, subtable_offset) != 1) return error.InvalidGpos;    const extension_lookup_type = try readGposU16(data, subtable_offset + 2);    if (extension_lookup_type == 9) return error.InvalidGpos;    const extension_offset = try readGposU32(data, subtable_offset + 4);    return .{        .lookup_type = extension_lookup_type,        .subtable_offset = try gposChildOffset32(data, subtable_offset, extension_offset, 2),    };}fn previousLookupGlyphIndexLocal(data: []const u8, gdef: ?Gdef, filter: LookupFilter, glyph_ids: []const u32, glyph_index: usize) FontError!?usize {    var index = glyph_index;    while (index > 0) {        index -= 1;        const glyph_id = glyph_ids[index];        if (glyph_id > std.math.maxInt(u16)) return null;        if (try lookupGlyphIgnored(data, gdef, filter, @intCast(glyph_id))) continue;        return index;    }    return null;}fn gposLangSysOffset(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 gposLangSysOffsetForScript(data, script_list_offset, script_tag, selection.language)) |langsys_offset| return langsys_offset;        }        if (!scriptTagListContains(selection.script_tags, defaultScriptTag)) {            return try gposLangSysOffsetForScript(data, script_list_offset, defaultScriptTag, selection.language);        }        return null;    }    if (try gposScriptOffset(data, script_list_offset, selection.script)) |script_offset| {        return try gposLangSysOffsetInScript(data, script_offset, selection.language);    }    if (selection.script != defaultScriptTag) {        if (try gposScriptOffset(data, script_list_offset, defaultScriptTag)) |script_offset| {            return try gposLangSysOffsetInScript(data, script_offset, selection.language);        }    }    return null;}fn gposLangSysOffsetForScript(data: []const u8, script_list_offset: usize, script_tag: Tag, language: ?Tag) FontError!?usize {    const script_offset = (try gposScriptOffset(data, script_list_offset, script_tag)) orelse return null;    return try gposLangSysOffsetInScript(data, script_offset, language);}fn gposScriptOffset(data: []const u8, script_list_offset: usize, script_tag: Tag) FontError!?usize {    const script_count = try readGposU16(data, script_list_offset);    if (!hasGposBytes(data, script_list_offset + 2, @as(usize, script_count) * 6)) return error.InvalidGpos;    for (0..script_count) |i| {        const record = script_list_offset + 2 + i * 6;        if (try readGposU32(data, record) != script_tag) continue;        return try gposChildOffset(data, script_list_offset, try readGposU16(data, record + 4), 4);    }    return null;}fn gposLangSysOffsetInScript(data: []const u8, script_offset: usize, language: ?Tag) FontError!?usize {    if (language) |language_tag| {        const langsys_count = try readGposU16(data, script_offset + 2);        const records_offset = script_offset + 4;        if (!hasGposBytes(data, records_offset, @as(usize, langsys_count) * 6)) return error.InvalidGpos;        for (0..langsys_count) |i| {            const record = records_offset + i * 6;            if (try readGposU32(data, record) != language_tag) continue;            return try gposChildOffset(data, script_offset, try readGposU16(data, record + 4), 6);        }    }    const default_langsys_offset = try readGposU16(data, script_offset);    if (default_langsys_offset == 0) return null;    return try gposChildOffset(data, script_offset, default_langsys_offset, 6);}fn gposGlyphInCoverage(data: []const u8, coverage_offset: usize, glyph_id: u32) FontError!bool {    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    return (coverage.index(data, glyph_id) catch return error.InvalidGpos) != null;}fn pairAdjustmentSubtable(data: []const u8, subtable_offset: usize, left_glyph: u16, right_glyph: u16) FontError!?GposPairAdjustment {    if (!hasGposBytes(data, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(data, subtable_offset)) {        1 => pairAdjustmentFormat1(data, subtable_offset, left_glyph, right_glyph),        2 => pairAdjustmentFormat2(data, subtable_offset, left_glyph, right_glyph),        else => error.InvalidGpos,    };}fn singleAdjustmentSubtable(data: []const u8, subtable_offset: usize, glyph_id: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 2)) return error.InvalidGpos;    return switch (try readGposU16(data, subtable_offset)) {        1 => singleAdjustmentFormat1(data, subtable_offset, glyph_id),        2 => singleAdjustmentFormat2(data, subtable_offset, glyph_id),        else => error.InvalidGpos,    };}fn singleAdjustmentFormat1(data: []const u8, subtable_offset: usize, glyph_id: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 6)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    if ((coverage.index(data, glyph_id) catch return error.InvalidGpos) == null) return null;    const value_format = try readGposU16(data, subtable_offset + 4);    return try readGposValueRecord(data, subtable_offset, subtable_offset + 6, value_format);}fn singleAdjustmentFormat2(data: []const u8, subtable_offset: usize, glyph_id: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 8)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    const coverage_index = (coverage.index(data, glyph_id) catch return error.InvalidGpos) orelse return null;    const value_format = try readGposU16(data, subtable_offset + 4);    const value_count = try readGposU16(data, subtable_offset + 6);    if (coverage_index >= value_count) return error.InvalidGpos;    const value_size = try gposValueRecordSize(value_format);    const value_records_offset = subtable_offset + 8;    if (value_size != 0 and @as(usize, value_count) > (std.math.maxInt(usize) - 8) / value_size) return error.InvalidGpos;    if (!hasGposBytes(data, value_records_offset, @as(usize, value_count) * value_size)) return error.InvalidGpos;    return try readGposValueRecord(data, subtable_offset, value_records_offset + @as(usize, coverage_index) * value_size, value_format);}fn cursiveAttachmentSubtable(data: []const u8, subtable_offset: usize, exit_glyph: u16, entry_glyph: u16) FontError!?GposCursiveAttachment {    if (!hasGposBytes(data, subtable_offset, 6)) return error.InvalidGpos;    if (try readGposU16(data, subtable_offset) != 1) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    const exit_index = (coverage.index(data, exit_glyph) catch return error.InvalidGpos) orelse return null;    const entry_index = (coverage.index(data, entry_glyph) catch return error.InvalidGpos) orelse return null;    const entry_exit_count = try readGposU16(data, subtable_offset + 4);    if (exit_index >= entry_exit_count or entry_index >= entry_exit_count) return error.InvalidGpos;    const records_offset = subtable_offset + 6;    if (!hasGposBytes(data, records_offset, @as(usize, entry_exit_count) * 4)) return error.InvalidGpos;    const exit_record = records_offset + @as(usize, exit_index) * 4;    const entry_record = records_offset + @as(usize, entry_index) * 4;    const exit_anchor_relative = try readGposU16(data, exit_record + 2);    const entry_anchor_relative = try readGposU16(data, entry_record);    if (exit_anchor_relative == 0 or entry_anchor_relative == 0) return null;    const exit_anchor_offset = try gposChildOffset(data, subtable_offset, exit_anchor_relative, 6);    const entry_anchor_offset = try gposChildOffset(data, subtable_offset, entry_anchor_relative, 6);    const exit_anchor = try readGposAnchor(data, exit_anchor_offset);    const entry_anchor = try readGposAnchor(data, entry_anchor_offset);    return .{        .x_anchor_delta = @as(i32, exit_anchor.x) - @as(i32, entry_anchor.x),        .y_anchor_delta = @as(i32, exit_anchor.y) - @as(i32, entry_anchor.y),    };}fn pairAdjustmentFormat1(data: []const u8, subtable_offset: usize, left_glyph: u16, right_glyph: u16) FontError!?GposPairAdjustment {    if (!hasGposBytes(data, subtable_offset, 10)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    const coverage_index = (coverage.index(data, left_glyph) catch return error.InvalidGpos) orelse return null;    const value_format_1 = try readGposU16(data, subtable_offset + 4);    const value_format_2 = try readGposU16(data, subtable_offset + 6);    const value_size_1 = try gposValueRecordSize(value_format_1);    const value_size_2 = try gposValueRecordSize(value_format_2);    const pair_set_count = try readGposU16(data, subtable_offset + 8);    if (coverage_index >= pair_set_count) return error.InvalidGpos;    const pair_set_offsets = subtable_offset + 10;    if (!hasGposBytes(data, pair_set_offsets, @as(usize, pair_set_count) * 2)) return error.InvalidGpos;    const pair_set_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, pair_set_offsets + @as(usize, coverage_index) * 2), 2);    const pair_value_count = try readGposU16(data, pair_set_offset);    const pair_record_size = 2 + value_size_1 + value_size_2;    if (@as(usize, pair_value_count) > std.math.maxInt(usize) / pair_record_size) return error.InvalidGpos;    const pair_values_offset = pair_set_offset + 2;    if (!hasGposBytes(data, pair_values_offset, @as(usize, pair_value_count) * pair_record_size)) return error.InvalidGpos;    var low: usize = 0;    var high: usize = pair_value_count;    while (low < high) {        const mid = low + (high - low) / 2;        const record = pair_values_offset + mid * pair_record_size;        const second_glyph = try readGposU16(data, record);        if (right_glyph < second_glyph) {            high = mid;        } else if (right_glyph > second_glyph) {            low = mid + 1;        } else {            const value_1 = try readGposValueRecord(data, pair_set_offset, record + 2, value_format_1);            const value_2 = try readGposValueRecord(data, pair_set_offset, record + 2 + value_size_1, value_format_2);            return .{ .left = value_1, .right = value_2 };        }    }    return null;}fn pairAdjustmentFormat2(data: []const u8, subtable_offset: usize, left_glyph: u16, right_glyph: u16) FontError!?GposPairAdjustment {    if (!hasGposBytes(data, subtable_offset, 16)) return error.InvalidGpos;    const coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const coverage = layout.Coverage.init(data, coverage_offset) catch return error.InvalidGpos;    if ((coverage.index(data, left_glyph) catch return error.InvalidGpos) == null) return null;    const value_format_1 = try readGposU16(data, subtable_offset + 4);    const value_format_2 = try readGposU16(data, subtable_offset + 6);    const value_size_1 = try gposValueRecordSize(value_format_1);    const value_size_2 = try gposValueRecordSize(value_format_2);    const class_def_1_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 8), 2);    const class_def_2_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 10), 2);    const class_count_1 = try readGposU16(data, subtable_offset + 12);    const class_count_2 = try readGposU16(data, subtable_offset + 14);    const class_def_1 = layout.ClassDef.init(data, class_def_1_offset) catch return error.InvalidGpos;    const class_def_2 = layout.ClassDef.init(data, class_def_2_offset) catch return error.InvalidGpos;    const class_1 = class_def_1.class(data, left_glyph) catch return error.InvalidGpos;    const class_2 = class_def_2.class(data, right_glyph) catch return error.InvalidGpos;    if (class_1 >= class_count_1 or class_2 >= class_count_2) return error.InvalidGpos;    const record_size = value_size_1 + value_size_2;    const class_pair_count = @as(usize, class_count_1) * @as(usize, class_count_2);    if (record_size != 0 and class_pair_count > std.math.maxInt(usize) / record_size) return error.InvalidGpos;    const class_records_offset = subtable_offset + 16;    if (!hasGposBytes(data, class_records_offset, class_pair_count * record_size)) return error.InvalidGpos;    const class_record_index = @as(usize, class_1) * @as(usize, class_count_2) + @as(usize, class_2);    const record = class_records_offset + class_record_index * record_size;    const value_1 = try readGposValueRecord(data, subtable_offset, record, value_format_1);    const value_2 = try readGposValueRecord(data, subtable_offset, record + value_size_1, value_format_2);    return .{ .left = value_1, .right = value_2 };}fn markToBaseAdjustmentSubtable(data: []const u8, subtable_offset: usize, base_glyph: u16, mark_glyph: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 12)) return error.InvalidGpos;    if (try readGposU16(data, subtable_offset) != 1) return error.InvalidGpos;    const mark_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const base_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 4), 2);    const mark_class_count = try readGposU16(data, subtable_offset + 6);    const mark_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 8), 2);    const base_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 10), 2);    const mark_coverage = layout.Coverage.init(data, mark_coverage_offset) catch return error.InvalidGpos;    const base_coverage = layout.Coverage.init(data, base_coverage_offset) catch return error.InvalidGpos;    const mark_index = (mark_coverage.index(data, mark_glyph) catch return error.InvalidGpos) orelse return null;    const base_index = (base_coverage.index(data, base_glyph) catch return error.InvalidGpos) orelse return null;    const mark_count = try readGposU16(data, mark_array_offset);    if (mark_index >= mark_count) return error.InvalidGpos;    const mark_record_offset = mark_array_offset + 2 + @as(usize, mark_index) * 4;    if (!hasGposBytes(data, mark_record_offset, 4)) return error.InvalidGpos;    const mark_class = try readGposU16(data, mark_record_offset);    if (mark_class >= mark_class_count) return error.InvalidGpos;    const mark_anchor_offset = try gposChildOffset(data, mark_array_offset, try readGposU16(data, mark_record_offset + 2), 6);    const mark_anchor = try readGposAnchor(data, mark_anchor_offset);    const base_count = try readGposU16(data, base_array_offset);    if (base_index >= base_count) return error.InvalidGpos;    const base_record_size = @as(usize, mark_class_count) * 2;    const base_records_offset = base_array_offset + 2;    if (base_record_size != 0 and @as(usize, base_count) > (std.math.maxInt(usize) - 2) / base_record_size) return error.InvalidGpos;    if (!hasGposBytes(data, base_records_offset, @as(usize, base_count) * base_record_size)) return error.InvalidGpos;    const base_anchor_record = base_records_offset + @as(usize, base_index) * base_record_size + @as(usize, mark_class) * 2;    const base_anchor_relative = try readGposU16(data, base_anchor_record);    if (base_anchor_relative == 0) return null;    const base_anchor_offset = try gposChildOffset(data, base_array_offset, base_anchor_relative, 6);    const base_anchor = try readGposAnchor(data, base_anchor_offset);    return .{        .x_placement = @as(i32, base_anchor.x) - @as(i32, mark_anchor.x),        .y_placement = @as(i32, base_anchor.y) - @as(i32, mark_anchor.y),    };}fn markToLigatureAdjustmentSubtable(data: []const u8, subtable_offset: usize, ligature_glyph: u16, mark_glyph: u16, component_index: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 12)) return error.InvalidGpos;    if (try readGposU16(data, subtable_offset) != 1) return error.InvalidGpos;    const mark_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const ligature_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 4), 2);    const mark_class_count = try readGposU16(data, subtable_offset + 6);    const mark_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 8), 2);    const ligature_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 10), 2);    const mark_coverage = layout.Coverage.init(data, mark_coverage_offset) catch return error.InvalidGpos;    const ligature_coverage = layout.Coverage.init(data, ligature_coverage_offset) catch return error.InvalidGpos;    const mark_index = (mark_coverage.index(data, mark_glyph) catch return error.InvalidGpos) orelse return null;    const ligature_index = (ligature_coverage.index(data, ligature_glyph) catch return error.InvalidGpos) orelse return null;    const mark_count = try readGposU16(data, mark_array_offset);    if (mark_index >= mark_count) return error.InvalidGpos;    const mark_record_offset = mark_array_offset + 2 + @as(usize, mark_index) * 4;    if (!hasGposBytes(data, mark_record_offset, 4)) return error.InvalidGpos;    const mark_class = try readGposU16(data, mark_record_offset);    if (mark_class >= mark_class_count) return error.InvalidGpos;    const mark_anchor_offset = try gposChildOffset(data, mark_array_offset, try readGposU16(data, mark_record_offset + 2), 6);    const mark_anchor = try readGposAnchor(data, mark_anchor_offset);    const ligature_count = try readGposU16(data, ligature_array_offset);    if (ligature_index >= ligature_count) return error.InvalidGpos;    const ligature_offsets_offset = ligature_array_offset + 2;    if (!hasGposBytes(data, ligature_offsets_offset, @as(usize, ligature_count) * 2)) return error.InvalidGpos;    const ligature_attach_offset = try gposChildOffset(data, ligature_array_offset, try readGposU16(data, ligature_offsets_offset + @as(usize, ligature_index) * 2), 2);    const component_count = try readGposU16(data, ligature_attach_offset);    if (component_index >= component_count) return null;    const component_record_size = @as(usize, mark_class_count) * 2;    const component_records_offset = ligature_attach_offset + 2;    if (component_record_size != 0 and @as(usize, component_count) > (std.math.maxInt(usize) - 2) / component_record_size) return error.InvalidGpos;    if (!hasGposBytes(data, component_records_offset, @as(usize, component_count) * component_record_size)) return error.InvalidGpos;    const ligature_anchor_record = component_records_offset + @as(usize, component_index) * component_record_size + @as(usize, mark_class) * 2;    const ligature_anchor_relative = try readGposU16(data, ligature_anchor_record);    if (ligature_anchor_relative == 0) return null;    const ligature_anchor_offset = try gposChildOffset(data, ligature_attach_offset, ligature_anchor_relative, 6);    const ligature_anchor = try readGposAnchor(data, ligature_anchor_offset);    return .{        .x_placement = @as(i32, ligature_anchor.x) - @as(i32, mark_anchor.x),        .y_placement = @as(i32, ligature_anchor.y) - @as(i32, mark_anchor.y),    };}fn markToMarkAdjustmentSubtable(data: []const u8, subtable_offset: usize, base_mark_glyph: u16, mark_glyph: u16) FontError!?GposValueAdjustment {    if (!hasGposBytes(data, subtable_offset, 12)) return error.InvalidGpos;    if (try readGposU16(data, subtable_offset) != 1) return error.InvalidGpos;    const mark_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 2), 2);    const base_mark_coverage_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 4), 2);    const mark_class_count = try readGposU16(data, subtable_offset + 6);    const mark_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 8), 2);    const base_mark_array_offset = try gposChildOffset(data, subtable_offset, try readGposU16(data, subtable_offset + 10), 2);    const mark_coverage = layout.Coverage.init(data, mark_coverage_offset) catch return error.InvalidGpos;    const base_mark_coverage = layout.Coverage.init(data, base_mark_coverage_offset) catch return error.InvalidGpos;    const mark_index = (mark_coverage.index(data, mark_glyph) catch return error.InvalidGpos) orelse return null;    const base_mark_index = (base_mark_coverage.index(data, base_mark_glyph) catch return error.InvalidGpos) orelse return null;    const mark_count = try readGposU16(data, mark_array_offset);    if (mark_index >= mark_count) return error.InvalidGpos;    const mark_record_offset = mark_array_offset + 2 + @as(usize, mark_index) * 4;    if (!hasGposBytes(data, mark_record_offset, 4)) return error.InvalidGpos;    const mark_class = try readGposU16(data, mark_record_offset);    if (mark_class >= mark_class_count) return error.InvalidGpos;    const mark_anchor_offset = try gposChildOffset(data, mark_array_offset, try readGposU16(data, mark_record_offset + 2), 6);    const mark_anchor = try readGposAnchor(data, mark_anchor_offset);    const base_mark_count = try readGposU16(data, base_mark_array_offset);    if (base_mark_index >= base_mark_count) return error.InvalidGpos;    const base_mark_record_size = @as(usize, mark_class_count) * 2;    const base_mark_records_offset = base_mark_array_offset + 2;    if (base_mark_record_size != 0 and @as(usize, base_mark_count) > (std.math.maxInt(usize) - 2) / base_mark_record_size) return error.InvalidGpos;    if (!hasGposBytes(data, base_mark_records_offset, @as(usize, base_mark_count) * base_mark_record_size)) return error.InvalidGpos;    const base_mark_anchor_record = base_mark_records_offset + @as(usize, base_mark_index) * base_mark_record_size + @as(usize, mark_class) * 2;    const base_mark_anchor_relative = try readGposU16(data, base_mark_anchor_record);    if (base_mark_anchor_relative == 0) return null;    const base_mark_anchor_offset = try gposChildOffset(data, base_mark_array_offset, base_mark_anchor_relative, 6);    const base_mark_anchor = try readGposAnchor(data, base_mark_anchor_offset);    return .{        .x_placement = @as(i32, base_mark_anchor.x) - @as(i32, mark_anchor.x),        .y_placement = @as(i32, base_mark_anchor.y) - @as(i32, mark_anchor.y),    };}const GposAnchor = struct {    x: i16,    y: i16,};fn readGposAnchor(data: []const u8, anchor_offset: usize) FontError!GposAnchor {    const format = try readGposU16(data, anchor_offset);    return switch (format) {        1 => blk: {            if (!hasGposBytes(data, anchor_offset, 6)) return error.InvalidGpos;            break :blk .{                .x = try readGposI16(data, anchor_offset + 2),                .y = try readGposI16(data, anchor_offset + 4),            };        },        2 => blk: {            if (!hasGposBytes(data, anchor_offset, 8)) return error.InvalidGpos;            break :blk .{                .x = try readGposI16(data, anchor_offset + 2),                .y = try readGposI16(data, anchor_offset + 4),            };        },        3 => blk: {            if (!hasGposBytes(data, anchor_offset, 10)) return error.InvalidGpos;            const x_device_offset = try readGposU16(data, anchor_offset + 6);            const y_device_offset = try readGposU16(data, anchor_offset + 8);            if (x_device_offset != 0) _ = try gposChildOffset(data, anchor_offset, x_device_offset, 2);            if (y_device_offset != 0) _ = try gposChildOffset(data, anchor_offset, y_device_offset, 2);            break :blk .{                .x = try readGposI16(data, anchor_offset + 2),                .y = try readGposI16(data, anchor_offset + 4),            };        },        else => error.InvalidGpos,    };}fn gposValueRecordSize(value_format: u16) FontError!usize {    if ((value_format & 0xff00) != 0) return error.InvalidGpos;    var count: usize = 0;    if ((value_format & 0x0001) != 0) count += 1;    if ((value_format & 0x0002) != 0) count += 1;    if ((value_format & 0x0004) != 0) count += 1;    if ((value_format & 0x0008) != 0) count += 1;    if ((value_format & 0x0010) != 0) count += 1;    if ((value_format & 0x0020) != 0) count += 1;    if ((value_format & 0x0040) != 0) count += 1;    if ((value_format & 0x0080) != 0) count += 1;    return count * 2;}fn readGposValueRecord(data: []const u8, value_base_offset: usize, offset: usize, value_format: u16) FontError!GposValueAdjustment {    const size = try gposValueRecordSize(value_format);    if (!hasGposBytes(data, offset, size)) return error.InvalidGpos;    var cursor = offset;    var value = GposValueAdjustment{};    if ((value_format & 0x0001) != 0) {        value.x_placement = try readGposI16(data, cursor);        cursor += 2;    }    if ((value_format & 0x0002) != 0) {        value.y_placement = try readGposI16(data, cursor);        cursor += 2;    }    if ((value_format & 0x0004) != 0) {        value.x_advance = try readGposI16(data, cursor);        cursor += 2;    }    if ((value_format & 0x0008) != 0) {        value.y_advance = try readGposI16(data, cursor);        cursor += 2;    }    if ((value_format & 0x0010) != 0) {        value.x_placement_variation = try readGposDeviceOrVariationIndex(data, value_base_offset, try readGposU16(data, cursor));        cursor += 2;    }    if ((value_format & 0x0020) != 0) {        value.y_placement_variation = try readGposDeviceOrVariationIndex(data, value_base_offset, try readGposU16(data, cursor));        cursor += 2;    }    if ((value_format & 0x0040) != 0) {        value.x_advance_variation = try readGposDeviceOrVariationIndex(data, value_base_offset, try readGposU16(data, cursor));        cursor += 2;    }    if ((value_format & 0x0080) != 0) {        value.y_advance_variation = try readGposDeviceOrVariationIndex(data, value_base_offset, try readGposU16(data, cursor));    }    return value;}fn readGposDeviceOrVariationIndex(data: []const u8, value_base_offset: usize, relative_offset: u16) FontError!u32 {    if (relative_offset == 0) return noGposVariationIndex;    const offset = try gposChildOffset(data, value_base_offset, relative_offset, 6);    const delta_format = try readGposU16(data, offset + 4);    if (delta_format == 0x8000) {        return packGposVariationIndex(            try readGposU16(data, offset),            try readGposU16(data, offset + 2),        );    }    const start_size = try readGposU16(data, offset);    const end_size = try readGposU16(data, offset + 2);    if (end_size < start_size) return error.InvalidGpos;    const bits_per_value: usize = switch (delta_format) {        1 => 2,        2 => 4,        3 => 8,        else => return error.InvalidGpos,    };    const value_count = @as(usize, end_size - start_size) + 1;    const bit_count = value_count * bits_per_value;    const word_count = (bit_count + 15) / 16;    if (word_count > std.math.maxInt(usize) / 2) return error.InvalidGpos;    if (!hasGposBytes(data, offset + 6, word_count * 2)) return error.InvalidGpos;    return noGposVariationIndex;}fn packGposVariationIndex(outer: u16, inner: u16) u32 {    return (@as(u32, outer) << 16) | @as(u32, inner);}test "GPOS extension positioning dispatches wrapped single adjustments" {    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,  'd', 'i', 's',  't',  0, 8,  0, 0,        0, 1,  0,   0,   0,    1,    0, 4,  0, 9,        0, 0,  0,   1,   0,    8,    0, 1,  0, 1,        0, 0,  0,   8,   0,    1,    0, 8,  0, 1,        0, 50, 0,   1,   0,    1,    0, 1,    };    const gpos = try Gpos.init(&data, .{ .offset = 0, .len = data.len });    const adjustment = try gpos.singleAdjustment(&data, .{}, null, 1);    try std.testing.expect(try gpos.hasActiveLookupType(&data, null, .{}, 1));    try std.testing.expectEqual(@as(i32, 50), adjustment.x_placement);    try std.testing.expectEqual(@as(i32, 0), adjustment.x_advance);}test "GPOS offsets stay inside table data" {    const data = @as([10]u8, @splat(0));    try std.testing.expectEqual(@as(usize, 8), try gposOffset(&data, 8));    try std.testing.expectError(error.InvalidGpos, gposOffset(&data, 0));    try std.testing.expectError(error.InvalidGpos, gposOffset(&data, 9));    try std.testing.expectError(error.InvalidGpos, gposOffset(&data, 10));}test "GPOS child offsets stay inside table data" {    const data = @as([10]u8, @splat(0));    try std.testing.expectEqual(@as(usize, 6), try gposChildOffset(&data, 2, 4, 2));    try std.testing.expectError(error.InvalidGpos, gposChildOffset(&data, 2, 0, 2));    try std.testing.expectError(error.InvalidGpos, gposChildOffset(&data, 10, 1, 2));    try std.testing.expectError(error.InvalidGpos, gposChildOffset(&data, 8, 1, 2));}test "GPOS 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 gposLangSysOffset(&data, 0, .{ .script = tag("latn"), .language = tag("TRK ") }));    try std.testing.expectEqual(@as(?usize, 26), try gposLangSysOffset(&data, 0, .{ .script = tag("latn"), .language = tag("DEU ") }));    try std.testing.expectEqual(@as(?usize, null), try gposLangSysOffset(&data, 0, .{ .script = tag("cyrl"), .language = tag("TRK ") }));}test "GPOS value records read placement and advance fields" {    const data = [_]u8{        0xff, 0xfe,        0,    3,        0xff, 0xb0,        0,    4,    };    const value = try readGposValueRecord(&data, 0, 0, 0x000f);    try std.testing.expectEqual(@as(i32, -2), value.x_placement);    try std.testing.expectEqual(@as(i32, 3), value.y_placement);    try std.testing.expectEqual(@as(i32, -80), value.x_advance);    try std.testing.expectEqual(@as(i32, 4), value.y_advance);    try std.testing.expectEqual(@as(usize, 16), try gposValueRecordSize(0x00ff));    try std.testing.expectError(error.InvalidGpos, gposValueRecordSize(0x0100));}test "GPOS value records validate device and variation offsets" {    const data = [_]u8{        0,    10,        0,    6,        0,    14,        0,    10,        0,    13,        0,    1,        0,    0,        0,    2,        0,    3,        0x80, 0,    };    const value = try readGposValueRecord(&data, 0, 0, 0x0091);    try std.testing.expectEqual(@as(i32, 10), value.x_placement);    try std.testing.expectEqual(@as(i32, 0), value.y_placement);    try std.testing.expectEqual(@as(i32, 0), value.x_advance);    try std.testing.expectEqual(@as(i32, 0), value.y_advance);    const malformed = [_]u8{        0, 4,        0, 0,        0, 13,        0, 10,        0, 1,    };    try std.testing.expectError(error.InvalidGpos, readGposValueRecord(&malformed, 0, 0, 0x0010));}test "GPOS single adjustment format 1 uses one value record" {    const data = [_]u8{        0,    1,        0,    10,        0,    3,        0,    7,        0xff, 0xf8,        0,    1,        0,    1,        0,    'A' - 31,    };    const adjustment = (try singleAdjustmentSubtable(&data, 0, 'A' - 31)).?;    try std.testing.expectEqual(@as(i32, 7), adjustment.x_placement);    try std.testing.expectEqual(@as(i32, -8), adjustment.y_placement);    try std.testing.expectEqual(@as(?GposValueAdjustment, null), try singleAdjustmentSubtable(&data, 0, 'B' - 31));}test "GPOS single adjustment format 2 uses coverage index records" {    const data = [_]u8{        0,    2,        0,    20,        0,    7,        0,    2,        0,    20,        0,    30,        0xff, 0xd8,        0xff, 0xf6,        0,    0,        0,    0,        0,    1,        0,    2,        0,    'A' - 31,        0,    'B' - 31,    };    const a_adjustment = (try singleAdjustmentSubtable(&data, 0, 'A' - 31)).?;    try std.testing.expectEqual(@as(i32, 20), a_adjustment.x_placement);    try std.testing.expectEqual(@as(i32, 30), a_adjustment.y_placement);    try std.testing.expectEqual(@as(i32, -40), a_adjustment.x_advance);    const b_adjustment = (try singleAdjustmentSubtable(&data, 0, 'B' - 31)).?;    try std.testing.expectEqual(@as(i32, -10), b_adjustment.x_placement);    try std.testing.expectEqual(@as(i32, 0), b_adjustment.y_placement);    try std.testing.expectEqual(@as(i32, 0), b_adjustment.x_advance);    try std.testing.expectEqual(@as(?GposValueAdjustment, null), try singleAdjustmentSubtable(&data, 0, '!' - 31));}test "GPOS cursive attachment format 1 uses entry and exit anchors" {    const data = [_]u8{        0, 1,        0, 14,        0, 2,        0, 0,        0, 22,        0, 28,        0, 0,        0, 1,        0, 2,        0, 'A' - 31,        0, 'B' - 31,        0, 1,        1, 224,        1, 44,        0, 1,        0, 20,        0, 200,    };    const attachment = (try cursiveAttachmentSubtable(&data, 0, 'A' - 31, 'B' - 31)).?;    try std.testing.expectEqual(@as(i32, 460), attachment.x_anchor_delta);    try std.testing.expectEqual(@as(i32, 100), attachment.y_anchor_delta);    try std.testing.expectEqual(@as(?GposCursiveAttachment, null), try cursiveAttachmentSubtable(&data, 0, 'B' - 31, 'A' - 31));    try std.testing.expectEqual(@as(?GposCursiveAttachment, null), try cursiveAttachmentSubtable(&data, 0, 'A' - 31, '!' - 31));}test "GPOS pair adjustment format 1 uses coverage and second glyph" {    const data = [_]u8{        0,    1,        0,    18,        0,    4,        0,    0,        0,    1,        0,    12,        0,    1,        0,    'V' - 31,        0xff, 0xb0,        0,    1,        0,    1,        0,    'A' - 31,    };    const adjustment = (try pairAdjustmentSubtable(&data, 0, 'A' - 31, 'V' - 31)).?;    try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);    try std.testing.expectEqual(@as(?GposPairAdjustment, null), try pairAdjustmentSubtable(&data, 0, 'A' - 31, 'A' - 31));}test "GPOS pair adjustment format 1 resolves device offsets from PairSet" {    const data = [_]u8{        0,    1,        0,    28,        0,    0x44,        0,    0,        0,    1,        0,    12,        0,    1,        0,    'V' - 31,        0xff, 0xb0,        0,    8,        0,    10,        0,    13,        0,    1,        0,    0,        0,    1,        0,    1,        0,    'A' - 31,    };    const adjustment = (try pairAdjustmentSubtable(&data, 0, 'A' - 31, 'V' - 31)).?;    try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);    try std.testing.expectEqual(@as(i32, 0), adjustment.right.x_advance);}test "GPOS pair adjustment format 2 uses glyph classes" {    const data = [_]u8{        0,    2,        0,    40,        0,    4,        0,    0,        0,    24,        0,    32,        0,    2,        0,    2,        0,    0,        0,    0,        0,    0,        0xff, 0xb0,        0,    1,        0,    'A' - 31,        0,    1,        0,    1,        0,    1,        0,    'V' - 31,        0,    1,        0,    1,        0,    1,        0,    1,        0,    'A' - 31,    };    const adjustment = (try pairAdjustmentSubtable(&data, 0, 'A' - 31, 'V' - 31)).?;    try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);    try std.testing.expect((try pairAdjustmentSubtable(&data, 0, 'A' - 31, 'A' - 31)).?.isZero());    try std.testing.expectEqual(@as(?GposPairAdjustment, null), try pairAdjustmentSubtable(&data, 0, 'B' - 31, 'V' - 31));}test "GPOS mark-to-base attachment format 1 uses anchors" {    const data = [_]u8{        0, 1,        0, 34,        0, 40,        0, 1,        0, 12,        0, 24,        0, 1,        0, 0,        0, 6,        0, 1,        0, 100,        0, 0,        0, 1,        0, 4,        0, 1,        0, 250,        2, 188,        0, 1,        0, 1,        0, '^' - 31,        0, 1,        0, 1,        0, 'A' - 31,    };    const adjustment = (try markToBaseAdjustmentSubtable(&data, 0, 'A' - 31, '^' - 31)).?;    try std.testing.expectEqual(@as(i32, 150), adjustment.x_placement);    try std.testing.expectEqual(@as(i32, 700), adjustment.y_placement);    try std.testing.expect((try markToBaseAdjustmentSubtable(&data, 0, 'B' - 31, '^' - 31)) == null);    try std.testing.expect((try markToBaseAdjustmentSubtable(&data, 0, 'A' - 31, '_' - 31)) == null);}test "GPOS mark-to-ligature attachment format 1 uses component anchors" {    const data = [_]u8{        0, 1,        0, 46,        0, 52,        0, 1,        0, 12,        0, 24,        0, 1,        0, 0,        0, 6,        0, 1,        0, 100,        0, 0,        0, 1,        0, 4,        0, 2,        0, 6,        0, 12,        0, 1,        0, 180,        2, 188,        0, 1,        1, 74,        2, 138,        0, 1,        0, 1,        0, '^' - 31,        0, 1,        0, 1,        0, 96,    };    const first_component = (try markToLigatureAdjustmentSubtable(&data, 0, 96, '^' - 31, 0)).?;    try std.testing.expectEqual(@as(i32, 80), first_component.x_placement);    try std.testing.expectEqual(@as(i32, 700), first_component.y_placement);    const second_component = (try markToLigatureAdjustmentSubtable(&data, 0, 96, '^' - 31, 1)).?;    try std.testing.expectEqual(@as(i32, 230), second_component.x_placement);    try std.testing.expectEqual(@as(i32, 650), second_component.y_placement);    try std.testing.expect((try markToLigatureAdjustmentSubtable(&data, 0, 96, '^' - 31, 2)) == null);    try std.testing.expect((try markToLigatureAdjustmentSubtable(&data, 0, 'A' - 31, '^' - 31, 0)) == null);    try std.testing.expect((try markToLigatureAdjustmentSubtable(&data, 0, 96, '!' - 31, 0)) == null);}test "GPOS mark-to-mark attachment format 1 uses anchors" {    const data = [_]u8{        0, 1,        0, 34,        0, 40,        0, 1,        0, 12,        0, 24,        0, 1,        0, 0,        0, 6,        0, 1,        0, 80,        0, 100,        0, 1,        0, 4,        0, 1,        0, 100,        1, 144,        0, 1,        0, 1,        0, '_' - 31,        0, 1,        0, 1,        0, '^' - 31,    };    const adjustment = (try markToMarkAdjustmentSubtable(&data, 0, '^' - 31, '_' - 31)).?;    try std.testing.expectEqual(@as(i32, 20), adjustment.x_placement);    try std.testing.expectEqual(@as(i32, 300), adjustment.y_placement);    try std.testing.expect((try markToMarkAdjustmentSubtable(&data, 0, '_' - 31, '^' - 31)) == null);    try std.testing.expect((try markToMarkAdjustmentSubtable(&data, 0, '^' - 31, '!' - 31)) == null);}test "GPOS anchor coordinates read supported formats" {    const format_1 = [_]u8{        0,    1,        0xff, 0xfe,        0,    3,    };    const format_2 = [_]u8{        0, 2,        0, 4,        0, 5,        0, 9,    };    const format_3 = [_]u8{        0,    3,        0xff, 0x9c,        0,    200,        0,    0,        0,    0,    };    const anchor_1 = try readGposAnchor(&format_1, 0);    const anchor_2 = try readGposAnchor(&format_2, 0);    const anchor_3 = try readGposAnchor(&format_3, 0);    try std.testing.expectEqual(@as(i16, -2), anchor_1.x);    try std.testing.expectEqual(@as(i16, 3), anchor_1.y);    try std.testing.expectEqual(@as(i16, 4), anchor_2.x);    try std.testing.expectEqual(@as(i16, 5), anchor_2.y);    try std.testing.expectEqual(@as(i16, -100), anchor_3.x);    try std.testing.expectEqual(@as(i16, 200), anchor_3.y);    try std.testing.expectError(error.InvalidGpos, readGposAnchor(&[_]u8{ 0, 4 }, 0));}

Source: lib/filigree/src/font/root.zig:8

zig
pub const gpos = @import("gpos.zig");

Audit

Definitions24
Public names24
Members5
Version26.7.0
Revisiondaab053ee433