tiny.filigree.font.gpos
Defined in font.
API (23)
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Gpos.contextualPairAdjustmentAtGpos.contextualPairAdjustmentAtLookupGpos.cursiveAttachmentGpos.cursiveAttachmentAtLookupGpos.hasActiveContextualPairAdjustmentGpos.hasActiveLookupTypeGpos.hasActiveSingleAdjustmentGpos.initGpos.lookupCountGpos.lookupResolvedTypeGpos.markToBaseAdjustmentGpos.markToBaseAdjustmentAtLookupGpos.markToLigatureAdjustmentGpos.markToLigatureAdjustmentAtLookupGpos.markToMarkAdjustmentGpos.markToMarkAdjustmentAtLookupGpos.pairAdjustmentGpos.pairAdjustmentBeforeGpos.pairAdjustmentBeforeAtLookupGpos.singleAdjustmentGpos.singleAdjustmentAtGpos.singleAdjustmentAtLookup
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
| Definitions | 24 |
|---|---|
| Public names | 24 |
| Members | 5 |
| Version | 26.7.0 |
| Revision | daab053ee433 |