lib/filigree/src/font/face.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const binary = @import("binary.zig");
   3 const cmap_owner = @import("cmap.zig");
   4 const directory = @import("directory.zig");
   5 const gdef_owner = @import("gdef.zig");
   6 const gsub_owner = @import("gsub.zig");
   7 const gpos_owner = @import("gpos.zig");
   8 const kern_owner = @import("kern.zig");
   9 const lookups = @import("lookups.zig");
  10 const math_owner = @import("math.zig");
  11 const metrics_table = @import("metrics.zig");
  12 const model = @import("model.zig");
  13 const names = @import("names.zig");
  14 const variations = @import("variations.zig");
  15 const test_font = @import("../fixture/root.zig");
  16 const fixture_binary = test_font.binary;
  17 
  18 const Cmap = cmap_owner.Cmap;
  19 const FontError = model.FontError;
  20 const Table = binary.Table;
  21 const Tag = model.Tag;
  22 const readI8 = binary.readI8;
  23 const readI16 = binary.readI16;
  24 const readI32 = binary.readI32;
  25 const readU16 = binary.readU16;
  26 const readU24 = binary.readU24;
  27 const readU32 = binary.readU32;
  28 const table = directory.table;
  29 const tableAt = directory.tableAt;
  30 const tableAtTag = directory.tableAtTag;
  31 const tableOptionalAt = directory.optionalAt;
  32 const tableOptionalAtTag = directory.optionalAtTag;
  33 const fontDirectoryOffset = directory.fontDirectoryOffset;
  34 const tag = model.tag;
  35 const tagLiteral = model.tag;
  36 
  37 const GlyphClass = model.GlyphClass;
  38 const LigatureCaret = model.LigatureCaret;
  39 const FeatureRange = model.FeatureRange;
  40 const VariationAxis = model.VariationAxis;
  41 const VariationSetting = model.VariationSetting;
  42 const NormalizedVariation = model.NormalizedVariation;
  43 const LayoutSelection = model.LayoutSelection;
  44 const defaultScriptTag = model.defaultScriptTag;
  45 const featureEnabled = model.featureEnabled;
  46 const fixed16ToFloat = model.fixed16ToFloat;
  47 
  48 const noGposVariationIndex = model.noGposVariationIndex;
  49 const GsubSubstitution = model.GsubSubstitution;
  50 const GposValueAdjustment = model.GposValueAdjustment;
  51 const GposPairAdjustment = model.GposPairAdjustment;
  52 const GposPairAdjustmentBefore = model.GposPairAdjustmentBefore;
  53 const GposContextualPairAdjustment = model.GposContextualPairAdjustment;
  54 const GposSingleAdjustment = model.GposSingleAdjustment;
  55 const GposCursiveAttachment = model.GposCursiveAttachment;
  56 const Gpos = gpos_owner.Gpos;
  57 const Gdef = gdef_owner.Gdef;
  58 const Gsub = gsub_owner.Gsub;
  59 const Kern = kern_owner.Kern;
  60 const Math = math_owner.Math;
  61 const MathConstants = math_owner.Constants;
  62 const MathGlyphAssembly = math_owner.GlyphAssembly;
  63 const MathGlyphVariant = math_owner.GlyphVariant;
  64 const MathValueRecord = math_owner.ValueRecord;
  65 const VerticalMetrics = metrics_table.VerticalMetrics;
  66 const Vorg = metrics_table.Vorg;
  67 const HorizontalMetrics = metrics_table.HorizontalMetrics;
  68 const verticalMetrics = metrics_table.verticalMetrics;
  69 const horizontalTypoMetrics = metrics_table.horizontalTypoMetrics;
  70 const bestNameRecord = names.bestNameRecord;
  71 const utf16BeToUtf8Alloc = names.utf16BeToUtf8Alloc;
  72 const lookup_use_mark_filtering_set = lookups.use_mark_filtering_set;
  73 const lookupGlyphIgnored = lookups.lookupGlyphIgnored;
  74 const Fvar = variations.Fvar;
  75 const Avar = variations.Avar;
  76 const Hvar = variations.Hvar;
  77 const Vvar = variations.Vvar;
  78 const Mvar = variations.Mvar;
  79 const DeltaSetIndex = variations.DeltaSetIndex;
  80 const ItemVariationStore = variations.ItemVariationStore;
  81 const fixed16FromFloat = variations.fixed16FromFloat;
  82 const normalizeVariationAxis = variations.normalizeVariationAxis;
  83 const fixed16ToF2Dot14 = variations.fixed16ToF2Dot14;
  84 const clampDesignMetric = variations.clampDesignMetric;
  85 const clampI32 = variations.clampI32;
  86 
  87 pub const Face = struct {
  88     data: []const u8,
  89     directory_offset: usize,
  90     units_per_em: u16,
  91     ascender: i16,
  92     descender: i16,
  93     line_gap: i16,
  94     typo_metrics: ?HorizontalMetrics,
  95     num_glyphs: u16,
  96     num_h_metrics: u16,
  97     num_v_metrics: u16,
  98     cmap: Cmap,
  99     hmtx: Table,
 100     vmtx: ?Table,
 101     vorg: ?Vorg,
 102     kern: ?Kern,
 103     gsub: ?Gsub,
 104     gpos: ?Gpos,
 105     gdef: ?Gdef,
 106     fvar: ?Fvar,
 107     avar: ?Avar,
 108     hvar: ?Hvar,
 109     vvar: ?Vvar,
 110     mvar: ?Mvar,
 111     math: ?Math,
 112 
 113     pub const init = initImpl;
 114     pub const initAt = initAtImpl;
 115     const initDirectory = initDirectoryImpl;
 116     pub const glyphId = glyphIdImpl;
 117     pub const tableSlice = tableSliceImpl;
 118     pub const tableSliceByTag = tableSliceByTagImpl;
 119     pub const nameUtf8Alloc = nameUtf8AllocImpl;
 120     pub const glyphIdForVariation = glyphIdForVariationImpl;
 121     pub const hasVariationSequences = hasVariationSequencesImpl;
 122     pub const variationAxisCount = variationAxisCountImpl;
 123     pub const variationAxis = variationAxisImpl;
 124     pub const variationAxisFor = variationAxisForImpl;
 125     pub const validateVariations = validateVariationsImpl;
 126     pub const normalizedVariation = normalizedVariationImpl;
 127     pub const normalizedCoordinateAt = normalizedCoordinateAtImpl;
 128     pub const advanceWidth = advanceWidthImpl;
 129     pub const advanceWidthForVariations = advanceWidthForVariationsImpl;
 130     pub const advanceHeight = advanceHeightImpl;
 131     pub const advanceHeightForVariations = advanceHeightForVariationsImpl;
 132     pub const verticalOriginY = verticalOriginYImpl;
 133     pub const metricVariationDelta = metricVariationDeltaImpl;
 134     pub const ascenderForVariations = ascenderForVariationsImpl;
 135     pub const descenderForVariations = descenderForVariationsImpl;
 136     pub const lineGapForVariations = lineGapForVariationsImpl;
 137     pub const heightUnitsForVariations = heightUnitsForVariationsImpl;
 138     pub const kerning = kerningImpl;
 139     pub const hasGpos = hasGposImpl;
 140     pub const gposLookupCount = gposLookupCountImpl;
 141     pub const gposLookupType = gposLookupTypeImpl;
 142     pub const gposVariationValue = gposVariationValueImpl;
 143     const variationContext = variationContextImpl;
 144     pub const hasGsub = hasGsubImpl;
 145     pub const usesAutomaticFractions = usesAutomaticFractionsImpl;
 146     pub const usesArabicJoiningForms = usesArabicJoiningFormsImpl;
 147     pub const gposPairAdjustment = gposPairAdjustmentImpl;
 148     pub const gposPairAdjustmentFor = gposPairAdjustmentForImpl;
 149     pub const gposPairAdjustmentBeforeFor = gposPairAdjustmentBeforeForImpl;
 150     pub const gposPairAdjustmentBeforeForRange = gposPairAdjustmentBeforeForRangeImpl;
 151     pub const gposPairAdjustmentBeforeAtLookupForRange = gposPairAdjustmentBeforeAtLookupForRangeImpl;
 152     pub const gposContextualPairAdjustmentAtFor = gposContextualPairAdjustmentAtForImpl;
 153     pub const gposContextualPairAdjustmentAtForRange = gposContextualPairAdjustmentAtForRangeImpl;
 154     pub const gposContextualPairAdjustmentAtLookupForRange = gposContextualPairAdjustmentAtLookupForRangeImpl;
 155     pub const hasGposContextualPairAdjustmentFor = hasGposContextualPairAdjustmentForImpl;
 156     pub const gposSingleAdjustment = gposSingleAdjustmentImpl;
 157     pub const gposSingleAdjustmentFor = gposSingleAdjustmentForImpl;
 158     pub const gposSingleAdjustmentAtFor = gposSingleAdjustmentAtForImpl;
 159     pub const gposSingleAdjustmentAtForRange = gposSingleAdjustmentAtForRangeImpl;
 160     pub const gposSingleAdjustmentAtLookupForRange = gposSingleAdjustmentAtLookupForRangeImpl;
 161     pub const gposCursiveAttachment = gposCursiveAttachmentImpl;
 162     pub const gposCursiveAttachmentFor = gposCursiveAttachmentForImpl;
 163     pub const gposCursiveAttachmentForRange = gposCursiveAttachmentForRangeImpl;
 164     pub const gposCursiveAttachmentAtLookupForRange = gposCursiveAttachmentAtLookupForRangeImpl;
 165     pub const gposMarkToBaseAdjustment = gposMarkToBaseAdjustmentImpl;
 166     pub const gposMarkToBaseAdjustmentFor = gposMarkToBaseAdjustmentForImpl;
 167     pub const gposMarkToBaseAdjustmentForRange = gposMarkToBaseAdjustmentForRangeImpl;
 168     pub const gposMarkToBaseAdjustmentAtLookupForRange = gposMarkToBaseAdjustmentAtLookupForRangeImpl;
 169     pub const gposMarkToLigatureAdjustment = gposMarkToLigatureAdjustmentImpl;
 170     pub const gposMarkToLigatureAdjustmentFor = gposMarkToLigatureAdjustmentForImpl;
 171     pub const gposMarkToLigatureAdjustmentForRange = gposMarkToLigatureAdjustmentForRangeImpl;
 172     pub const gposMarkToLigatureAdjustmentAtLookupForRange = gposMarkToLigatureAdjustmentAtLookupForRangeImpl;
 173     pub const gposMarkToMarkAdjustment = gposMarkToMarkAdjustmentImpl;
 174     pub const gposMarkToMarkAdjustmentFor = gposMarkToMarkAdjustmentForImpl;
 175     pub const gposMarkToMarkAdjustmentForRange = gposMarkToMarkAdjustmentForRangeImpl;
 176     pub const gposMarkToMarkAdjustmentAtLookupForRange = gposMarkToMarkAdjustmentAtLookupForRangeImpl;
 177     pub const hasGposMarkToBase = hasGposMarkToBaseImpl;
 178     pub const hasGposMarkToBaseFor = hasGposMarkToBaseForImpl;
 179     pub const hasGposMarkToLigature = hasGposMarkToLigatureImpl;
 180     pub const hasGposMarkToLigatureFor = hasGposMarkToLigatureForImpl;
 181     pub const hasGposSingleAdjustment = hasGposSingleAdjustmentImpl;
 182     pub const hasGposSingleAdjustmentFor = hasGposSingleAdjustmentForImpl;
 183     pub const hasGposCursiveAttachment = hasGposCursiveAttachmentImpl;
 184     pub const hasGposCursiveAttachmentFor = hasGposCursiveAttachmentForImpl;
 185     pub const hasGposMarkToMark = hasGposMarkToMarkImpl;
 186     pub const hasGposMarkToMarkFor = hasGposMarkToMarkForImpl;
 187     pub const glyphClass = glyphClassImpl;
 188     pub const ligatureCaretCount = ligatureCaretCountImpl;
 189     pub const ligatureCaret = ligatureCaretImpl;
 190     pub const substituteGlyph = substituteGlyphImpl;
 191     pub const substituteGlyphFor = substituteGlyphForImpl;
 192     pub const gsubLookupCount = gsubLookupCountImpl;
 193     pub const gsubLookupIsReverseFor = gsubLookupIsReverseForImpl;
 194     pub const substitutionAt = substitutionAtImpl;
 195     pub const substitutionAtFor = substitutionAtForImpl;
 196     pub const substitutionAtForRange = substitutionAtForRangeImpl;
 197     pub const substitutionGlyph = substitutionGlyphImpl;
 198     pub const mathConstants = mathConstantsImpl;
 199     pub const mathHorizontalAssemblyAlloc = mathHorizontalAssemblyAllocImpl;
 200     pub const mathHorizontalVariant = mathHorizontalVariantImpl;
 201     pub const mathTopAccentAttachment = mathTopAccentAttachmentImpl;
 202     pub const mathVerticalAssemblyAlloc = mathVerticalAssemblyAllocImpl;
 203     pub const mathVerticalVariant = mathVerticalVariantImpl;
 204     pub const heightUnits = heightUnitsImpl;
 205     const horizontalMetrics = horizontalMetricsImpl;
 206 };
 207 
 208 fn initImpl(data: []const u8) FontError!Face {
 209     return initAtImpl(data, 0);
 210 }
 211 
 212 fn initAtImpl(data: []const u8, face_index: u32) FontError!Face {
 213     const directory_offset = try fontDirectoryOffset(data, face_index);
 214     return initDirectoryImpl(data, directory_offset);
 215 }
 216 
 217 fn initDirectoryImpl(data: []const u8, directory_offset: usize) FontError!Face {
 218     if (data.len < 12) return error.InvalidFont;
 219 
 220     const version = try readU32(data, directory_offset);
 221     if (version != 0x00010000 and version != tagLiteral("true") and version != tagLiteral("OTTO")) {
 222         return error.InvalidFont;
 223     }
 224     const has_cff_outlines = version == tagLiteral("OTTO");
 225 
 226     const num_tables = try readU16(data, directory_offset + 4);
 227     if (directory_offset > data.len or data.len - directory_offset < 12 or @as(usize, num_tables) > (data.len - directory_offset - 12) / 16) return error.InvalidFont;
 228 
 229     const head = try tableAt(data, directory_offset, "head");
 230     const hhea = try tableAt(data, directory_offset, "hhea");
 231     const hmtx = try tableAt(data, directory_offset, "hmtx");
 232     const maxp = try tableAt(data, directory_offset, "maxp");
 233     const cmap_table = try tableAt(data, directory_offset, "cmap");
 234 
 235     if (head.len < 54 or hhea.len < 36 or maxp.len < 6) return error.InvalidFont;
 236 
 237     const units_per_em = try readU16(data, head.offset + 18);
 238     const ascender = try readI16(data, hhea.offset + 4);
 239     const descender = try readI16(data, hhea.offset + 6);
 240     const line_gap = try readI16(data, hhea.offset + 8);
 241     const num_h_metrics = try readU16(data, hhea.offset + 34);
 242     const num_glyphs = try readU16(data, maxp.offset + 4);
 243 
 244     if (num_h_metrics == 0) return error.InvalidFont;
 245 
 246     const vertical_metrics = verticalMetrics(data, directory_offset, num_glyphs);
 247     const vorg = if (tableOptionalAt(data, directory_offset, "VORG")) |vorg_table|
 248         Vorg.init(data, vorg_table) catch null
 249     else
 250         null;
 251     const typo_metrics = horizontalTypoMetrics(data, directory_offset);
 252     const kern = if (has_cff_outlines)
 253         null
 254     else if (tableOptionalAt(data, directory_offset, "kern")) |kern_table|
 255         Kern.init(data, kern_table) catch null
 256     else
 257         null;
 258     const gsub = if (tableOptionalAt(data, directory_offset, "GSUB")) |gsub_table|
 259         Gsub.init(data, gsub_table) catch null
 260     else
 261         null;
 262     const gpos = if (tableOptionalAt(data, directory_offset, "GPOS")) |gpos_table|
 263         Gpos.init(data, gpos_table) catch null
 264     else
 265         null;
 266     const fvar = if (tableOptionalAt(data, directory_offset, "fvar")) |fvar_table|
 267         Fvar.init(data, fvar_table) catch null
 268     else
 269         null;
 270     const gdef = if (tableOptionalAt(data, directory_offset, "GDEF")) |gdef_table|
 271         Gdef.init(data, gdef_table, if (fvar) |variation_table| variation_table.axis_count else null) catch null
 272     else
 273         null;
 274     const avar = if (fvar) |variation_table|
 275         if (tableOptionalAt(data, directory_offset, "avar")) |avar_table|
 276             Avar.init(data, avar_table, variation_table.axis_count) catch null
 277         else
 278             null
 279     else
 280         null;
 281     const hvar = if (fvar) |variation_table|
 282         if (tableOptionalAt(data, directory_offset, "HVAR")) |hvar_table|
 283             Hvar.init(data, hvar_table, variation_table.axis_count) catch null
 284         else
 285             null
 286     else
 287         null;
 288     const vvar = if (fvar) |variation_table|
 289         if (tableOptionalAt(data, directory_offset, "VVAR")) |vvar_table|
 290             Vvar.init(data, vvar_table, variation_table.axis_count) catch null
 291         else
 292             null
 293     else
 294         null;
 295     const mvar = if (fvar) |variation_table|
 296         if (tableOptionalAt(data, directory_offset, "MVAR")) |mvar_table|
 297             Mvar.init(data, mvar_table, variation_table.axis_count) catch null
 298         else
 299             null
 300     else
 301         null;
 302     const math = if (tableOptionalAt(data, directory_offset, "MATH")) |math_table|
 303         Math.init(data, math_table) catch null
 304     else
 305         null;
 306 
 307     return .{
 308         .data = data,
 309         .directory_offset = directory_offset,
 310         .units_per_em = if (units_per_em == 0) 1000 else units_per_em,
 311         .ascender = ascender,
 312         .descender = descender,
 313         .line_gap = line_gap,
 314         .typo_metrics = typo_metrics,
 315         .num_glyphs = num_glyphs,
 316         .num_h_metrics = num_h_metrics,
 317         .num_v_metrics = vertical_metrics.count,
 318         .cmap = try Cmap.init(data, cmap_table),
 319         .hmtx = hmtx,
 320         .vmtx = vertical_metrics.table,
 321         .vorg = vorg,
 322         .kern = kern,
 323         .gsub = gsub,
 324         .gpos = gpos,
 325         .gdef = gdef,
 326         .fvar = fvar,
 327         .avar = avar,
 328         .hvar = hvar,
 329         .vvar = vvar,
 330         .mvar = mvar,
 331         .math = math,
 332     };
 333 }
 334 
 335 fn glyphIdImpl(self: Face, codepoint: u21) u32 {
 336     const glyph = self.cmap.map(self.data, @intCast(codepoint)) catch return 0;
 337     if (glyph >= self.num_glyphs) return 0;
 338     return glyph;
 339 }
 340 
 341 fn tableSliceImpl(self: Face, comptime name: []const u8) ?[]const u8 {
 342     const found = tableOptionalAt(self.data, self.directory_offset, name) orelse return null;
 343     return self.data[found.offset..][0..found.len];
 344 }
 345 
 346 fn tableSliceByTagImpl(self: Face, table_tag: Tag) ?[]const u8 {
 347     const found = tableOptionalAtTag(self.data, self.directory_offset, table_tag) orelse return null;
 348     return self.data[found.offset..][0..found.len];
 349 }
 350 
 351 fn mathConstantsImpl(self: Face) ?MathConstants {
 352     if (self.math) |math| return math.constants;
 353     return null;
 354 }
 355 
 356 fn mathTopAccentAttachmentImpl(self: Face, glyph_id: u32) ?MathValueRecord {
 357     const math = self.math orelse return null;
 358     return math.topAccentAttachment(self.data, glyph_id) catch null;
 359 }
 360 
 361 fn mathVerticalVariantImpl(self: Face, glyph_id: u32, target_measurement: u16) ?MathGlyphVariant {
 362     const math = self.math orelse return null;
 363     return math.verticalVariant(self.data, glyph_id, target_measurement) catch null;
 364 }
 365 
 366 fn mathHorizontalVariantImpl(self: Face, glyph_id: u32, target_measurement: u16) ?MathGlyphVariant {
 367     const math = self.math orelse return null;
 368     return math.horizontalVariant(self.data, glyph_id, target_measurement) catch null;
 369 }
 370 
 371 fn mathVerticalAssemblyAllocImpl(self: Face, allocator: std.mem.Allocator, glyph_id: u32, target_measurement: u16) std.mem.Allocator.Error!?MathGlyphAssembly {
 372     const math = self.math orelse return null;
 373     return math.verticalAssemblyAlloc(allocator, self.data, glyph_id, target_measurement) catch |err| switch (err) {
 374         error.OutOfMemory => return error.OutOfMemory,
 375         else => return null,
 376     };
 377 }
 378 
 379 fn mathHorizontalAssemblyAllocImpl(self: Face, allocator: std.mem.Allocator, glyph_id: u32, target_measurement: u16) std.mem.Allocator.Error!?MathGlyphAssembly {
 380     const math = self.math orelse return null;
 381     return math.horizontalAssemblyAlloc(allocator, self.data, glyph_id, target_measurement) catch |err| switch (err) {
 382         error.OutOfMemory => return error.OutOfMemory,
 383         else => return null,
 384     };
 385 }
 386 
 387 fn nameUtf8AllocImpl(self: Face, allocator: std.mem.Allocator, name_id: u16) !?[]u8 {
 388     const name_table = tableOptionalAt(self.data, self.directory_offset, "name") orelse return null;
 389     const record = (try bestNameRecord(self.data, name_table, name_id)) orelse return null;
 390     return try utf16BeToUtf8Alloc(allocator, record.bytes);
 391 }
 392 
 393 fn glyphIdForVariationImpl(self: Face, codepoint: u21, variation_selector: u21) ?u32 {
 394     const glyph = self.cmap.mapVariation(self.data, @intCast(codepoint), @intCast(variation_selector)) catch return null;
 395     const mapped = glyph orelse return null;
 396     if (mapped >= self.num_glyphs) return 0;
 397     return mapped;
 398 }
 399 
 400 fn hasVariationSequencesImpl(self: Face) bool {
 401     return self.cmap.variation != null;
 402 }
 403 
 404 fn variationAxisCountImpl(self: Face) u16 {
 405     if (self.fvar) |fvar| return fvar.axis_count;
 406     return 0;
 407 }
 408 
 409 fn variationAxisImpl(self: Face, axis_index: usize) ?VariationAxis {
 410     if (self.fvar) |fvar| return fvar.axis(self.data, axis_index) catch null;
 411     return null;
 412 }
 413 
 414 fn variationAxisForImpl(self: Face, axis_tag: Tag) ?VariationAxis {
 415     const fvar = self.fvar orelse return null;
 416     const axis_index = (fvar.axisIndex(self.data, axis_tag) catch return null) orelse return null;
 417     return fvar.axis(self.data, axis_index) catch null;
 418 }
 419 
 420 fn validateVariationsImpl(self: Face, settings: []const VariationSetting) FontError!void {
 421     if (settings.len == 0) return;
 422     const fvar = self.fvar orelse return error.InvalidVariations;
 423     for (settings) |setting| {
 424         const axis_index = (try fvar.axisIndex(self.data, setting.tag)) orelse return error.InvalidVariations;
 425         _ = try self.normalizedCoordinateAt(settings, axis_index);
 426     }
 427 }
 428 
 429 fn normalizedVariationImpl(self: Face, setting: VariationSetting) FontError!?NormalizedVariation {
 430     const fvar = self.fvar orelse return null;
 431     const axis_index = (try fvar.axisIndex(self.data, setting.tag)) orelse return null;
 432     return .{
 433         .tag = setting.tag,
 434         .value = try self.normalizedCoordinateAt(&.{setting}, axis_index),
 435     };
 436 }
 437 
 438 fn normalizedCoordinateAtImpl(self: Face, settings: []const VariationSetting, axis_index: usize) FontError!i16 {
 439     const fvar = self.fvar orelse return error.InvalidVariations;
 440     const axis = try fvar.axis(self.data, axis_index);
 441     var user_value = axis.default_value;
 442     for (settings) |setting| {
 443         if (setting.tag != axis.tag) continue;
 444         user_value = try fixed16FromFloat(setting.value);
 445     }
 446     var normalized = normalizeVariationAxis(axis, user_value);
 447     if (self.avar) |avar| {
 448         normalized = try avar.map(self.data, axis_index, normalized);
 449     }
 450     return fixed16ToF2Dot14(normalized);
 451 }
 452 
 453 fn advanceWidthImpl(self: Face, glyph_id: u32) u16 {
 454     const metric_count: usize = self.num_h_metrics;
 455     const glyph_index = @as(usize, @intCast(glyph_id));
 456     const metric_index = if (glyph_index < metric_count) glyph_index else metric_count - 1;
 457     const offset = self.hmtx.offset + metric_index * 4;
 458     if (offset + 2 > self.data.len) return self.units_per_em;
 459     return readU16(self.data, offset) catch self.units_per_em;
 460 }
 461 
 462 fn advanceWidthForVariationsImpl(self: Face, glyph_id: u32, settings: []const VariationSetting) i32 {
 463     const base = @as(i32, self.advanceWidth(glyph_id));
 464     if (settings.len == 0) return base;
 465     const delta = if (self.hvar) |hvar|
 466         hvar.advanceWidthDelta(self.data, self, settings, glyph_id) catch 0
 467     else
 468         0;
 469     return clampDesignMetric(base, delta);
 470 }
 471 
 472 fn advanceHeightImpl(self: Face, glyph_id: u32) u16 {
 473     const metrics = self.vmtx orelse return self.heightUnits();
 474     const metric_count: usize = self.num_v_metrics;
 475     if (metric_count == 0) return self.heightUnits();
 476     const glyph_index = @as(usize, @intCast(glyph_id));
 477     const metric_index = if (glyph_index < metric_count) glyph_index else metric_count - 1;
 478     if (metric_index > (std.math.maxInt(usize) - metrics.offset) / 4) return self.heightUnits();
 479     const offset = metrics.offset + metric_index * 4;
 480     if (offset + 2 > self.data.len) return self.heightUnits();
 481     return readU16(self.data, offset) catch self.heightUnits();
 482 }
 483 
 484 fn advanceHeightForVariationsImpl(self: Face, glyph_id: u32, settings: []const VariationSetting) i32 {
 485     const base = @as(i32, self.advanceHeight(glyph_id));
 486     if (settings.len == 0) return base;
 487     const delta = if (self.vvar) |vvar|
 488         vvar.advanceHeightDelta(self.data, self, settings, glyph_id) catch 0
 489     else
 490         0;
 491     return clampDesignMetric(base, delta);
 492 }
 493 
 494 fn verticalOriginYImpl(self: Face, glyph_id: u32) ?i16 {
 495     const vorg = self.vorg orelse return null;
 496     return vorg.originY(self.data, glyph_id) catch null;
 497 }
 498 
 499 fn metricVariationDeltaImpl(self: Face, metric_tag: Tag, settings: []const VariationSetting) i32 {
 500     if (settings.len == 0) return 0;
 501     const mvar = self.mvar orelse return 0;
 502     return mvar.valueDelta(self.data, self, settings, metric_tag) catch 0;
 503 }
 504 
 505 fn ascenderForVariationsImpl(self: Face, settings: []const VariationSetting) i32 {
 506     const metrics = self.horizontalMetrics();
 507     return clampI32(@as(i64, metrics.ascender) + @as(i64, self.metricVariationDelta(tagLiteral("hasc"), settings)));
 508 }
 509 
 510 fn descenderForVariationsImpl(self: Face, settings: []const VariationSetting) i32 {
 511     const metrics = self.horizontalMetrics();
 512     return clampI32(@as(i64, metrics.descender) + @as(i64, self.metricVariationDelta(tagLiteral("hdsc"), settings)));
 513 }
 514 
 515 fn lineGapForVariationsImpl(self: Face, settings: []const VariationSetting) i32 {
 516     const metrics = self.horizontalMetrics();
 517     return clampI32(@as(i64, metrics.line_gap) + @as(i64, self.metricVariationDelta(tagLiteral("hlgp"), settings)));
 518 }
 519 
 520 fn heightUnitsForVariationsImpl(self: Face, settings: []const VariationSetting) i32 {
 521     const height = @as(i64, self.ascenderForVariations(settings)) - @as(i64, self.descenderForVariations(settings));
 522     if (height <= 0) return self.units_per_em;
 523     return clampI32(height);
 524 }
 525 
 526 fn kerningImpl(self: Face, left_glyph: u32, right_glyph: u32) i16 {
 527     if (self.kern) |kern| {
 528         return kern.value(self.data, left_glyph, right_glyph) catch 0;
 529     }
 530     return 0;
 531 }
 532 
 533 fn hasGposImpl(self: Face) bool {
 534     return self.gpos != null;
 535 }
 536 
 537 fn gposLookupCountImpl(self: Face) u16 {
 538     if (self.gpos) |gpos| {
 539         return gpos.lookupCount(self.data) catch 0;
 540     }
 541     return 0;
 542 }
 543 
 544 fn gposLookupTypeImpl(self: Face, lookup_index: u16) u16 {
 545     if (self.gpos) |gpos| {
 546         return gpos.lookupResolvedType(self.data, lookup_index) catch 0;
 547     }
 548     return 0;
 549 }
 550 
 551 fn gposVariationValueImpl(self: Face, base: i32, settings: []const VariationSetting, packed_index: u32) i32 {
 552     return gposVariationValueForFace(base, self, settings, packed_index);
 553 }
 554 
 555 fn variationContextImpl(self: *const Face) lookups.VariationContext {
 556     return .{
 557         .ptr = self,
 558         .variation_axis_count = faceVariationAxisCount,
 559         .normalized_coordinate_at = faceNormalizedCoordinateAt,
 560     };
 561 }
 562 
 563 fn hasGsubImpl(self: Face) bool {
 564     return self.gsub != null;
 565 }
 566 
 567 fn usesAutomaticFractionsImpl(self: Face, selection: LayoutSelection) bool {
 568     if (self.gsub) |gsub| {
 569         return gsub.usesAutomaticFractions(self.data, selection) catch false;
 570     }
 571     return false;
 572 }
 573 
 574 fn usesArabicJoiningFormsImpl(self: Face, selection: LayoutSelection) bool {
 575     if (self.gsub) |gsub| {
 576         return gsub.usesArabicJoiningForms(self.data, selection) catch false;
 577     }
 578     return false;
 579 }
 580 
 581 fn gposPairAdjustmentImpl(self: Face, left_glyph: u32, right_glyph: u32) GposPairAdjustment {
 582     return self.gposPairAdjustmentFor(.{}, left_glyph, right_glyph);
 583 }
 584 
 585 fn gposPairAdjustmentForImpl(self: Face, selection: LayoutSelection, left_glyph: u32, right_glyph: u32) GposPairAdjustment {
 586     if (self.gpos) |gpos| {
 587         return gpos.pairAdjustment(self.data, self.variationContext(), selection, self.gdef, left_glyph, right_glyph) catch .{};
 588     }
 589     return .{};
 590 }
 591 
 592 fn gposPairAdjustmentBeforeForImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize) ?GposPairAdjustmentBefore {
 593     return self.gposPairAdjustmentBeforeForRange(selection, glyph_ids, glyph_index, null);
 594 }
 595 
 596 fn gposPairAdjustmentBeforeForRangeImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposPairAdjustmentBefore {
 597     if (self.gpos) |gpos| {
 598         return gpos.pairAdjustmentBefore(self.data, self.variationContext(), selection, self.gdef, glyph_ids, glyph_index, source) catch null;
 599     }
 600     return null;
 601 }
 602 
 603 fn gposPairAdjustmentBeforeAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposPairAdjustmentBefore {
 604     if (self.gpos) |gpos| {
 605         return gpos.pairAdjustmentBeforeAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, glyph_ids, glyph_index, source) catch null;
 606     }
 607     return null;
 608 }
 609 
 610 fn gposContextualPairAdjustmentAtForImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize) ?GposContextualPairAdjustment {
 611     return self.gposContextualPairAdjustmentAtForRange(selection, glyph_ids, glyph_index, null);
 612 }
 613 
 614 fn gposContextualPairAdjustmentAtForRangeImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposContextualPairAdjustment {
 615     if (self.gpos) |gpos| {
 616         return gpos.contextualPairAdjustmentAt(self.data, self.variationContext(), selection, self.gdef, glyph_ids, glyph_index, source) catch null;
 617     }
 618     return null;
 619 }
 620 
 621 fn gposContextualPairAdjustmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposContextualPairAdjustment {
 622     if (self.gpos) |gpos| {
 623         return gpos.contextualPairAdjustmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, glyph_ids, glyph_index, source) catch null;
 624     }
 625     return null;
 626 }
 627 
 628 fn hasGposContextualPairAdjustmentForImpl(self: Face, selection: LayoutSelection) bool {
 629     if (self.gpos) |gpos| {
 630         return gpos.hasActiveContextualPairAdjustment(self.data, self.variationContext(), selection) catch false;
 631     }
 632     return false;
 633 }
 634 
 635 fn gposSingleAdjustmentImpl(self: Face, glyph_id: u32) GposValueAdjustment {
 636     return self.gposSingleAdjustmentFor(.{}, glyph_id);
 637 }
 638 
 639 fn gposSingleAdjustmentForImpl(self: Face, selection: LayoutSelection, glyph_id: u32) GposValueAdjustment {
 640     var glyphs = [_]u32{glyph_id};
 641     const result = self.gposSingleAdjustmentAtFor(selection, &glyphs, 0) orelse return .{};
 642     if (result.target_offset != 0) return .{};
 643     return result.adjustment;
 644 }
 645 
 646 fn gposSingleAdjustmentAtForImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize) ?GposSingleAdjustment {
 647     return self.gposSingleAdjustmentAtForRange(selection, glyph_ids, glyph_index, null);
 648 }
 649 
 650 fn gposSingleAdjustmentAtForRangeImpl(self: Face, selection: LayoutSelection, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposSingleAdjustment {
 651     if (self.gpos) |gpos| {
 652         return gpos.singleAdjustmentAt(self.data, self.variationContext(), selection, self.gdef, glyph_ids, glyph_index, source) catch null;
 653     }
 654     return null;
 655 }
 656 
 657 fn gposSingleAdjustmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) ?GposSingleAdjustment {
 658     if (self.gpos) |gpos| {
 659         return gpos.singleAdjustmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, glyph_ids, glyph_index, source) catch null;
 660     }
 661     return null;
 662 }
 663 
 664 fn gposCursiveAttachmentImpl(self: Face, exit_glyph: u32, entry_glyph: u32) ?GposCursiveAttachment {
 665     return self.gposCursiveAttachmentFor(.{}, exit_glyph, entry_glyph);
 666 }
 667 
 668 fn gposCursiveAttachmentForImpl(self: Face, selection: LayoutSelection, exit_glyph: u32, entry_glyph: u32) ?GposCursiveAttachment {
 669     return self.gposCursiveAttachmentForRange(selection, exit_glyph, entry_glyph, null);
 670 }
 671 
 672 fn gposCursiveAttachmentForRangeImpl(self: Face, selection: LayoutSelection, exit_glyph: u32, entry_glyph: u32, source: ?FeatureRange) ?GposCursiveAttachment {
 673     if (self.gpos) |gpos| {
 674         return gpos.cursiveAttachment(self.data, self.variationContext(), selection, self.gdef, exit_glyph, entry_glyph, source) catch null;
 675     }
 676     return null;
 677 }
 678 
 679 fn gposCursiveAttachmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, exit_glyph: u32, entry_glyph: u32, source: ?FeatureRange) ?GposCursiveAttachment {
 680     if (self.gpos) |gpos| {
 681         return gpos.cursiveAttachmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, exit_glyph, entry_glyph, source) catch null;
 682     }
 683     return null;
 684 }
 685 
 686 fn gposMarkToBaseAdjustmentImpl(self: Face, base_glyph: u32, mark_glyph: u32) ?GposValueAdjustment {
 687     return self.gposMarkToBaseAdjustmentFor(.{}, base_glyph, mark_glyph);
 688 }
 689 
 690 fn gposMarkToBaseAdjustmentForImpl(self: Face, selection: LayoutSelection, base_glyph: u32, mark_glyph: u32) ?GposValueAdjustment {
 691     return self.gposMarkToBaseAdjustmentForRange(selection, base_glyph, mark_glyph, null);
 692 }
 693 
 694 fn gposMarkToBaseAdjustmentForRangeImpl(self: Face, selection: LayoutSelection, base_glyph: u32, mark_glyph: u32, source: ?FeatureRange) ?GposValueAdjustment {
 695     if (self.gpos) |gpos| {
 696         return gpos.markToBaseAdjustment(self.data, self.variationContext(), selection, self.gdef, base_glyph, mark_glyph, source) catch null;
 697     }
 698     return null;
 699 }
 700 
 701 fn gposMarkToBaseAdjustmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, base_glyph: u32, mark_glyph: u32, source: ?FeatureRange) ?GposValueAdjustment {
 702     if (self.gpos) |gpos| {
 703         return gpos.markToBaseAdjustmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, base_glyph, mark_glyph, source) catch null;
 704     }
 705     return null;
 706 }
 707 
 708 fn gposMarkToLigatureAdjustmentImpl(self: Face, ligature_glyph: u32, mark_glyph: u32, component_index: u16) ?GposValueAdjustment {
 709     return self.gposMarkToLigatureAdjustmentFor(.{}, ligature_glyph, mark_glyph, component_index);
 710 }
 711 
 712 fn gposMarkToLigatureAdjustmentForImpl(self: Face, selection: LayoutSelection, ligature_glyph: u32, mark_glyph: u32, component_index: u16) ?GposValueAdjustment {
 713     return self.gposMarkToLigatureAdjustmentForRange(selection, ligature_glyph, mark_glyph, component_index, null);
 714 }
 715 
 716 fn gposMarkToLigatureAdjustmentForRangeImpl(self: Face, selection: LayoutSelection, ligature_glyph: u32, mark_glyph: u32, component_index: u16, source: ?FeatureRange) ?GposValueAdjustment {
 717     if (self.gpos) |gpos| {
 718         return gpos.markToLigatureAdjustment(self.data, self.variationContext(), selection, self.gdef, ligature_glyph, mark_glyph, component_index, source) catch null;
 719     }
 720     return null;
 721 }
 722 
 723 fn gposMarkToLigatureAdjustmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, ligature_glyph: u32, mark_glyph: u32, component_index: u16, source: ?FeatureRange) ?GposValueAdjustment {
 724     if (self.gpos) |gpos| {
 725         return gpos.markToLigatureAdjustmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, ligature_glyph, mark_glyph, component_index, source) catch null;
 726     }
 727     return null;
 728 }
 729 
 730 fn gposMarkToMarkAdjustmentImpl(self: Face, base_mark_glyph: u32, mark_glyph: u32) ?GposValueAdjustment {
 731     return self.gposMarkToMarkAdjustmentFor(.{}, base_mark_glyph, mark_glyph);
 732 }
 733 
 734 fn gposMarkToMarkAdjustmentForImpl(self: Face, selection: LayoutSelection, base_mark_glyph: u32, mark_glyph: u32) ?GposValueAdjustment {
 735     return self.gposMarkToMarkAdjustmentForRange(selection, base_mark_glyph, mark_glyph, null);
 736 }
 737 
 738 fn gposMarkToMarkAdjustmentForRangeImpl(self: Face, selection: LayoutSelection, base_mark_glyph: u32, mark_glyph: u32, source: ?FeatureRange) ?GposValueAdjustment {
 739     if (self.gpos) |gpos| {
 740         return gpos.markToMarkAdjustment(self.data, self.variationContext(), selection, self.gdef, base_mark_glyph, mark_glyph, source) catch null;
 741     }
 742     return null;
 743 }
 744 
 745 fn gposMarkToMarkAdjustmentAtLookupForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, base_mark_glyph: u32, mark_glyph: u32, source: ?FeatureRange) ?GposValueAdjustment {
 746     if (self.gpos) |gpos| {
 747         return gpos.markToMarkAdjustmentAtLookup(self.data, self.variationContext(), selection, self.gdef, lookup_index, base_mark_glyph, mark_glyph, source) catch null;
 748     }
 749     return null;
 750 }
 751 
 752 fn hasGposMarkToBaseImpl(self: Face) bool {
 753     return self.hasGposMarkToBaseFor(.{});
 754 }
 755 
 756 fn hasGposMarkToBaseForImpl(self: Face, selection: LayoutSelection) bool {
 757     if (self.gpos) |gpos| {
 758         return gpos.hasActiveLookupType(self.data, self.variationContext(), selection, 4) catch false;
 759     }
 760     return false;
 761 }
 762 
 763 fn hasGposMarkToLigatureImpl(self: Face) bool {
 764     return self.hasGposMarkToLigatureFor(.{});
 765 }
 766 
 767 fn hasGposMarkToLigatureForImpl(self: Face, selection: LayoutSelection) bool {
 768     if (self.gpos) |gpos| {
 769         return gpos.hasActiveLookupType(self.data, self.variationContext(), selection, 5) catch false;
 770     }
 771     return false;
 772 }
 773 
 774 fn hasGposSingleAdjustmentImpl(self: Face) bool {
 775     return self.hasGposSingleAdjustmentFor(.{});
 776 }
 777 
 778 fn hasGposSingleAdjustmentForImpl(self: Face, selection: LayoutSelection) bool {
 779     if (self.gpos) |gpos| {
 780         return gpos.hasActiveSingleAdjustment(self.data, self.variationContext(), selection) catch false;
 781     }
 782     return false;
 783 }
 784 
 785 fn hasGposCursiveAttachmentImpl(self: Face) bool {
 786     return self.hasGposCursiveAttachmentFor(.{});
 787 }
 788 
 789 fn hasGposCursiveAttachmentForImpl(self: Face, selection: LayoutSelection) bool {
 790     if (self.gpos) |gpos| {
 791         return gpos.hasActiveLookupType(self.data, self.variationContext(), selection, 3) catch false;
 792     }
 793     return false;
 794 }
 795 
 796 fn hasGposMarkToMarkImpl(self: Face) bool {
 797     return self.hasGposMarkToMarkFor(.{});
 798 }
 799 
 800 fn hasGposMarkToMarkForImpl(self: Face, selection: LayoutSelection) bool {
 801     if (self.gpos) |gpos| {
 802         return gpos.hasActiveLookupType(self.data, self.variationContext(), selection, 6) catch false;
 803     }
 804     return false;
 805 }
 806 
 807 fn glyphClassImpl(self: Face, glyph_id: u32) GlyphClass {
 808     if (self.gdef) |gdef| {
 809         return gdef.glyphClass(self.data, glyph_id) catch .unknown;
 810     }
 811     return .unknown;
 812 }
 813 
 814 fn ligatureCaretCountImpl(self: Face, glyph_id: u32) u16 {
 815     if (self.gdef) |gdef| {
 816         return gdef.ligatureCaretCount(self.data, glyph_id) catch 0;
 817     }
 818     return 0;
 819 }
 820 
 821 fn ligatureCaretImpl(self: Face, glyph_id: u32, caret_index: usize) ?LigatureCaret {
 822     if (self.gdef) |gdef| {
 823         return gdef.ligatureCaret(self.data, glyph_id, caret_index) catch null;
 824     }
 825     return null;
 826 }
 827 
 828 fn substituteGlyphImpl(self: Face, glyph_id: u32) u32 {
 829     return self.substituteGlyphFor(.{}, glyph_id);
 830 }
 831 
 832 fn substituteGlyphForImpl(self: Face, selection: LayoutSelection, glyph_id: u32) u32 {
 833     var glyphs = [_]u32{glyph_id};
 834     const lookup_count = self.gsubLookupCount() catch return glyph_id;
 835     for (0..lookup_count) |lookup_index| {
 836         const substitute = self.substitutionAtFor(selection, @intCast(lookup_index), &glyphs, 0) catch return glyph_id;
 837         if (substitute) |result| {
 838             if (result.component_count == 1 and result.replacement_count == 1 and result.glyph_id < self.num_glyphs) return result.glyph_id;
 839         }
 840     }
 841     return glyph_id;
 842 }
 843 
 844 fn gsubLookupCountImpl(self: Face) FontError!u16 {
 845     if (self.gsub) |gsub| return gsub.lookupCount(self.data);
 846     return 0;
 847 }
 848 
 849 fn gsubLookupIsReverseForImpl(self: Face, selection: LayoutSelection, lookup_index: u16) FontError!bool {
 850     if (self.gsub) |gsub| return gsub.lookupIsReverse(self.data, selection, lookup_index);
 851     return false;
 852 }
 853 
 854 fn substitutionAtImpl(self: Face, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution {
 855     return self.substitutionAtFor(.{}, lookup_index, glyph_ids, glyph_index);
 856 }
 857 
 858 fn substitutionAtForImpl(self: Face, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize) FontError!?GsubSubstitution {
 859     return self.substitutionAtForRange(selection, lookup_index, glyph_ids, glyph_index, null);
 860 }
 861 
 862 fn substitutionAtForRangeImpl(self: Face, selection: LayoutSelection, lookup_index: u16, glyph_ids: []const u32, glyph_index: usize, source: ?FeatureRange) FontError!?GsubSubstitution {
 863     if (self.gsub) |gsub| {
 864         const result = try gsub.substitutionAtWithGdef(self.data, self.variationContext(), selection, self.gdef, lookup_index, glyph_ids, glyph_index, source);
 865         if (result) |substitution| {
 866             if (substitution.component_count == 0 or substitution.replacement_count == 0) return null;
 867             for (0..substitution.replacement_count) |replacement_index| {
 868                 _ = self.substitutionGlyph(substitution, replacement_index) orelse return null;
 869             }
 870             return substitution;
 871         }
 872     }
 873     return null;
 874 }
 875 
 876 fn substitutionGlyphImpl(self: Face, substitution: GsubSubstitution, replacement_index: usize) ?u32 {
 877     if (replacement_index >= substitution.replacement_count) return null;
 878     if (substitution.replacement_glyphs_offset == 0) {
 879         if (replacement_index != 0) return null;
 880         return if (substitution.glyph_id < self.num_glyphs) substitution.glyph_id else null;
 881     }
 882     const byte_delta = replacement_index * 2;
 883     if (substitution.replacement_glyphs_offset > std.math.maxInt(usize) - byte_delta) return null;
 884     const offset = substitution.replacement_glyphs_offset + byte_delta;
 885     const glyph = readU16(self.data, offset) catch return null;
 886     return if (glyph < self.num_glyphs) glyph else null;
 887 }
 888 
 889 fn heightUnitsImpl(self: Face) u16 {
 890     const height = @as(i32, self.ascender) - @as(i32, self.descender);
 891     if (height <= 0 or height > std.math.maxInt(u16)) return self.units_per_em;
 892     return @intCast(height);
 893 }
 894 
 895 fn horizontalMetricsImpl(self: Face) HorizontalMetrics {
 896     return self.typo_metrics orelse .{
 897         .ascender = self.ascender,
 898         .descender = self.descender,
 899         .line_gap = self.line_gap,
 900     };
 901 }
 902 
 903 fn faceVariationAxisCount(ptr: *const anyopaque) u16 {
 904     const face: *const Face = @ptrCast(@alignCast(ptr));
 905     return face.variationAxisCount();
 906 }
 907 
 908 fn faceNormalizedCoordinateAt(ptr: *const anyopaque, settings: []const VariationSetting, axis_index: usize) FontError!i16 {
 909     const face: *const Face = @ptrCast(@alignCast(ptr));
 910     return try face.normalizedCoordinateAt(settings, axis_index);
 911 }
 912 
 913 fn gposVariationValueForFace(base: i32, face: Face, settings: []const VariationSetting, packed_index: u32) i32 {
 914     if (packed_index == noGposVariationIndex or settings.len == 0) return base;
 915     const gdef = face.gdef orelse return base;
 916     const index = DeltaSetIndex{
 917         .outer = packed_index >> 16,
 918         .inner = packed_index & 0xffff,
 919     };
 920     const delta = gdef.variationDelta(face.data, face, settings, index) catch return base;
 921     return clampI32(@as(i64, base) + @as(i64, delta));
 922 }
 923 
 924 test "Face rejects empty data" {
 925     try std.testing.expectError(error.InvalidFont, Face.init(&.{}));
 926 }
 927 
 928 test "Face maps BMP codepoints through cmap format 4" {
 929     const bytes = try test_font.create(std.testing.allocator);
 930     defer std.testing.allocator.free(bytes);
 931 
 932     const face = try Face.init(bytes);
 933     try std.testing.expectEqual(@as(u16, 1000), face.units_per_em);
 934     try std.testing.expectEqual(@as(u16, 500), face.advanceWidth(face.glyphId('A')));
 935     try std.testing.expectEqual(@as(u32, 'A' - 31), face.glyphId('A'));
 936     try std.testing.expectEqual(@as(u32, 96), face.glyphId(0x25cc));
 937     try std.testing.expectEqual(@as(u32, 0), face.glyphId(0x20ac));
 938 }
 939 
 940 test "Face opens indexed OpenType collection faces" {
 941     const bytes = try test_font.createCollection(std.testing.allocator);
 942     defer std.testing.allocator.free(bytes);
 943 
 944     const first = try Face.init(bytes);
 945     const second = try Face.initAt(bytes, 1);
 946     const a = first.glyphId('A');
 947 
 948     try std.testing.expectEqual(a, first.substituteGlyph(a));
 949     try std.testing.expectEqual(second.glyphId('Z'), second.substituteGlyph(second.glyphId('A')));
 950     try std.testing.expectError(error.InvalidFont, Face.initAt(bytes, 2));
 951 
 952     const plain = try test_font.create(std.testing.allocator);
 953     defer std.testing.allocator.free(plain);
 954     try std.testing.expectError(error.InvalidFont, Face.initAt(plain, 1));
 955 }
 956 
 957 test "Face exposes table slices for indexed faces" {
 958     const bytes = try test_font.createCollection(std.testing.allocator);
 959     defer std.testing.allocator.free(bytes);
 960 
 961     const second = try Face.initAt(bytes, 1);
 962     const head = second.tableSlice("head").?;
 963     const by_tag = second.tableSliceByTag(tag("head")).?;
 964 
 965     try std.testing.expectEqual(@as(usize, 54), head.len);
 966     try std.testing.expectEqualSlices(u8, head, by_tag);
 967     try std.testing.expectEqual(@as(?[]const u8, null), second.tableSlice("COLR"));
 968 }
 969 
 970 test "Face decodes UTF-16BE name records" {
 971     const bytes = try test_font.create(std.testing.allocator);
 972     defer std.testing.allocator.free(bytes);
 973 
 974     const face = try Face.init(bytes);
 975     const family = (try face.nameUtf8Alloc(std.testing.allocator, 1)).?;
 976     defer std.testing.allocator.free(family);
 977     const full = (try face.nameUtf8Alloc(std.testing.allocator, 4)).?;
 978     defer std.testing.allocator.free(full);
 979 
 980     try std.testing.expectEqualStrings("Tiny Fixture", family);
 981     try std.testing.expectEqualStrings("Tiny Fixture Regular", full);
 982     try std.testing.expectEqual(@as(?[]u8, null), try face.nameUtf8Alloc(std.testing.allocator, 6));
 983 }
 984 
 985 test "Face reads vertical advance metrics from vmtx" {
 986     const bytes = try test_font.createWithVerticalMetrics(std.testing.allocator);
 987     defer std.testing.allocator.free(bytes);
 988 
 989     const face = try Face.init(bytes);
 990 
 991     try std.testing.expectEqual(@as(u16, 700), face.advanceHeight(face.glyphId('A')));
 992     try std.testing.expectEqual(@as(u16, 900), face.advanceHeight(face.glyphId('B')));
 993     try std.testing.expectEqual(@as(u16, 700), face.advanceHeight(face.glyphId('!')));
 994 }
 995 
 996 test "Face reads vertical origin metrics from VORG" {
 997     const bytes = try test_font.createWithVerticalOrigins(std.testing.allocator);
 998     defer std.testing.allocator.free(bytes);
 999 
1000     const face = try Face.init(bytes);
1001 
1002     try std.testing.expectEqual(@as(?i16, 760), face.verticalOriginY(face.glyphId('A')));
1003     try std.testing.expectEqual(@as(?i16, 880), face.verticalOriginY(face.glyphId('B')));
1004 }
1005 
1006 test "Face falls back to line height without vertical metrics" {
1007     const bytes = try test_font.create(std.testing.allocator);
1008     defer std.testing.allocator.free(bytes);
1009 
1010     const face = try Face.init(bytes);
1011 
1012     try std.testing.expectEqual(@as(u16, 1000), face.advanceHeight(face.glyphId('A')));
1013 }
1014 
1015 test "Face maps non-default Unicode variation sequences through cmap format 14" {
1016     const bytes = try test_font.createWithVariationSequences(std.testing.allocator);
1017     defer std.testing.allocator.free(bytes);
1018 
1019     const face = try Face.init(bytes);
1020 
1021     try std.testing.expectEqual(@as(u32, 'A' - 31), face.glyphId('A'));
1022     try std.testing.expectEqual(@as(?u32, 96), face.glyphIdForVariation('A', 0xfe0f));
1023     try std.testing.expectEqual(@as(?u32, null), face.glyphIdForVariation('B', 0xfe0f));
1024     try std.testing.expectEqual(@as(?u32, null), face.glyphIdForVariation('A', 0xfe0e));
1025 }
1026 
1027 test "Face reads fvar variation axes" {
1028     const bytes = try test_font.createWithVariations(std.testing.allocator);
1029     defer std.testing.allocator.free(bytes);
1030 
1031     const face = try Face.init(bytes);
1032     try std.testing.expectEqual(@as(u16, 2), face.variationAxisCount());
1033 
1034     const weight = face.variationAxis(0).?;
1035     try std.testing.expectEqual(tag("wght"), weight.tag);
1036     try std.testing.expectEqual(@as(i32, 100 << 16), weight.min_value);
1037     try std.testing.expectEqual(@as(i32, 400 << 16), weight.default_value);
1038     try std.testing.expectEqual(@as(i32, 900 << 16), weight.max_value);
1039     try std.testing.expectEqual(@as(u16, 0), weight.flags);
1040     try std.testing.expectEqual(@as(u16, 256), weight.name_id);
1041     try std.testing.expectEqual(@as(f32, 400), fixed16ToFloat(weight.default_value));
1042 
1043     const width = face.variationAxisFor(tag("wdth")).?;
1044     try std.testing.expectEqual(@as(i32, 50 << 16), width.min_value);
1045     try std.testing.expectEqual(@as(i32, 100 << 16), width.default_value);
1046     try std.testing.expectEqual(@as(i32, 200 << 16), width.max_value);
1047     try std.testing.expectEqual(@as(u16, 1), width.flags);
1048     try std.testing.expectEqual(@as(?VariationAxis, null), face.variationAxis(2));
1049     try std.testing.expectEqual(@as(?VariationAxis, null), face.variationAxisFor(tag("opsz")));
1050 }
1051 
1052 test "Face normalizes fvar variation coordinates" {
1053     const bytes = try test_font.createWithVariations(std.testing.allocator);
1054     defer std.testing.allocator.free(bytes);
1055 
1056     const face = try Face.init(bytes);
1057     try std.testing.expectEqual(@as(i16, 0), try face.normalizedCoordinateAt(&.{}, 0));
1058     try std.testing.expectEqual(@as(i16, -8192), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wght"), .value = 250.0 }}, 0));
1059     try std.testing.expectEqual(@as(i16, 8192), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wght"), .value = 650.0 }}, 0));
1060     try std.testing.expectEqual(@as(i16, 16384), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wght"), .value = 1200.0 }}, 0));
1061     try std.testing.expectEqual(@as(i16, -16384), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wdth"), .value = 25.0 }}, 1));
1062 
1063     const normalized = (try face.normalizedVariation(.{ .tag = tag("wght"), .value = 650.0 })).?;
1064     try std.testing.expectEqual(tag("wght"), normalized.tag);
1065     try std.testing.expectEqual(@as(i16, 8192), normalized.value);
1066     try std.testing.expectEqual(@as(?NormalizedVariation, null), try face.normalizedVariation(.{ .tag = tag("opsz"), .value = 12.0 }));
1067     try std.testing.expectError(error.InvalidVariations, face.validateVariations(&.{.{ .tag = tag("opsz"), .value = 12.0 }}));
1068     try std.testing.expectError(error.InvalidVariations, face.validateVariations(&.{.{ .tag = tag("wght"), .value = std.math.nan(f32) }}));
1069 }
1070 
1071 test "Face applies avar variation coordinate remapping" {
1072     const bytes = try test_font.createWithAxisVariations(std.testing.allocator);
1073     defer std.testing.allocator.free(bytes);
1074 
1075     const face = try Face.init(bytes);
1076     try std.testing.expectEqual(@as(i16, -4096), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wght"), .value = 250.0 }}, 0));
1077     try std.testing.expectEqual(@as(i16, 12288), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wght"), .value = 650.0 }}, 0));
1078     try std.testing.expectEqual(@as(i16, 8192), try face.normalizedCoordinateAt(&.{.{ .tag = tag("wdth"), .value = 150.0 }}, 1));
1079 }
1080 
1081 test "Face applies HVAR advance width deltas" {
1082     const bytes = try test_font.createWithHorizontalMetricVariations(std.testing.allocator);
1083     defer std.testing.allocator.free(bytes);
1084 
1085     const face = try Face.init(bytes);
1086     const a = face.glyphId('A');
1087     const v = face.glyphId('V');
1088 
1089     try std.testing.expectEqual(@as(u16, 500), face.advanceWidth(a));
1090     try std.testing.expectEqual(@as(i32, 500), face.advanceWidthForVariations(a, &.{}));
1091     try std.testing.expectEqual(@as(i32, 500), face.advanceWidthForVariations(a, &.{.{ .tag = tag("wght"), .value = 400.0 }}));
1092     try std.testing.expectEqual(@as(i32, 550), face.advanceWidthForVariations(a, &.{.{ .tag = tag("wght"), .value = 650.0 }}));
1093     try std.testing.expectEqual(@as(i32, 600), face.advanceWidthForVariations(a, &.{.{ .tag = tag("wght"), .value = 900.0 }}));
1094     try std.testing.expectEqual(@as(i32, 500), face.advanceWidthForVariations(v, &.{.{ .tag = tag("wght"), .value = 900.0 }}));
1095 }
1096 
1097 test "Face applies avar-normalized coordinates to HVAR" {
1098     const bytes = try test_font.createWithHorizontalMetricAxisVariations(std.testing.allocator);
1099     defer std.testing.allocator.free(bytes);
1100 
1101     const face = try Face.init(bytes);
1102     const a = face.glyphId('A');
1103 
1104     try std.testing.expectEqual(@as(i32, 575), face.advanceWidthForVariations(a, &.{.{ .tag = tag("wght"), .value = 650.0 }}));
1105 }
1106 
1107 test "Face applies VVAR advance height deltas" {
1108     const bytes = try test_font.createWithVerticalMetricVariations(std.testing.allocator);
1109     defer std.testing.allocator.free(bytes);
1110 
1111     const face = try Face.init(bytes);
1112     const a = face.glyphId('A');
1113     const b = face.glyphId('B');
1114 
1115     try std.testing.expectEqual(@as(u16, 700), face.advanceHeight(a));
1116     try std.testing.expectEqual(@as(i32, 700), face.advanceHeightForVariations(a, &.{}));
1117     try std.testing.expectEqual(@as(i32, 700), face.advanceHeightForVariations(a, &.{.{ .tag = tag("wght"), .value = 400.0 }}));
1118     try std.testing.expectEqual(@as(i32, 750), face.advanceHeightForVariations(a, &.{.{ .tag = tag("wght"), .value = 650.0 }}));
1119     try std.testing.expectEqual(@as(i32, 800), face.advanceHeightForVariations(a, &.{.{ .tag = tag("wght"), .value = 900.0 }}));
1120     try std.testing.expectEqual(@as(i32, 900), face.advanceHeightForVariations(b, &.{.{ .tag = tag("wght"), .value = 900.0 }}));
1121 }
1122 
1123 test "Face applies avar-normalized coordinates to VVAR" {
1124     const bytes = try test_font.createWithVerticalMetricAxisVariations(std.testing.allocator);
1125     defer std.testing.allocator.free(bytes);
1126 
1127     const face = try Face.init(bytes);
1128     const a = face.glyphId('A');
1129 
1130     try std.testing.expectEqual(@as(i32, 775), face.advanceHeightForVariations(a, &.{.{ .tag = tag("wght"), .value = 650.0 }}));
1131 }
1132 
1133 test "Face applies MVAR horizontal metric deltas" {
1134     const bytes = try test_font.createWithMetricVariations(std.testing.allocator);
1135     defer std.testing.allocator.free(bytes);
1136 
1137     const face = try Face.init(bytes);
1138 
1139     try std.testing.expectEqual(@as(i32, 800), face.ascenderForVariations(&.{}));
1140     try std.testing.expectEqual(@as(i32, 800), face.ascenderForVariations(&.{.{ .tag = tag("wght"), .value = 400.0 }}));
1141     try std.testing.expectEqual(@as(i32, 860), face.ascenderForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1142     try std.testing.expectEqual(@as(i32, 920), face.ascenderForVariations(&.{.{ .tag = tag("wght"), .value = 900.0 }}));
1143 
1144     try std.testing.expectEqual(@as(i32, -200), face.descenderForVariations(&.{}));
1145     try std.testing.expectEqual(@as(i32, -240), face.descenderForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1146     try std.testing.expectEqual(@as(i32, -280), face.descenderForVariations(&.{.{ .tag = tag("wght"), .value = 900.0 }}));
1147 
1148     try std.testing.expectEqual(@as(i32, 40), face.lineGapForVariations(&.{}));
1149     try std.testing.expectEqual(@as(i32, 50), face.lineGapForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1150     try std.testing.expectEqual(@as(i32, 60), face.lineGapForVariations(&.{.{ .tag = tag("wght"), .value = 900.0 }}));
1151 
1152     try std.testing.expectEqual(@as(i32, 0), face.metricVariationDelta(tag("vasc"), &.{.{ .tag = tag("wght"), .value = 900.0 }}));
1153     try std.testing.expectEqual(@as(i32, 1000), face.heightUnitsForVariations(&.{}));
1154     try std.testing.expectEqual(@as(i32, 1100), face.heightUnitsForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1155     try std.testing.expectEqual(@as(i32, 1200), face.heightUnitsForVariations(&.{.{ .tag = tag("wght"), .value = 900.0 }}));
1156 }
1157 
1158 test "Face applies avar-normalized coordinates to MVAR" {
1159     const bytes = try test_font.createWithMetricAxisVariations(std.testing.allocator);
1160     defer std.testing.allocator.free(bytes);
1161 
1162     const face = try Face.init(bytes);
1163 
1164     try std.testing.expectEqual(@as(i32, 890), face.ascenderForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1165     try std.testing.expectEqual(@as(i32, -260), face.descenderForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1166     try std.testing.expectEqual(@as(i32, 55), face.lineGapForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1167     try std.testing.expectEqual(@as(i32, 1150), face.heightUnitsForVariations(&.{.{ .tag = tag("wght"), .value = 650.0 }}));
1168 }
1169 
1170 test "Face reads classic kern format 0 pairs" {
1171     const bytes = try test_font.createWithKern(std.testing.allocator);
1172     defer std.testing.allocator.free(bytes);
1173 
1174     const face = try Face.init(bytes);
1175     const a = face.glyphId('A');
1176     const v = face.glyphId('V');
1177 
1178     try std.testing.expectEqual(@as(i16, -80), face.kerning(a, v));
1179     try std.testing.expectEqual(@as(i16, 0), face.kerning(v, a));
1180 }
1181 
1182 test "Face applies GSUB required single substitution" {
1183     const bytes = try test_font.createWithGsubSingleSubstitution(std.testing.allocator);
1184     defer std.testing.allocator.free(bytes);
1185 
1186     const face = try Face.init(bytes);
1187     const a = face.glyphId('A');
1188     const z = face.glyphId('Z');
1189 
1190     try std.testing.expectEqual(z, face.substituteGlyph(a));
1191     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyph(face.glyphId('B')));
1192 }
1193 
1194 test "Face applies GSUB single substitutions with mark filters" {
1195     const bytes = try test_font.createWithGsubSingleIgnoreMarks(std.testing.allocator);
1196     defer std.testing.allocator.free(bytes);
1197 
1198     const face = try Face.init(bytes);
1199     const a = face.glyphId('A');
1200     const mark = face.glyphId('^');
1201     const glyphs = [_]u32{ a, mark };
1202 
1203     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1204     try std.testing.expectEqual(face.glyphId('Z'), substitution.glyph_id);
1205     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 1));
1206 }
1207 
1208 test "Face applies GSUB alternate substitutions" {
1209     const bytes = try test_font.createWithGsubAlternateSubstitution(std.testing.allocator);
1210     defer std.testing.allocator.free(bytes);
1211 
1212     const face = try Face.init(bytes);
1213     const a = face.glyphId('A');
1214     const glyphs = [_]u32{a};
1215     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1216 
1217     try std.testing.expectEqual(@as(u32, 96), face.substituteGlyph(a));
1218     try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id);
1219     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1220     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyph(face.glyphId('B')));
1221 }
1222 
1223 test "Face applies GSUB contextual substitutions" {
1224     const bytes = try test_font.createWithGsubContextualSubstitution(std.testing.allocator);
1225     defer std.testing.allocator.free(bytes);
1226 
1227     const face = try Face.init(bytes);
1228     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1229     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1230     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1231 
1232     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1233     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1234     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1235     try std.testing.expectEqual(@as(usize, 1), substitution.target_offset);
1236     try std.testing.expectEqual(@as(usize, 3), substitution.skip_count);
1237     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &mismatch, 0));
1238     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1239 }
1240 
1241 test "Face applies GSUB class contextual substitutions" {
1242     const bytes = try test_font.createWithGsubClassContextualSubstitution(std.testing.allocator);
1243     defer std.testing.allocator.free(bytes);
1244 
1245     const face = try Face.init(bytes);
1246     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1247     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1248     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1249 
1250     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1251     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1252     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1253     try std.testing.expectEqual(@as(usize, 1), substitution.target_offset);
1254     try std.testing.expectEqual(@as(usize, 3), substitution.skip_count);
1255     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyph(face.glyphId('B')));
1256     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &mismatch, 0));
1257     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1258 }
1259 
1260 test "Face applies GSUB coverage contextual substitutions" {
1261     const bytes = try test_font.createWithGsubCoverageContextualSubstitution(std.testing.allocator);
1262     defer std.testing.allocator.free(bytes);
1263 
1264     const face = try Face.init(bytes);
1265     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1266     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1267     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1268 
1269     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1270     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1271     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1272     try std.testing.expectEqual(@as(usize, 1), substitution.target_offset);
1273     try std.testing.expectEqual(@as(usize, 3), substitution.skip_count);
1274     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &mismatch, 0));
1275     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1276 }
1277 
1278 test "Face applies GSUB chained contextual substitutions" {
1279     const bytes = try test_font.createWithGsubChainedContextualSubstitution(std.testing.allocator);
1280     defer std.testing.allocator.free(bytes);
1281 
1282     const face = try Face.init(bytes);
1283     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1284     const missing_backtrack = [_]u32{ face.glyphId('!'), face.glyphId('B'), face.glyphId('C') };
1285     const missing_lookahead = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1286     const substitution = (try face.substitutionAt(0, &glyphs, 1)).?;
1287 
1288     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1289     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1290     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1291     try std.testing.expectEqual(@as(usize, 0), substitution.target_offset);
1292     try std.testing.expectEqual(@as(usize, 1), substitution.skip_count);
1293     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 0));
1294     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_backtrack, 1));
1295     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_lookahead, 1));
1296     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1297 }
1298 
1299 test "Face applies GSUB chained simple contextual substitutions" {
1300     const bytes = try test_font.createWithGsubChainedSimpleContextualSubstitution(std.testing.allocator);
1301     defer std.testing.allocator.free(bytes);
1302 
1303     const face = try Face.init(bytes);
1304     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1305     const missing_backtrack = [_]u32{ face.glyphId('!'), face.glyphId('B'), face.glyphId('C') };
1306     const missing_lookahead = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1307     const substitution = (try face.substitutionAt(0, &glyphs, 1)).?;
1308 
1309     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1310     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1311     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1312     try std.testing.expectEqual(@as(usize, 0), substitution.target_offset);
1313     try std.testing.expectEqual(@as(usize, 1), substitution.skip_count);
1314     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyph(face.glyphId('B')));
1315     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 0));
1316     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_backtrack, 1));
1317     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_lookahead, 1));
1318     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1319 }
1320 
1321 test "Face applies GSUB chained class contextual substitutions" {
1322     const bytes = try test_font.createWithGsubChainedClassContextualSubstitution(std.testing.allocator);
1323     defer std.testing.allocator.free(bytes);
1324 
1325     const face = try Face.init(bytes);
1326     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1327     const missing_backtrack = [_]u32{ face.glyphId('!'), face.glyphId('B'), face.glyphId('C') };
1328     const missing_lookahead = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1329     const substitution = (try face.substitutionAt(0, &glyphs, 1)).?;
1330 
1331     try std.testing.expectEqual(@as(u16, 2), try face.gsubLookupCount());
1332     try std.testing.expectEqual(@as(u32, face.glyphId('Z')), substitution.glyph_id);
1333     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1334     try std.testing.expectEqual(@as(usize, 0), substitution.target_offset);
1335     try std.testing.expectEqual(@as(usize, 1), substitution.skip_count);
1336     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyph(face.glyphId('B')));
1337     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 0));
1338     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_backtrack, 1));
1339     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &missing_lookahead, 1));
1340     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(1, &glyphs, 1));
1341 }
1342 
1343 test "Face exposes GSUB default ligature substitutions" {
1344     const bytes = try test_font.createWithGsubLigature(std.testing.allocator);
1345     defer std.testing.allocator.free(bytes);
1346 
1347     const face = try Face.init(bytes);
1348     const glyphs = [_]u32{ face.glyphId('f'), face.glyphId('i') };
1349     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1350 
1351     try std.testing.expectEqual(@as(u16, 1), try face.gsubLookupCount());
1352     try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id);
1353     try std.testing.expectEqual(@as(u16, 2), substitution.component_count);
1354     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 1));
1355 }
1356 
1357 test "Face applies GSUB localized forms by default" {
1358     const bytes = try test_font.createWithGsubLocalizedForms(std.testing.allocator);
1359     defer std.testing.allocator.free(bytes);
1360 
1361     const face = try Face.init(bytes);
1362     const glyphs = [_]u32{face.glyphId('A')};
1363     const disabled = LayoutSelection{ .features = &.{.{ .tag = tag("locl"), .value = 0 }} };
1364 
1365     try std.testing.expectEqual(face.glyphId('Z'), (try face.substitutionAtFor(.{}, 0, &glyphs, 0)).?.glyph_id);
1366     try std.testing.expectEqual(face.glyphId('Z'), (try face.substitutionAtFor(.{ .vertical = true }, 0, &glyphs, 0)).?.glyph_id);
1367     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(disabled, 0, &glyphs, 0));
1368 }
1369 
1370 test "Face applies GSUB contextual ligatures as a horizontal default" {
1371     const bytes = try test_font.createWithGsubContextualLigature(std.testing.allocator);
1372     defer std.testing.allocator.free(bytes);
1373 
1374     const face = try Face.init(bytes);
1375     const glyphs = [_]u32{ face.glyphId('f'), face.glyphId('i') };
1376     const disabled = LayoutSelection{ .features = &.{.{ .tag = tag("clig"), .value = 0 }} };
1377     const vertical_enabled = LayoutSelection{ .vertical = true, .features = &.{.{ .tag = tag("clig"), .value = 1 }} };
1378 
1379     const substitution = (try face.substitutionAtFor(.{}, 0, &glyphs, 0)).?;
1380     try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id);
1381     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{ .vertical = true }, 0, &glyphs, 0));
1382     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(disabled, 0, &glyphs, 0));
1383     try std.testing.expectEqual(@as(u32, 96), (try face.substitutionAtFor(vertical_enabled, 0, &glyphs, 0)).?.glyph_id);
1384 }
1385 
1386 test "Face applies GSUB contextual alternates as horizontal defaults" {
1387     const calt_bytes = try test_font.createWithGsubContextualAlternates(std.testing.allocator);
1388     defer std.testing.allocator.free(calt_bytes);
1389     const rclt_bytes = try test_font.createWithGsubRequiredContextualAlternates(std.testing.allocator);
1390     defer std.testing.allocator.free(rclt_bytes);
1391 
1392     const calt_face = try Face.init(calt_bytes);
1393     const rclt_face = try Face.init(rclt_bytes);
1394     const calt_glyphs = [_]u32{ calt_face.glyphId('A'), calt_face.glyphId('B'), calt_face.glyphId('C') };
1395     const rclt_glyphs = [_]u32{ rclt_face.glyphId('A'), rclt_face.glyphId('B'), rclt_face.glyphId('C') };
1396     const disabled_calt = LayoutSelection{ .features = &.{.{ .tag = tag("calt"), .value = 0 }} };
1397     const disabled_rclt = LayoutSelection{ .features = &.{.{ .tag = tag("rclt"), .value = 0 }} };
1398 
1399     try std.testing.expectEqual(calt_face.glyphId('Z'), (try calt_face.substitutionAtFor(.{}, 0, &calt_glyphs, 0)).?.glyph_id);
1400     try std.testing.expectEqual(rclt_face.glyphId('Z'), (try rclt_face.substitutionAtFor(.{}, 0, &rclt_glyphs, 0)).?.glyph_id);
1401     try std.testing.expectEqual(@as(?GsubSubstitution, null), try calt_face.substitutionAtFor(.{ .vertical = true }, 0, &calt_glyphs, 0));
1402     try std.testing.expectEqual(@as(?GsubSubstitution, null), try rclt_face.substitutionAtFor(.{ .vertical = true }, 0, &rclt_glyphs, 0));
1403     try std.testing.expectEqual(@as(?GsubSubstitution, null), try calt_face.substitutionAtFor(disabled_calt, 0, &calt_glyphs, 0));
1404     try std.testing.expectEqual(rclt_face.glyphId('Z'), (try rclt_face.substitutionAtFor(disabled_rclt, 0, &rclt_glyphs, 0)).?.glyph_id);
1405 }
1406 
1407 test "Face applies GSUB direction alternates by resolved direction" {
1408     const ltra_bytes = try test_font.createWithGsubLeftToRightAlternates(std.testing.allocator);
1409     defer std.testing.allocator.free(ltra_bytes);
1410     const ltrm_bytes = try test_font.createWithGsubLeftToRightMirroredForms(std.testing.allocator);
1411     defer std.testing.allocator.free(ltrm_bytes);
1412     const rtla_bytes = try test_font.createWithGsubRightToLeftAlternates(std.testing.allocator);
1413     defer std.testing.allocator.free(rtla_bytes);
1414     const rtlm_bytes = try test_font.createWithGsubRightToLeftMirroredForms(std.testing.allocator);
1415     defer std.testing.allocator.free(rtlm_bytes);
1416 
1417     const ltra_face = try Face.init(ltra_bytes);
1418     const ltrm_face = try Face.init(ltrm_bytes);
1419     const rtla_face = try Face.init(rtla_bytes);
1420     const rtlm_face = try Face.init(rtlm_bytes);
1421     const ltra_glyphs = [_]u32{ltra_face.glyphId('A')};
1422     const ltrm_glyphs = [_]u32{ltrm_face.glyphId('A')};
1423     const rtla_glyphs = [_]u32{rtla_face.glyphId('A')};
1424     const rtlm_glyphs = [_]u32{rtlm_face.glyphId('A')};
1425     const rtl_selection = LayoutSelection{ .right_to_left = true };
1426     const disabled_ltra = LayoutSelection{ .features = &.{.{ .tag = tag("ltra"), .value = 0 }} };
1427     const enabled_ltra_rtl = LayoutSelection{ .right_to_left = true, .features = &.{.{ .tag = tag("ltra"), .value = 1 }} };
1428     const disabled_rtla_rtl = LayoutSelection{ .right_to_left = true, .features = &.{.{ .tag = tag("rtla"), .value = 0 }} };
1429 
1430     try std.testing.expectEqual(ltra_face.glyphId('Z'), (try ltra_face.substitutionAtFor(.{}, 0, &ltra_glyphs, 0)).?.glyph_id);
1431     try std.testing.expectEqual(ltrm_face.glyphId('Z'), (try ltrm_face.substitutionAtFor(.{}, 0, &ltrm_glyphs, 0)).?.glyph_id);
1432     try std.testing.expectEqual(@as(?GsubSubstitution, null), try ltra_face.substitutionAtFor(rtl_selection, 0, &ltra_glyphs, 0));
1433     try std.testing.expectEqual(@as(?GsubSubstitution, null), try ltrm_face.substitutionAtFor(rtl_selection, 0, &ltrm_glyphs, 0));
1434     try std.testing.expectEqual(@as(?GsubSubstitution, null), try ltra_face.substitutionAtFor(disabled_ltra, 0, &ltra_glyphs, 0));
1435     try std.testing.expectEqual(ltra_face.glyphId('Z'), (try ltra_face.substitutionAtFor(enabled_ltra_rtl, 0, &ltra_glyphs, 0)).?.glyph_id);
1436 
1437     try std.testing.expectEqual(@as(?GsubSubstitution, null), try rtla_face.substitutionAtFor(.{}, 0, &rtla_glyphs, 0));
1438     try std.testing.expectEqual(@as(?GsubSubstitution, null), try rtlm_face.substitutionAtFor(.{}, 0, &rtlm_glyphs, 0));
1439     try std.testing.expectEqual(rtla_face.glyphId('Z'), (try rtla_face.substitutionAtFor(rtl_selection, 0, &rtla_glyphs, 0)).?.glyph_id);
1440     try std.testing.expectEqual(rtlm_face.glyphId('Z'), (try rtlm_face.substitutionAtFor(rtl_selection, 0, &rtlm_glyphs, 0)).?.glyph_id);
1441     try std.testing.expectEqual(@as(?GsubSubstitution, null), try rtla_face.substitutionAtFor(disabled_rtla_rtl, 0, &rtla_glyphs, 0));
1442 }
1443 
1444 test "Face detects automatic fraction feature availability" {
1445     const frac_bytes = try test_font.createWithGsubFractions(std.testing.allocator);
1446     defer std.testing.allocator.free(frac_bytes);
1447     const component_bytes = try test_font.createWithGsubFractionComponents(std.testing.allocator);
1448     defer std.testing.allocator.free(component_bytes);
1449     const ordinary_bytes = try test_font.create(std.testing.allocator);
1450     defer std.testing.allocator.free(ordinary_bytes);
1451 
1452     const frac_face = try Face.init(frac_bytes);
1453     const component_face = try Face.init(component_bytes);
1454     const ordinary_face = try Face.init(ordinary_bytes);
1455 
1456     try std.testing.expect(frac_face.usesAutomaticFractions(.{}));
1457     try std.testing.expect(component_face.usesAutomaticFractions(.{}));
1458     try std.testing.expect(!ordinary_face.usesAutomaticFractions(.{}));
1459 }
1460 
1461 test "Face detects Arabic joining form feature availability" {
1462     const joining_bytes = try test_font.createWithGsubArabicJoiningForms(std.testing.allocator);
1463     defer std.testing.allocator.free(joining_bytes);
1464     const ordinary_bytes = try test_font.create(std.testing.allocator);
1465     defer std.testing.allocator.free(ordinary_bytes);
1466 
1467     const joining_face = try Face.init(joining_bytes);
1468     const ordinary_face = try Face.init(ordinary_bytes);
1469 
1470     try std.testing.expect(joining_face.usesArabicJoiningForms(.{}));
1471     try std.testing.expect(!ordinary_face.usesArabicJoiningForms(.{}));
1472     try std.testing.expectEqual(@as(u32, 'A' - 31), joining_face.glyphId(0x0628));
1473     try std.testing.expectEqual(@as(u32, 'B' - 31), joining_face.glyphId(0x0627));
1474 }
1475 
1476 test "Face applies GSUB feature variations" {
1477     const bytes = try test_font.createWithGsubFeatureVariations(std.testing.allocator);
1478     defer std.testing.allocator.free(bytes);
1479 
1480     const face = try Face.init(bytes);
1481     const glyphs = [_]u32{face.glyphId('A')};
1482     const heavy = [_]VariationSetting{.{ .tag = tag("wght"), .value = 900.0 }};
1483 
1484     const default_substitution = (try face.substitutionAtFor(.{}, 0, &glyphs, 0)).?;
1485     try std.testing.expectEqual(face.glyphId('Z'), default_substitution.glyph_id);
1486     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{}, 1, &glyphs, 0));
1487 
1488     const heavy_selection = LayoutSelection{ .variations = &heavy };
1489     const heavy_substitution = (try face.substitutionAtFor(heavy_selection, 1, &glyphs, 0)).?;
1490     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(heavy_selection, 0, &glyphs, 0));
1491     try std.testing.expectEqual(face.glyphId('B'), heavy_substitution.glyph_id);
1492     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyphFor(heavy_selection, face.glyphId('A')));
1493 }
1494 
1495 test "Face applies GSUB required variation alternates" {
1496     const bytes = try test_font.createWithGsubRequiredVariationAlternates(std.testing.allocator);
1497     defer std.testing.allocator.free(bytes);
1498 
1499     const face = try Face.init(bytes);
1500     const glyphs = [_]u32{face.glyphId('A')};
1501     const ligature_glyphs = [_]u32{ face.glyphId('f'), face.glyphId('i') };
1502     const heavy = [_]VariationSetting{.{ .tag = tag("wght"), .value = 900.0 }};
1503     const heavy_selection = LayoutSelection{ .variations = &heavy };
1504     const disabled_selection = LayoutSelection{ .features = &.{.{ .tag = tag("rvrn"), .value = 0 }} };
1505     const disabled_heavy_selection = LayoutSelection{ .features = disabled_selection.features, .variations = &heavy };
1506 
1507     const default_substitution = (try face.substitutionAtFor(.{}, 0, &glyphs, 0)).?;
1508     try std.testing.expectEqual(@as(u16, 3), try face.gsubLookupCount());
1509     try std.testing.expectEqual(face.glyphId('Z'), default_substitution.glyph_id);
1510     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{}, 1, &glyphs, 0));
1511     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{}, 2, &ligature_glyphs, 0));
1512     try std.testing.expectEqual(face.glyphId('Z'), face.substituteGlyphFor(.{}, face.glyphId('A')));
1513 
1514     const heavy_substitution = (try face.substitutionAtFor(heavy_selection, 1, &glyphs, 0)).?;
1515     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(heavy_selection, 0, &glyphs, 0));
1516     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(heavy_selection, 2, &ligature_glyphs, 0));
1517     try std.testing.expectEqual(face.glyphId('B'), heavy_substitution.glyph_id);
1518     try std.testing.expectEqual(face.glyphId('B'), face.substituteGlyphFor(heavy_selection, face.glyphId('A')));
1519 
1520     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(disabled_selection, 0, &glyphs, 0));
1521     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(disabled_heavy_selection, 1, &glyphs, 0));
1522     try std.testing.expectEqual(face.glyphId('A'), face.substituteGlyphFor(disabled_selection, face.glyphId('A')));
1523 }
1524 
1525 test "Face applies GSUB vertical alternates only for vertical selection" {
1526     const bytes = try test_font.createWithGsubVerticalAlternates(std.testing.allocator);
1527     defer std.testing.allocator.free(bytes);
1528 
1529     const face = try Face.init(bytes);
1530     const glyphs = [_]u32{face.glyphId('A')};
1531 
1532     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{}, 0, &glyphs, 0));
1533 
1534     const vertical_substitution = (try face.substitutionAtFor(.{ .vertical = true }, 0, &glyphs, 0)).?;
1535     try std.testing.expectEqual(face.glyphId('B'), vertical_substitution.glyph_id);
1536 
1537     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{
1538         .vertical = true,
1539         .features = &.{.{ .tag = tag("vert"), .value = 0 }},
1540     }, 0, &glyphs, 0));
1541 }
1542 
1543 test "Face applies GSUB vertical rotation alternates only for vertical selection" {
1544     const bytes = try test_font.createWithGsubVerticalRotationAlternates(std.testing.allocator);
1545     defer std.testing.allocator.free(bytes);
1546 
1547     const face = try Face.init(bytes);
1548     const glyphs = [_]u32{face.glyphId('A')};
1549 
1550     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{}, 0, &glyphs, 0));
1551 
1552     const vertical_substitution = (try face.substitutionAtFor(.{ .vertical = true }, 0, &glyphs, 0)).?;
1553     try std.testing.expectEqual(face.glyphId('C'), vertical_substitution.glyph_id);
1554 
1555     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{
1556         .vertical = true,
1557         .features = &.{.{ .tag = tag("vrt2"), .value = 0 }},
1558     }, 0, &glyphs, 0));
1559 }
1560 
1561 test "Face activates GSUB substitutions for selected script tags" {
1562     const bytes = try test_font.createWithGsubLigature(std.testing.allocator);
1563     defer std.testing.allocator.free(bytes);
1564     try fixture_binary.replaceLayoutScript(bytes, "GSUB", "latn");
1565 
1566     const face = try Face.init(bytes);
1567     const glyphs = [_]u32{ face.glyphId('f'), face.glyphId('i') };
1568 
1569     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 0));
1570 
1571     const substitution = (try face.substitutionAtFor(.{ .script = tag("latn") }, 0, &glyphs, 0)).?;
1572     try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id);
1573     try std.testing.expectEqual(@as(u16, 2), substitution.component_count);
1574 
1575     const fallback = try face.substitutionAtFor(.{ .script = tag("cyrl") }, 0, &glyphs, 0);
1576     try std.testing.expectEqual(@as(?GsubSubstitution, null), fallback);
1577 }
1578 
1579 test "Face resolves GSUB substitutions from ordered script tags" {
1580     const bytes = try test_font.createWithGsubLigature(std.testing.allocator);
1581     defer std.testing.allocator.free(bytes);
1582     try fixture_binary.replaceLayoutScript(bytes, "GSUB", "dev2");
1583 
1584     const face = try Face.init(bytes);
1585     const glyphs = [_]u32{ face.glyphId('f'), face.glyphId('i') };
1586     const script_tags = [_]Tag{ tag("dev3"), tag("dev2"), tag("deva") };
1587 
1588     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAtFor(.{ .script = tag("deva") }, 0, &glyphs, 0));
1589 
1590     const substitution = (try face.substitutionAtFor(.{ .script = tag("deva"), .script_tags = &script_tags }, 0, &glyphs, 0)).?;
1591     try std.testing.expectEqual(@as(u32, 96), substitution.glyph_id);
1592     try std.testing.expectEqual(@as(u16, 2), substitution.component_count);
1593 }
1594 
1595 test "Face exposes GSUB default multiple substitutions" {
1596     const bytes = try test_font.createWithGsubMultipleSubstitution(std.testing.allocator);
1597     defer std.testing.allocator.free(bytes);
1598 
1599     const face = try Face.init(bytes);
1600     const a = face.glyphId('A');
1601     const glyphs = [_]u32{ a, face.glyphId('!') };
1602     const substitution = (try face.substitutionAt(0, &glyphs, 0)).?;
1603 
1604     try std.testing.expectEqual(@as(u16, 1), try face.gsubLookupCount());
1605     try std.testing.expectEqual(face.glyphId('X'), substitution.glyph_id);
1606     try std.testing.expectEqual(@as(u16, 1), substitution.component_count);
1607     try std.testing.expectEqual(@as(u16, 2), substitution.replacement_count);
1608     try std.testing.expectEqual(@as(?u32, face.glyphId('X')), face.substitutionGlyph(substitution, 0));
1609     try std.testing.expectEqual(@as(?u32, face.glyphId('Y')), face.substitutionGlyph(substitution, 1));
1610     try std.testing.expectEqual(@as(?u32, null), face.substitutionGlyph(substitution, 2));
1611     try std.testing.expectEqual(a, face.substituteGlyph(a));
1612     try std.testing.expectEqual(@as(?GsubSubstitution, null), try face.substitutionAt(0, &glyphs, 1));
1613 }
1614 
1615 test "Face applies GPOS pair adjustments" {
1616     const bytes = try test_font.createWithGposPairAdjustment(std.testing.allocator);
1617     defer std.testing.allocator.free(bytes);
1618 
1619     const face = try Face.init(bytes);
1620     const a = face.glyphId('A');
1621     const v = face.glyphId('V');
1622 
1623     const adjustment = face.gposPairAdjustment(a, v);
1624     try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);
1625     try std.testing.expectEqual(@as(i32, 0), adjustment.right.x_advance);
1626     try std.testing.expect(face.gposPairAdjustment(v, a).isZero());
1627     try std.testing.expect(!face.hasGposSingleAdjustment());
1628     try std.testing.expect(!face.hasGposCursiveAttachment());
1629     try std.testing.expect(!face.hasGposMarkToBase());
1630     try std.testing.expect(!face.hasGposMarkToLigature());
1631     try std.testing.expect(!face.hasGposMarkToMark());
1632 }
1633 
1634 test "Face applies GPOS vertical pair adjustments only for vertical selection" {
1635     const bytes = try test_font.createWithGposVerticalPairAdjustment(std.testing.allocator);
1636     defer std.testing.allocator.free(bytes);
1637 
1638     const face = try Face.init(bytes);
1639     const a = face.glyphId('A');
1640     const v = face.glyphId('V');
1641 
1642     try std.testing.expect(face.gposPairAdjustmentFor(.{}, a, v).isZero());
1643 
1644     const vertical = face.gposPairAdjustmentFor(.{ .vertical = true }, a, v);
1645     try std.testing.expectEqual(@as(i32, 0), vertical.left.x_advance);
1646     try std.testing.expectEqual(@as(i32, -80), vertical.left.y_advance);
1647     try std.testing.expectEqual(@as(i32, 0), vertical.right.y_advance);
1648 
1649     const disabled = face.gposPairAdjustmentFor(.{
1650         .vertical = true,
1651         .features = &.{.{ .tag = tag("vkrn"), .value = 0 }},
1652     }, a, v);
1653     try std.testing.expect(disabled.isZero());
1654 }
1655 
1656 test "Face applies GPOS pair adjustments across ignored marks" {
1657     const bytes = try test_font.createWithGposPairIgnoreMarks(std.testing.allocator);
1658     defer std.testing.allocator.free(bytes);
1659 
1660     const face = try Face.init(bytes);
1661     const glyphs = [_]u32{
1662         face.glyphId('A'),
1663         face.glyphId('^'),
1664         face.glyphId('V'),
1665     };
1666 
1667     const adjustment = face.gposPairAdjustmentBeforeFor(.{}, &glyphs, 2).?;
1668     try std.testing.expectEqual(@as(usize, 2), adjustment.left_offset);
1669     try std.testing.expectEqual(@as(i32, -80), adjustment.adjustment.left.x_advance);
1670     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1671     try std.testing.expectEqual(@as(?GposPairAdjustmentBefore, null), face.gposPairAdjustmentBeforeFor(.{}, &glyphs, 1));
1672 }
1673 
1674 test "Face keeps unfiltered GPOS pair adjustments adjacent" {
1675     const bytes = try test_font.createWithGposPairAdjustment(std.testing.allocator);
1676     defer std.testing.allocator.free(bytes);
1677 
1678     const face = try Face.init(bytes);
1679     const glyphs = [_]u32{
1680         face.glyphId('A'),
1681         face.glyphId('^'),
1682         face.glyphId('V'),
1683     };
1684 
1685     try std.testing.expectEqual(@as(?GposPairAdjustmentBefore, null), face.gposPairAdjustmentBeforeFor(.{}, &glyphs, 2));
1686 }
1687 
1688 test "Face applies GPOS contextual pair adjustments" {
1689     const bytes = try test_font.createWithGposContextualPairAdjustment(std.testing.allocator);
1690     defer std.testing.allocator.free(bytes);
1691 
1692     const face = try Face.init(bytes);
1693     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('C') };
1694     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('!') };
1695     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0).?;
1696 
1697     try std.testing.expect(face.gposPairAdjustment(face.glyphId('A'), face.glyphId('V')).isZero());
1698     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1699     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1700     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1701     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1702     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1));
1703     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &mismatch, 0));
1704 }
1705 
1706 test "Face applies GPOS simple contextual pair adjustments" {
1707     const bytes = try test_font.createWithGposSimpleContextualPairAdjustment(std.testing.allocator);
1708     defer std.testing.allocator.free(bytes);
1709 
1710     const face = try Face.init(bytes);
1711     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('C') };
1712     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('!') };
1713     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0).?;
1714 
1715     try std.testing.expect(face.gposPairAdjustment(face.glyphId('A'), face.glyphId('V')).isZero());
1716     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1717     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1718     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1719     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1720     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1));
1721     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &mismatch, 0));
1722 }
1723 
1724 test "Face applies GPOS class contextual pair adjustments" {
1725     const bytes = try test_font.createWithGposClassContextualPairAdjustment(std.testing.allocator);
1726     defer std.testing.allocator.free(bytes);
1727 
1728     const face = try Face.init(bytes);
1729     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1730     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1731     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0).?;
1732 
1733     try std.testing.expect(face.gposPairAdjustment(face.glyphId('A'), face.glyphId('B')).isZero());
1734     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1735     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1736     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1737     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1738     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1));
1739     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &mismatch, 0));
1740 }
1741 
1742 test "Face applies GPOS chained contextual pair adjustments" {
1743     const bytes = try test_font.createWithGposChainedContextualPairAdjustment(std.testing.allocator);
1744     defer std.testing.allocator.free(bytes);
1745 
1746     const face = try Face.init(bytes);
1747     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('C'), face.glyphId('!') };
1748     const lookahead_mismatch = [_]u32{ face.glyphId('A'), face.glyphId('V'), face.glyphId('C'), face.glyphId('A') };
1749     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1).?;
1750 
1751     try std.testing.expect(face.gposPairAdjustment(face.glyphId('V'), face.glyphId('C')).isZero());
1752     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1753     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1754     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1755     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1756     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0));
1757     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &lookahead_mismatch, 1));
1758 }
1759 
1760 test "Face applies GPOS chained simple contextual pair adjustments" {
1761     const bytes = try test_font.createWithGposChainedSimpleContextualPairAdjustment(std.testing.allocator);
1762     defer std.testing.allocator.free(bytes);
1763 
1764     const face = try Face.init(bytes);
1765     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1766     const missing_backtrack = [_]u32{ face.glyphId('B'), face.glyphId('C'), face.glyphId('!') };
1767     const missing_input = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1768     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1).?;
1769 
1770     try std.testing.expect(face.gposPairAdjustment(face.glyphId('B'), face.glyphId('C')).isZero());
1771     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1772     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1773     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1774     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1775     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0));
1776     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &missing_backtrack, 0));
1777     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &missing_input, 1));
1778 }
1779 
1780 test "Face applies GPOS chained class contextual pair adjustments" {
1781     const bytes = try test_font.createWithGposChainedClassContextualPairAdjustment(std.testing.allocator);
1782     defer std.testing.allocator.free(bytes);
1783 
1784     const face = try Face.init(bytes);
1785     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1786     const missing_backtrack = [_]u32{ face.glyphId('B'), face.glyphId('C'), face.glyphId('!') };
1787     const missing_input = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1788     const adjustment = face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 1).?;
1789 
1790     try std.testing.expect(face.gposPairAdjustment(face.glyphId('B'), face.glyphId('C')).isZero());
1791     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1792     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1793     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.left.x_advance);
1794     try std.testing.expectEqual(@as(i32, 0), adjustment.adjustment.right.x_advance);
1795     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &glyphs, 0));
1796     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &missing_backtrack, 0));
1797     try std.testing.expectEqual(@as(?GposContextualPairAdjustment, null), face.gposContextualPairAdjustmentAtFor(.{}, &missing_input, 1));
1798 }
1799 
1800 test "Face activates GPOS adjustments for selected script tags" {
1801     const bytes = try test_font.createWithGposPairAdjustment(std.testing.allocator);
1802     defer std.testing.allocator.free(bytes);
1803     try fixture_binary.replaceLayoutScript(bytes, "GPOS", "latn");
1804 
1805     const face = try Face.init(bytes);
1806     const a = face.glyphId('A');
1807     const v = face.glyphId('V');
1808 
1809     try std.testing.expect(face.gposPairAdjustment(a, v).isZero());
1810 
1811     const adjustment = face.gposPairAdjustmentFor(.{ .script = tag("latn") }, a, v);
1812     try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);
1813     try std.testing.expectEqual(@as(i32, 0), adjustment.right.x_advance);
1814     try std.testing.expect(face.gposPairAdjustmentFor(.{ .script = tag("cyrl") }, a, v).isZero());
1815 }
1816 
1817 test "Face resolves GPOS adjustments from ordered script tags" {
1818     const bytes = try test_font.createWithGposPairAdjustment(std.testing.allocator);
1819     defer std.testing.allocator.free(bytes);
1820     try fixture_binary.replaceLayoutScript(bytes, "GPOS", "bng2");
1821 
1822     const face = try Face.init(bytes);
1823     const a = face.glyphId('A');
1824     const v = face.glyphId('V');
1825     const script_tags = [_]Tag{ tag("bng3"), tag("bng2"), tag("beng") };
1826 
1827     try std.testing.expect(face.gposPairAdjustmentFor(.{ .script = tag("beng") }, a, v).isZero());
1828 
1829     const adjustment = face.gposPairAdjustmentFor(.{ .script = tag("beng"), .script_tags = &script_tags }, a, v);
1830     try std.testing.expectEqual(@as(i32, -80), adjustment.left.x_advance);
1831     try std.testing.expectEqual(@as(i32, 0), adjustment.right.x_advance);
1832 }
1833 
1834 test "Face applies GPOS single adjustments" {
1835     const bytes = try test_font.createWithGposSingleAdjustment(std.testing.allocator);
1836     defer std.testing.allocator.free(bytes);
1837 
1838     const face = try Face.init(bytes);
1839     const a = face.glyphId('A');
1840     const b = face.glyphId('B');
1841     const bang = face.glyphId('!');
1842 
1843     try std.testing.expect(face.hasGposSingleAdjustment());
1844     const a_adjustment = face.gposSingleAdjustment(a);
1845     try std.testing.expectEqual(@as(i32, 20), a_adjustment.x_placement);
1846     try std.testing.expectEqual(@as(i32, 30), a_adjustment.y_placement);
1847     try std.testing.expectEqual(@as(i32, -40), a_adjustment.x_advance);
1848     const b_adjustment = face.gposSingleAdjustment(b);
1849     try std.testing.expectEqual(@as(i32, -10), b_adjustment.x_placement);
1850     try std.testing.expectEqual(@as(i32, 0), b_adjustment.y_placement);
1851     try std.testing.expectEqual(@as(i32, 0), b_adjustment.x_advance);
1852     try std.testing.expect(face.gposSingleAdjustment(bang).isZero());
1853 
1854     const vertical = LayoutSelection{ .vertical = true };
1855     const vertical_enabled = LayoutSelection{ .vertical = true, .features = &.{.{ .tag = tag("dist"), .value = 1 }} };
1856     const glyphs = [_]u32{a};
1857 
1858     try std.testing.expect(!face.hasGposSingleAdjustmentFor(vertical));
1859     try std.testing.expect(face.hasGposSingleAdjustmentFor(vertical_enabled));
1860     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(vertical, &glyphs, 0));
1861     try std.testing.expectEqual(@as(i32, 20), face.gposSingleAdjustmentAtFor(vertical_enabled, &glyphs, 0).?.adjustment.x_placement);
1862 }
1863 
1864 test "Face applies GPOS feature variations" {
1865     const bytes = try test_font.createWithGposFeatureVariations(std.testing.allocator);
1866     defer std.testing.allocator.free(bytes);
1867 
1868     const face = try Face.init(bytes);
1869     const glyph = face.glyphId('A');
1870     const heavy = [_]VariationSetting{.{ .tag = tag("wght"), .value = 900.0 }};
1871     const heavy_selection = LayoutSelection{ .variations = &heavy };
1872 
1873     const default_adjustment = face.gposSingleAdjustmentFor(.{}, glyph);
1874     try std.testing.expectEqual(@as(i32, -40), default_adjustment.x_advance);
1875     try std.testing.expectEqual(@as(i32, 0), default_adjustment.x_placement);
1876 
1877     const heavy_adjustment = face.gposSingleAdjustmentFor(heavy_selection, glyph);
1878     try std.testing.expect(face.hasGposSingleAdjustmentFor(heavy_selection));
1879     try std.testing.expectEqual(@as(i32, -120), heavy_adjustment.x_advance);
1880     try std.testing.expectEqual(@as(i32, 0), heavy_adjustment.x_placement);
1881 }
1882 
1883 test "Face applies GPOS contextual single adjustments" {
1884     const bytes = try test_font.createWithGposContextualSingleAdjustment(std.testing.allocator);
1885     defer std.testing.allocator.free(bytes);
1886 
1887     const face = try Face.init(bytes);
1888     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1889     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1890     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0).?;
1891 
1892     try std.testing.expect(face.hasGposSingleAdjustment());
1893     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1894     try std.testing.expectEqual(@as(usize, 1), adjustment.target_offset);
1895     try std.testing.expectEqual(@as(usize, 3), adjustment.skip_count);
1896     try std.testing.expectEqual(@as(i32, 50), adjustment.adjustment.x_placement);
1897     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.x_advance);
1898     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &mismatch, 0));
1899     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1));
1900 }
1901 
1902 test "Face applies GPOS simple contextual single adjustments" {
1903     const bytes = try test_font.createWithGposSimpleContextualSingleAdjustment(std.testing.allocator);
1904     defer std.testing.allocator.free(bytes);
1905 
1906     const face = try Face.init(bytes);
1907     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1908     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1909     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0).?;
1910 
1911     try std.testing.expect(face.hasGposSingleAdjustment());
1912     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1913     try std.testing.expectEqual(@as(usize, 1), adjustment.target_offset);
1914     try std.testing.expectEqual(@as(usize, 3), adjustment.skip_count);
1915     try std.testing.expectEqual(@as(i32, 50), adjustment.adjustment.x_placement);
1916     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.x_advance);
1917     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &mismatch, 0));
1918     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1));
1919 }
1920 
1921 test "Face applies GPOS class contextual single adjustments" {
1922     const bytes = try test_font.createWithGposClassContextualSingleAdjustment(std.testing.allocator);
1923     defer std.testing.allocator.free(bytes);
1924 
1925     const face = try Face.init(bytes);
1926     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1927     const mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1928     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0).?;
1929 
1930     try std.testing.expect(face.hasGposSingleAdjustment());
1931     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1932     try std.testing.expectEqual(@as(usize, 1), adjustment.target_offset);
1933     try std.testing.expectEqual(@as(usize, 3), adjustment.skip_count);
1934     try std.testing.expectEqual(@as(i32, 50), adjustment.adjustment.x_placement);
1935     try std.testing.expectEqual(@as(i32, -100), adjustment.adjustment.x_advance);
1936     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &mismatch, 0));
1937     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1));
1938 }
1939 
1940 test "Face applies GPOS chained contextual single adjustments" {
1941     const bytes = try test_font.createWithGposChainedContextualSingleAdjustment(std.testing.allocator);
1942     defer std.testing.allocator.free(bytes);
1943 
1944     const face = try Face.init(bytes);
1945     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C'), face.glyphId('!') };
1946     const lookahead_mismatch = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C'), face.glyphId('A') };
1947     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1).?;
1948 
1949     try std.testing.expect(face.hasGposSingleAdjustment());
1950     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1951     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1952     try std.testing.expectEqual(@as(usize, 2), adjustment.skip_count);
1953     try std.testing.expectEqual(@as(i32, 75), adjustment.adjustment.x_placement);
1954     try std.testing.expectEqual(@as(i32, -125), adjustment.adjustment.x_advance);
1955     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0));
1956     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &lookahead_mismatch, 1));
1957 }
1958 
1959 test "Face applies GPOS chained simple contextual single adjustments" {
1960     const bytes = try test_font.createWithGposChainedSimpleContextualSingleAdjustment(std.testing.allocator);
1961     defer std.testing.allocator.free(bytes);
1962 
1963     const face = try Face.init(bytes);
1964     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1965     const missing_backtrack = [_]u32{ face.glyphId('B'), face.glyphId('C'), face.glyphId('!') };
1966     const missing_lookahead = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1967     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1).?;
1968 
1969     try std.testing.expect(face.hasGposSingleAdjustment());
1970     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1971     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1972     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1973     try std.testing.expectEqual(@as(i32, 75), adjustment.adjustment.x_placement);
1974     try std.testing.expectEqual(@as(i32, -125), adjustment.adjustment.x_advance);
1975     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0));
1976     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &missing_backtrack, 0));
1977     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &missing_lookahead, 1));
1978 }
1979 
1980 test "Face applies GPOS chained class contextual single adjustments" {
1981     const bytes = try test_font.createWithGposChainedClassContextualSingleAdjustment(std.testing.allocator);
1982     defer std.testing.allocator.free(bytes);
1983 
1984     const face = try Face.init(bytes);
1985     const glyphs = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('C') };
1986     const missing_backtrack = [_]u32{ face.glyphId('B'), face.glyphId('C'), face.glyphId('!') };
1987     const missing_lookahead = [_]u32{ face.glyphId('A'), face.glyphId('B'), face.glyphId('!') };
1988     const adjustment = face.gposSingleAdjustmentAtFor(.{}, &glyphs, 1).?;
1989 
1990     try std.testing.expect(face.hasGposSingleAdjustment());
1991     try std.testing.expect(face.gposSingleAdjustment(face.glyphId('B')).isZero());
1992     try std.testing.expectEqual(@as(usize, 0), adjustment.target_offset);
1993     try std.testing.expectEqual(@as(usize, 1), adjustment.skip_count);
1994     try std.testing.expectEqual(@as(i32, 75), adjustment.adjustment.x_placement);
1995     try std.testing.expectEqual(@as(i32, -125), adjustment.adjustment.x_advance);
1996     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &glyphs, 0));
1997     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &missing_backtrack, 0));
1998     try std.testing.expectEqual(@as(?GposSingleAdjustment, null), face.gposSingleAdjustmentAtFor(.{}, &missing_lookahead, 1));
1999 }
2000 
2001 test "Face applies GPOS cursive attachments" {
2002     const bytes = try test_font.createWithGposCursiveAttachment(std.testing.allocator);
2003     defer std.testing.allocator.free(bytes);
2004 
2005     const face = try Face.init(bytes);
2006     const a = face.glyphId('A');
2007     const b = face.glyphId('B');
2008 
2009     try std.testing.expect(face.hasGposCursiveAttachment());
2010     const attachment = face.gposCursiveAttachment(a, b).?;
2011     try std.testing.expectEqual(@as(i32, 460), attachment.x_anchor_delta);
2012     try std.testing.expectEqual(@as(i32, 100), attachment.y_anchor_delta);
2013     try std.testing.expect(face.gposCursiveAttachment(b, a) == null);
2014     try std.testing.expect(face.gposCursiveAttachment(a, face.glyphId('!')) == null);
2015 
2016     const vertical = LayoutSelection{ .vertical = true };
2017     const vertical_enabled = LayoutSelection{ .vertical = true, .features = &.{.{ .tag = tag("curs"), .value = 1 }} };
2018 
2019     try std.testing.expect(!face.hasGposCursiveAttachmentFor(vertical));
2020     try std.testing.expect(face.hasGposCursiveAttachmentFor(vertical_enabled));
2021     try std.testing.expect(face.gposCursiveAttachmentFor(vertical, a, b) == null);
2022     try std.testing.expectEqual(@as(i32, 460), face.gposCursiveAttachmentFor(vertical_enabled, a, b).?.x_anchor_delta);
2023 }
2024 
2025 test "Face applies GPOS mark-to-base attachment" {
2026     const bytes = try test_font.createWithGposMarkToBase(std.testing.allocator);
2027     defer std.testing.allocator.free(bytes);
2028 
2029     const face = try Face.init(bytes);
2030     const a = face.glyphId('A');
2031     const mark = face.glyphId('^');
2032 
2033     try std.testing.expect(face.hasGposMarkToBase());
2034     const adjustment = face.gposMarkToBaseAdjustment(a, mark).?;
2035     try std.testing.expectEqual(@as(i32, 150), adjustment.x_placement);
2036     try std.testing.expectEqual(@as(i32, 700), adjustment.y_placement);
2037     try std.testing.expect(face.gposMarkToBaseAdjustment(mark, a) == null);
2038     try std.testing.expect(face.gposMarkToBaseAdjustment(a, face.glyphId('!')) == null);
2039 }
2040 
2041 test "Face applies GPOS above and below-base mark positioning by default" {
2042     const abvm_bytes = try test_font.createWithGposAboveBaseMarkPositioning(std.testing.allocator);
2043     defer std.testing.allocator.free(abvm_bytes);
2044     const blwm_bytes = try test_font.createWithGposBelowBaseMarkPositioning(std.testing.allocator);
2045     defer std.testing.allocator.free(blwm_bytes);
2046 
2047     const abvm_face = try Face.init(abvm_bytes);
2048     const blwm_face = try Face.init(blwm_bytes);
2049     const abvm_base = abvm_face.glyphId('A');
2050     const abvm_mark = abvm_face.glyphId('^');
2051     const blwm_base = blwm_face.glyphId('A');
2052     const blwm_mark = blwm_face.glyphId('^');
2053     const disabled_abvm = LayoutSelection{ .features = &.{.{ .tag = tag("abvm"), .value = 0 }} };
2054     const disabled_blwm = LayoutSelection{ .features = &.{.{ .tag = tag("blwm"), .value = 0 }} };
2055 
2056     try std.testing.expectEqual(@as(i32, 150), abvm_face.gposMarkToBaseAdjustment(abvm_base, abvm_mark).?.x_placement);
2057     try std.testing.expectEqual(@as(i32, 700), abvm_face.gposMarkToBaseAdjustmentFor(.{ .vertical = true }, abvm_base, abvm_mark).?.y_placement);
2058     try std.testing.expectEqual(@as(i32, 150), blwm_face.gposMarkToBaseAdjustment(blwm_base, blwm_mark).?.x_placement);
2059     try std.testing.expectEqual(@as(i32, 700), blwm_face.gposMarkToBaseAdjustmentFor(.{ .vertical = true }, blwm_base, blwm_mark).?.y_placement);
2060     try std.testing.expect(abvm_face.gposMarkToBaseAdjustmentFor(disabled_abvm, abvm_base, abvm_mark) == null);
2061     try std.testing.expect(blwm_face.gposMarkToBaseAdjustmentFor(disabled_blwm, blwm_base, blwm_mark) == null);
2062 }
2063 
2064 test "Face applies GPOS mark-to-ligature attachment" {
2065     const bytes = try test_font.createWithGposMarkToLigature(std.testing.allocator);
2066     defer std.testing.allocator.free(bytes);
2067 
2068     const face = try Face.init(bytes);
2069     const ligature = @as(u32, 96);
2070     const mark = face.glyphId('^');
2071 
2072     try std.testing.expect(face.hasGposMarkToLigature());
2073     const first_component = face.gposMarkToLigatureAdjustment(ligature, mark, 0).?;
2074     try std.testing.expectEqual(@as(i32, 80), first_component.x_placement);
2075     try std.testing.expectEqual(@as(i32, 700), first_component.y_placement);
2076     const second_component = face.gposMarkToLigatureAdjustment(ligature, mark, 1).?;
2077     try std.testing.expectEqual(@as(i32, 230), second_component.x_placement);
2078     try std.testing.expectEqual(@as(i32, 650), second_component.y_placement);
2079     try std.testing.expect(face.gposMarkToLigatureAdjustment(ligature, mark, 2) == null);
2080     try std.testing.expect(face.gposMarkToLigatureAdjustment(mark, ligature, 0) == null);
2081     try std.testing.expect(face.gposMarkToLigatureAdjustment(ligature, face.glyphId('!'), 0) == null);
2082 }
2083 
2084 test "Face applies GPOS mark-to-mark attachment" {
2085     const bytes = try test_font.createWithGposMarkToMark(std.testing.allocator);
2086     defer std.testing.allocator.free(bytes);
2087 
2088     const face = try Face.init(bytes);
2089     const base_mark = face.glyphId('^');
2090     const mark = face.glyphId('_');
2091 
2092     try std.testing.expect(face.hasGposMarkToMark());
2093     const adjustment = face.gposMarkToMarkAdjustment(base_mark, mark).?;
2094     try std.testing.expectEqual(@as(i32, 20), adjustment.x_placement);
2095     try std.testing.expectEqual(@as(i32, 300), adjustment.y_placement);
2096     try std.testing.expect(face.gposMarkToMarkAdjustment(mark, base_mark) == null);
2097     try std.testing.expect(face.gposMarkToMarkAdjustment(base_mark, face.glyphId('!')) == null);
2098 }
2099 
2100 test "Face reads GDEF glyph classes" {
2101     const bytes = try test_font.createWithGdefGlyphClasses(std.testing.allocator);
2102     defer std.testing.allocator.free(bytes);
2103 
2104     const face = try Face.init(bytes);
2105 
2106     try std.testing.expectEqual(GlyphClass.base, face.glyphClass(face.glyphId('A')));
2107     try std.testing.expectEqual(GlyphClass.mark, face.glyphClass(face.glyphId('^')));
2108     try std.testing.expectEqual(GlyphClass.component, face.glyphClass(face.glyphId('_')));
2109     try std.testing.expectEqual(GlyphClass.ligature, face.glyphClass(96));
2110     try std.testing.expectEqual(GlyphClass.unknown, face.glyphClass(face.glyphId('!')));
2111 }
2112 
2113 test "Face applies GPOS mark-to-base with matching mark attachment type" {
2114     const bytes = try test_font.createWithGposMarkToBase(std.testing.allocator);
2115     defer std.testing.allocator.free(bytes);
2116     try fixture_binary.setLayoutLookupFlag(bytes, "GPOS", 0, 0x0300);
2117     const gdef_offset = try fixture_binary.tableOffset(bytes, "GDEF");
2118     fixture_binary.writeU16(bytes, gdef_offset + 10, 12);
2119 
2120     const face = try Face.init(bytes);
2121     const adjustment = face.gposMarkToBaseAdjustment(face.glyphId('A'), face.glyphId('^')).?;
2122 
2123     try std.testing.expectEqual(@as(i32, 150), adjustment.x_placement);
2124     try std.testing.expectEqual(@as(i32, 700), adjustment.y_placement);
2125 }
2126 
2127 test "Face filters GPOS mark-to-base with mismatched mark attachment type" {
2128     const bytes = try test_font.createWithGposMarkToBase(std.testing.allocator);
2129     defer std.testing.allocator.free(bytes);
2130     try fixture_binary.setLayoutLookupFlag(bytes, "GPOS", 0, 0x0200);
2131     const gdef_offset = try fixture_binary.tableOffset(bytes, "GDEF");
2132     fixture_binary.writeU16(bytes, gdef_offset + 10, 12);
2133 
2134     const face = try Face.init(bytes);
2135 
2136     try std.testing.expect(face.gposMarkToBaseAdjustment(face.glyphId('A'), face.glyphId('^')) == null);
2137 }
2138 
2139 test "Face reads GDEF ligature caret coordinates" {
2140     const bytes = try test_font.createWithGsubLigatureAndGdefCarets(std.testing.allocator);
2141     defer std.testing.allocator.free(bytes);
2142 
2143     const face = try Face.init(bytes);
2144     const caret = face.ligatureCaret(96, 0).?;
2145 
2146     try std.testing.expectEqual(@as(u16, 1), face.ligatureCaretCount(96));
2147     try std.testing.expectEqual(@as(i16, 250), caret.coordinate);
2148     try std.testing.expectEqual(false, caret.synthesized);
2149     try std.testing.expect(face.ligatureCaret(96, 1) == null);
2150     try std.testing.expectEqual(@as(u16, 0), face.ligatureCaretCount(face.glyphId('A')));
2151     try std.testing.expect(face.ligatureCaret(face.glyphId('A'), 0) == null);
2152 }
2153 
2154 test "Face exposes horizontal MATH assemblies" {
2155     const bytes = try test_font.createWithMathAssembly(std.testing.allocator);
2156     defer std.testing.allocator.free(bytes);
2157 
2158     const face = try Face.init(bytes);
2159     const arrow = face.glyphId(0x2192);
2160     var assembly = (try face.mathHorizontalAssemblyAlloc(std.testing.allocator, arrow, 1300)).?;
2161     defer assembly.deinit(std.testing.allocator);
2162 
2163     try std.testing.expect(arrow != 0);
2164     try std.testing.expect(face.mathHorizontalVariant(arrow, 1300) == null);
2165     try std.testing.expectEqual(@as(u16, 1300), assembly.advance_measurement);
2166     try std.testing.expectEqual(@as(usize, 4), assembly.parts.len);
2167 }
2168 
2169 test "Face exposes MATH top accent attachments" {
2170     const bytes = try test_font.createWithMathAssembly(std.testing.allocator);
2171     defer std.testing.allocator.free(bytes);
2172 
2173     const face = try Face.init(bytes);
2174     const base = face.mathTopAccentAttachment(face.glyphId('A')).?;
2175     const accent = face.mathTopAccentAttachment(face.glyphId('`')).?;
2176 
2177     try std.testing.expectEqual(@as(i16, 420), base.value);
2178     try std.testing.expectEqual(@as(i16, 80), accent.value);
2179     try std.testing.expect(face.mathTopAccentAttachment(face.glyphId('B')) == null);
2180 }
2181 
2182 test "Face ignores malformed optional GSUB table" {
2183     const bytes = try test_font.createWithGsubSingleSubstitution(std.testing.allocator);
2184     defer std.testing.allocator.free(bytes);
2185 
2186     const gsub_table = try table(bytes, "GSUB");
2187     std.mem.writeInt(u16, bytes[gsub_table.offset..][0..2], 2, .big);
2188 
2189     const face = try Face.init(bytes);
2190     const a = face.glyphId('A');
2191     try std.testing.expectEqual(a, face.substituteGlyph(a));
2192 }
2193 
2194 test "Face ignores malformed optional GPOS table" {
2195     const bytes = try test_font.createWithGposPairAdjustment(std.testing.allocator);
2196     defer std.testing.allocator.free(bytes);
2197 
2198     const gpos_table = try table(bytes, "GPOS");
2199     std.mem.writeInt(u16, bytes[gpos_table.offset..][0..2], 2, .big);
2200 
2201     const face = try Face.init(bytes);
2202     try std.testing.expect(face.gposPairAdjustment(face.glyphId('A'), face.glyphId('V')).isZero());
2203 }
2204 
2205 test "Face ignores malformed optional GDEF table" {
2206     const bytes = try test_font.createWithGdefGlyphClasses(std.testing.allocator);
2207     defer std.testing.allocator.free(bytes);
2208 
2209     const gdef_table = try table(bytes, "GDEF");
2210     std.mem.writeInt(u16, bytes[gdef_table.offset..][0..2], 2, .big);
2211 
2212     const face = try Face.init(bytes);
2213     try std.testing.expectEqual(GlyphClass.unknown, face.glyphClass(face.glyphId('A')));
2214 }
2215 
2216 test "Face ignores malformed optional kern table" {
2217     const bytes = try test_font.createWithKern(std.testing.allocator);
2218     defer std.testing.allocator.free(bytes);
2219 
2220     const kern_table = try table(bytes, "kern");
2221     std.mem.writeInt(u16, bytes[kern_table.offset + 6 ..][0..2], 2, .big);
2222 
2223     const face = try Face.init(bytes);
2224     try std.testing.expectEqual(@as(i16, 0), face.kerning(face.glyphId('A'), face.glyphId('V')));
2225 }
2226 
2227 test "GPOS value records apply GDEF variation deltas" {
2228     const bytes = try test_font.createWithGposSingleVariationAdjustment(std.testing.allocator);
2229     defer std.testing.allocator.free(bytes);
2230     const face = try Face.init(bytes);
2231 
2232     const adjustment = face.gposSingleAdjustment(face.glyphId('A'));
2233     try std.testing.expectEqual(@as(i32, 20), adjustment.x_placement);
2234     try std.testing.expectEqual(@as(i32, 20), adjustment.withVariations(face, &.{}).x_placement);
2235     try std.testing.expectEqual(@as(i32, 60), adjustment.withVariations(face, &.{.{ .tag = tag("wght"), .value = 650.0 }}).x_placement);
2236     try std.testing.expectEqual(@as(i32, 100), adjustment.withVariations(face, &.{.{ .tag = tag("wght"), .value = 900.0 }}).x_placement);
2237 }
2238 
2239 test "GDEF reads mark attachment classes and mark glyph sets" {
2240     const bytes = try createGdefMarkFilterFixture(std.testing.allocator);
2241     defer std.testing.allocator.free(bytes);
2242 
2243     const gdef = try Gdef.init(bytes, .{ .offset = 0, .len = bytes.len }, null);
2244 
2245     try std.testing.expectEqual(GlyphClass.mark, try gdef.glyphClass(bytes, '^' - 31));
2246     try std.testing.expectEqual(@as(u16, 1), try gdef.markAttachmentClass(bytes, '^' - 31));
2247     try std.testing.expectEqual(@as(u16, 2), try gdef.markAttachmentClass(bytes, '_' - 31));
2248     try std.testing.expect(try gdef.markGlyphSetContains(bytes, 0, '^' - 31));
2249     try std.testing.expect(!try gdef.markGlyphSetContains(bytes, 0, '_' - 31));
2250 }
2251 
2252 test "lookup filters use GDEF mark classes and mark glyph sets" {
2253     const bytes = try createGdefMarkFilterFixture(std.testing.allocator);
2254     defer std.testing.allocator.free(bytes);
2255 
2256     const gdef = try Gdef.init(bytes, .{ .offset = 0, .len = bytes.len }, null);
2257 
2258     try std.testing.expect(!try lookupGlyphIgnored(bytes, gdef, .{ .flags = 0x0100 }, '^' - 31));
2259     try std.testing.expect(try lookupGlyphIgnored(bytes, gdef, .{ .flags = 0x0100 }, '_' - 31));
2260     try std.testing.expect(!try lookupGlyphIgnored(bytes, gdef, .{ .flags = lookup_use_mark_filtering_set, .mark_filtering_set = 0 }, '^' - 31));
2261     try std.testing.expect(try lookupGlyphIgnored(bytes, gdef, .{ .flags = lookup_use_mark_filtering_set, .mark_filtering_set = 0 }, '_' - 31));
2262 }
2263 
2264 fn createGdefMarkFilterFixture(allocator: std.mem.Allocator) ![]u8 {
2265     var out = std.ArrayListUnmanaged(u8).empty;
2266     errdefer out.deinit(allocator);
2267 
2268     try fixture_binary.appendU16(allocator, &out, 1);
2269     try fixture_binary.appendU16(allocator, &out, 2);
2270     try fixture_binary.appendU16(allocator, &out, 14);
2271     try fixture_binary.appendU16(allocator, &out, 0);
2272     try fixture_binary.appendU16(allocator, &out, 0);
2273     try fixture_binary.appendU16(allocator, &out, 30);
2274     try fixture_binary.appendU16(allocator, &out, 46);
2275 
2276     try fixture_binary.appendU16(allocator, &out, 2);
2277     try fixture_binary.appendU16(allocator, &out, 2);
2278     try fixture_binary.appendU16(allocator, &out, '^' - 31);
2279     try fixture_binary.appendU16(allocator, &out, '^' - 31);
2280     try fixture_binary.appendU16(allocator, &out, 3);
2281     try fixture_binary.appendU16(allocator, &out, '_' - 31);
2282     try fixture_binary.appendU16(allocator, &out, '_' - 31);
2283     try fixture_binary.appendU16(allocator, &out, 3);
2284 
2285     try fixture_binary.appendU16(allocator, &out, 2);
2286     try fixture_binary.appendU16(allocator, &out, 2);
2287     try fixture_binary.appendU16(allocator, &out, '^' - 31);
2288     try fixture_binary.appendU16(allocator, &out, '^' - 31);
2289     try fixture_binary.appendU16(allocator, &out, 1);
2290     try fixture_binary.appendU16(allocator, &out, '_' - 31);
2291     try fixture_binary.appendU16(allocator, &out, '_' - 31);
2292     try fixture_binary.appendU16(allocator, &out, 2);
2293 
2294     try fixture_binary.appendU16(allocator, &out, 1);
2295     try fixture_binary.appendU16(allocator, &out, 1);
2296     try fixture_binary.appendU32(allocator, &out, 8);
2297 
2298     try fixture_binary.appendU16(allocator, &out, 1);
2299     try fixture_binary.appendU16(allocator, &out, 1);
2300     try fixture_binary.appendU16(allocator, &out, '^' - 31);
2301 
2302     return out.toOwnedSlice(allocator);
2303 }