package font // ============================================================================ // KERNING // ============================================================================ // Get the length of the kerning table (number of entries) get_kerning_table_length :: proc(info: ^Font_Info) -> i32 { data := info.data[info.kern:] // we only look at the first table. it must be 'horizontal' and format 0. if info.kern == 0 { return 0 } if ttUSHORT(data[2:]) < 1 { // number of tables, need at least 1 return 0 } if ttUSHORT(data[8:]) != 1 { // horizontal flag must be set in format return 0 } return i32(ttUSHORT(data[10:])) } // Get the complete kerning table // Returns how many entries were written (never more than table_length) get_kerning_table :: proc(info: ^Font_Info, table: [^]Kerning_Entry, table_length: i32) -> i32 { data := info.data[info.kern:] // we only look at the first table. it must be 'horizontal' and format 0. if info.kern == 0 { return 0 } if ttUSHORT(data[2:]) < 1 { // number of tables, need at least 1 return 0 } if ttUSHORT(data[8:]) != 1 { // horizontal flag must be set in format return 0 } length := i32(ttUSHORT(data[10:])) if table_length < length { length = table_length } for k: i32 = 0; k < length; k += 1 { table[k].glyph1 = i32(ttUSHORT(data[18 + k*6:])) table[k].glyph2 = i32(ttUSHORT(data[20 + k*6:])) table[k].advance = i32(ttSHORT(data[22 + k*6:])) } return length } // Private: Get kerning advance from kern table using binary search @(private) get_glyph_kern_info_advance :: proc(info: ^Font_Info, glyph1: i32, glyph2: i32) -> i32 { data := info.data[info.kern:] // we only look at the first table. it must be 'horizontal' and format 0. if info.kern == 0 { return 0 } if ttUSHORT(data[2:]) < 1 { // number of tables, need at least 1 return 0 } if ttUSHORT(data[8:]) != 1 { // horizontal flag must be set in format return 0 } l: i32 = 0 r: i32 = i32(ttUSHORT(data[10:])) - 1 needle := u32(glyph1) << 16 | u32(glyph2) for l <= r { m := (l + r) >> 1 straw := ttULONG(data[18 + m*6:]) // note: unaligned read if needle < straw { r = m - 1 } else if needle > straw { l = m + 1 } else { return i32(ttSHORT(data[22 + m*6:])) } } return 0 } // Private: Get coverage index from a GPOS coverage table @(private) get_coverage_index :: proc(coverage_table: [^]u8, glyph: i32) -> i32 { coverage_format := ttUSHORT(coverage_table) switch coverage_format { case 1: glyph_count := i32(ttUSHORT(coverage_table[2:])) // Binary search l: i32 = 0 r: i32 = glyph_count - 1 needle := glyph for l <= r { glyph_array := coverage_table[4:] m := (l + r) >> 1 glyph_id := i32(ttUSHORT(glyph_array[2 * m:])) straw := glyph_id if needle < straw { r = m - 1 } else if needle > straw { l = m + 1 } else { return m } } case 2: range_count := i32(ttUSHORT(coverage_table[2:])) range_array := coverage_table[4:] // Binary search l: i32 = 0 r: i32 = range_count - 1 needle := glyph for l <= r { m := (l + r) >> 1 range_record := range_array[6 * m:] straw_start := i32(ttUSHORT(range_record)) straw_end := i32(ttUSHORT(range_record[2:])) if needle < straw_start { r = m - 1 } else if needle > straw_end { l = m + 1 } else { start_coverage_index := i32(ttUSHORT(range_record[4:])) return start_coverage_index + glyph - straw_start } } case: return -1 // unsupported } return -1 } // Private: Get glyph class from a GPOS class definition table @(private) get_glyph_class :: proc(class_def_table: [^]u8, glyph: i32) -> i32 { class_def_format := ttUSHORT(class_def_table) switch class_def_format { case 1: start_glyph_id := i32(ttUSHORT(class_def_table[2:])) glyph_count := i32(ttUSHORT(class_def_table[4:])) class_def1_value_array := class_def_table[6:] if glyph >= start_glyph_id && glyph < start_glyph_id + glyph_count { return i32(ttUSHORT(class_def1_value_array[2 * (glyph - start_glyph_id):])) } case 2: class_range_count := i32(ttUSHORT(class_def_table[2:])) class_range_records := class_def_table[4:] // Binary search l: i32 = 0 r: i32 = class_range_count - 1 needle := glyph for l <= r { m := (l + r) >> 1 class_range_record := class_range_records[6 * m:] straw_start := i32(ttUSHORT(class_range_record)) straw_end := i32(ttUSHORT(class_range_record[2:])) if needle < straw_start { r = m - 1 } else if needle > straw_end { l = m + 1 } else { return i32(ttUSHORT(class_range_record[4:])) } } case: return -1 // Unsupported definition type } // "All glyphs not assigned to a class fall into class 0". (OpenType spec) return 0 } // Count bits set in a ValueFormat field to determine ValueRecord size @(private) value_record_size :: proc(format: u16) -> i32 { // Each bit in the format field indicates a 16-bit value is present count: i32 = 0 f := format for f != 0 { count += i32(f & 1) f >>= 1 } return count * 2 // each field is 2 bytes } // Extract XAdvance from a ValueRecord given its format @(private) value_record_x_advance :: proc(record: [^]u8, format: u16) -> i32 { if format & 4 == 0 do return 0 // XAdvance not present // XAdvance is the 3rd field (bit 2). Count preceding fields. offset: i32 = 0 if format & 1 != 0 do offset += 2 // XPlacement if format & 2 != 0 do offset += 2 // YPlacement return i32(ttSHORT(record[offset:])) } // Private: Get kerning advance from GPOS table @(private) get_glyph_gpos_info_advance :: proc(info: ^Font_Info, glyph1: i32, glyph2: i32) -> i32 { if info.gpos == 0 { return 0 } data := info.data[info.gpos:] if ttUSHORT(data) != 1 { // Major version 1 return 0 } minor_version := ttUSHORT(data[2:]) if minor_version != 0 && minor_version != 1 { return 0 } lookup_list_offset := ttUSHORT(data[8:]) lookup_list := data[lookup_list_offset:] lookup_count := i32(ttUSHORT(lookup_list)) for i: i32 = 0; i < lookup_count; i += 1 { lookup_offset := ttUSHORT(lookup_list[2 + 2*i:]) lookup_table := lookup_list[lookup_offset:] lookup_type := i32(ttUSHORT(lookup_table)) sub_table_count := i32(ttUSHORT(lookup_table[4:])) sub_table_offsets := lookup_table[6:] for sti: i32 = 0; sti < sub_table_count; sti += 1 { subtable_offset := ttUSHORT(sub_table_offsets[2*sti:]) table := lookup_table[subtable_offset:] actual_type := lookup_type // Handle Extension Positioning (Type 9) if lookup_type == 9 { ext_format := ttUSHORT(table) if ext_format != 1 do continue actual_type = i32(ttUSHORT(table[2:])) table = table[ttULONG(table[4:]):] } if actual_type != 2 do continue // Only handle Pair Adjustment pos_format := ttUSHORT(table) coverage_offset := i32(ttUSHORT(table[2:])) coverage_index := get_coverage_index(table[coverage_offset:], glyph1) if coverage_index == -1 do continue value_format1 := ttUSHORT(table[4:]) value_format2 := ttUSHORT(table[6:]) vr1_size := value_record_size(value_format1) vr2_size := value_record_size(value_format2) switch pos_format { case 1: // Individual pair sets pair_set_count := i32(ttUSHORT(table[8:])) if coverage_index >= pair_set_count do continue pair_pos_offset := i32(ttUSHORT(table[10 + 2*coverage_index:])) pair_value_table := table[pair_pos_offset:] pair_value_count := i32(ttUSHORT(pair_value_table)) pair_value_array := pair_value_table[2:] record_size := 2 + vr1_size + vr2_size // secondGlyph + vr1 + vr2 needle := glyph2 l: i32 = 0 r := pair_value_count - 1 for l <= r { m := (l + r) >> 1 pair_value := pair_value_array[record_size * m:] second_glyph := i32(ttUSHORT(pair_value)) if needle < second_glyph { r = m - 1 } else if needle > second_glyph { l = m + 1 } else { return value_record_x_advance(pair_value[2:], value_format1) } } case 2: // Class-based pairs class_def1_offset := i32(ttUSHORT(table[8:])) class_def2_offset := i32(ttUSHORT(table[10:])) glyph1class := get_glyph_class(table[class_def1_offset:], glyph1) glyph2class := get_glyph_class(table[class_def2_offset:], glyph2) class1_count := i32(ttUSHORT(table[12:])) class2_count := i32(ttUSHORT(table[14:])) if glyph1class < 0 || glyph1class >= class1_count do continue if glyph2class < 0 || glyph2class >= class2_count do continue record_pair_size := vr1_size + vr2_size class1_records := table[16:] offset := glyph1class * class2_count * record_pair_size + glyph2class * record_pair_size return value_record_x_advance(class1_records[offset:], value_format1) } } } return 0 } // Get kerning advance for a glyph pair // Uses cache for O(1) repeated lookups. GPOS takes priority over kern. get_glyph_kern_advance :: proc(info: ^Font_Info, g1: i32, g2: i32) -> i32 { if info.gpos == 0 && info.kern == 0 do return 0 key := (u64(u32(g1)) << 32) | u64(u32(g2)) // Check cache if info.kern_cache_ready { if val, ok := info.kern_cache[key]; ok { return val } } // Compute x_advance: i32 = 0 if info.gpos != 0 { x_advance = get_glyph_gpos_info_advance(info, g1, g2) } else if info.kern != 0 { x_advance = get_glyph_kern_info_advance(info, g1, g2) } // Store in cache if !info.kern_cache_ready { info.kern_cache = make(map[u64]i32) info.kern_cache_ready = true } info.kern_cache[key] = x_advance return x_advance } // Get kerning advance for a codepoint pair get_codepoint_kern_advance :: proc(info: ^Font_Info, ch1: i32, ch2: i32) -> i32 { if info.kern == 0 && info.gpos == 0 { // if no kerning table, don't waste time looking up both codepoint->glyphs return 0 } return get_glyph_kern_advance(info, find_glyph_index(info, ch1), find_glyph_index(info, ch2)) }