package font // ============================================================================ // GSUB — OpenType Glyph Substitution // ============================================================================ // Substitution result for a single glyph position GSUB_Subst :: struct { glyph: i32, // replacement glyph ID skip: i32, // number of additional input glyphs consumed (for ligatures) } // Apply all default GSUB substitutions to a glyph buffer. // Processes the default script/language with all default features. // Modifies glyphs in-place. Returns the new length (may shrink due to ligatures). apply_gsub_default :: proc(info: ^Font_Info, glyphs: []i32) -> i32 { if info.gsub == 0 do return i32(len(glyphs)) data := info.data gsub := u32(info.gsub) // Validate GSUB header major := ttUSHORT(data[gsub:]) if major != 1 do return i32(len(glyphs)) script_list := gsub + u32(ttUSHORT(data[gsub + 4:])) feature_list := gsub + u32(ttUSHORT(data[gsub + 6:])) lookup_list := gsub + u32(ttUSHORT(data[gsub + 8:])) // Find default LangSys: try "DFLT" then "latn" scripts, then first script lang_sys := gsub_find_default_langsys(data, script_list) if lang_sys == 0 do return i32(len(glyphs)) // Collect lookup indices from all features referenced by the default LangSys feature_count := i32(ttUSHORT(data[lang_sys + 4:])) feature_indices := data[lang_sys + 6:] // Process required feature if present req_feature := i32(ttUSHORT(data[lang_sys + 2:])) // Work buffer — copy input glyphs buf: [1024]i32 n := min(i32(len(glyphs)), 1024) for i in 0.. i32 { if info.gsub == 0 || len(tag) != 4 do return i32(len(glyphs)) data := info.data gsub := u32(info.gsub) major := ttUSHORT(data[gsub:]) if major != 1 do return i32(len(glyphs)) feature_list := gsub + u32(ttUSHORT(data[gsub + 6:])) lookup_list := gsub + u32(ttUSHORT(data[gsub + 8:])) // Find the feature by tag feat_count := i32(ttUSHORT(data[feature_list:])) feat_records := data[feature_list + 2:] buf: [1024]i32 n := min(i32(len(glyphs)), 1024) for i in 0.. u32 { script_count := i32(ttUSHORT(data[script_list:])) records := data[script_list + 2:] // Priority: "DFLT", "latn", first available try_tags := [?]string{"DFLT", "latn"} for t in try_tags { for si in 0.. 0 { script_offset := script_list + u32(ttUSHORT(records[4:])) default_lang_sys_offset := u32(ttUSHORT(data[script_offset:])) if default_lang_sys_offset != 0 { return script_offset + default_lang_sys_offset } } return 0 } @(private) gsub_apply_feature :: proc(info: ^Font_Info, data: [^]u8, gsub: u32, feature_list: u32, lookup_list: u32, feat_idx: i32, glyphs: []i32) -> i32 { feat_count := i32(ttUSHORT(data[feature_list:])) if feat_idx < 0 || feat_idx >= feat_count do return i32(len(glyphs)) feat_records := data[feature_list + 2:] feat_offset := feature_list + u32(ttUSHORT(feat_records[feat_idx * 6 + 4:])) lookup_count := i32(ttUSHORT(data[feat_offset + 2:])) lookup_indices := data[feat_offset + 4:] n := i32(len(glyphs)) for li in 0.. i32 { lookup_count := i32(ttUSHORT(data[lookup_list:])) if lookup_idx < 0 || lookup_idx >= lookup_count do return i32(len(glyphs)) lookup_offset := lookup_list + u32(ttUSHORT(data[lookup_list + 2 + u32(lookup_idx) * 2:])) lookup_type := i32(ttUSHORT(data[lookup_offset:])) // lookup_flag := ttUSHORT(data[lookup_offset + 2:]) subtable_count := i32(ttUSHORT(data[lookup_offset + 4:])) n := i32(len(glyphs)) for sti in 0.. i32 { format := ttUSHORT(data[subtable:]) coverage_off := subtable + u32(ttUSHORT(data[subtable + 2:])) switch format { case 1: // Delta delta := i32(ttSHORT(data[subtable + 4:])) for i in 0..= 0 { glyphs[i] = (glyphs[i] + delta) & 0xFFFF } } case 2: // Substitute array glyph_count := i32(ttUSHORT(data[subtable + 4:])) subst_array := data[subtable + 6:] for i in 0..= 0 && ci < glyph_count { glyphs[i] = i32(ttUSHORT(subst_array[ci * 2:])) } } } return i32(len(glyphs)) } // Lookup Type 2: Multiple Substitution (1:M replacement) @(private) gsub_multiple_subst :: proc(data: [^]u8, subtable: u32, glyphs: []i32) -> i32 { format := ttUSHORT(data[subtable:]) if format != 1 do return i32(len(glyphs)) coverage_off := subtable + u32(ttUSHORT(data[subtable + 2:])) seq_count := i32(ttUSHORT(data[subtable + 4:])) seq_offsets := data[subtable + 6:] n := i32(len(glyphs)) i: i32 = 0 for i < n { ci := get_coverage_index(data[coverage_off:], glyphs[i]) if ci >= 0 && ci < seq_count { seq_off := subtable + u32(ttUSHORT(seq_offsets[ci * 2:])) replace_count := i32(ttUSHORT(data[seq_off:])) replace_glyphs := data[seq_off + 2:] if replace_count == 1 { // Simple 1:1, just replace in place glyphs[i] = i32(ttUSHORT(replace_glyphs)) } else if replace_count == 0 { // Delete glyph for j in i.. 1 && n + replace_count - 1 <= i32(len(glyphs)) { // Expand: shift right to make room shift := replace_count - 1 for j := n - 1; j > i; j -= 1 { glyphs[j + shift] = glyphs[j] } for ri in 0.. i32 { format := ttUSHORT(data[subtable:]) if format != 1 do return i32(len(glyphs)) coverage_off := subtable + u32(ttUSHORT(data[subtable + 2:])) lig_set_count := i32(ttUSHORT(data[subtable + 4:])) lig_set_offsets := data[subtable + 6:] n := i32(len(glyphs)) i: i32 = 0 for i < n { ci := get_coverage_index(data[coverage_off:], glyphs[i]) if ci >= 0 && ci < lig_set_count { lig_set_off := subtable + u32(ttUSHORT(lig_set_offsets[ci * 2:])) lig_count := i32(ttUSHORT(data[lig_set_off:])) lig_offsets := data[lig_set_off + 2:] matched := false for li in 0.. n do continue match := true for ci2 in 0.. i32 { format := ttUSHORT(data[subtable:]) if format != 3 do return i32(len(glyphs)) // Only format 3 for now lookup_list := gsub + u32(ttUSHORT(data[gsub + 8:])) n := i32(len(glyphs)) // Parse chaining context format 3 off: u32 = subtable + 2 // Backtrack coverages backtrack_count := i32(ttUSHORT(data[off:])) off += 2 backtrack_covs: [16]u32 for bi in 0.. 16 do off += u32(backtrack_count - 16) * 2 // Input coverages input_count := i32(ttUSHORT(data[off:])) off += 2 input_covs: [16]u32 for ii in 0.. 16 do off += u32(input_count - 16) * 2 // Lookahead coverages lookahead_count := i32(ttUSHORT(data[off:])) off += 2 lookahead_covs: [16]u32 for li in 0.. 16 do off += u32(lookahead_count - 16) * 2 // Substitution lookup records subst_count := i32(ttUSHORT(data[off:])) off += 2 subst_records := data[off:] // Apply to glyph buffer i: i32 = 0 for i < n { // Check if enough glyphs for backtrack + input + lookahead if i < backtrack_count { i += 1; continue } if i + input_count + lookahead_count > n do break // Match input coverages input_match := true for ii in 0..= 0 && target < n { // Apply the referenced lookup to just this position one_glyph := glyphs[target:target+1] gsub_apply_lookup(info, data, gsub, lookup_list, lookup_idx, one_glyph) } } i += input_count } return n }