package font import "core:mem" import "core:math" // ============================================================================ // GVAR — Glyph Variations (TrueType outline deltas) // ============================================================================ // Apply glyph variation deltas to a set of points. // Takes raw glyph points (x, y pairs as i16) and modifies them in-place // based on the normalized variation coordinates. // Returns true if deltas were applied. apply_gvar_deltas :: proc( info: ^Font_Info, glyph_index: i32, coords: ^Var_Coords, points_x: []f32, points_y: []f32, ) -> bool { gvar := find_table(info.data, u32(info.fontstart), "gvar") if gvar == 0 do return false data := info.data[gvar:] version := ttULONG(data) if version != 0x00010000 do return false axis_count := i32(ttUSHORT(data[4:])) shared_tuple_count := i32(ttUSHORT(data[6:])) shared_tuples_off := ttULONG(data[8:]) glyph_count := i32(ttUSHORT(data[12:])) flags := ttUSHORT(data[14:]) gvar_data_off := ttULONG(data[16:]) if glyph_index < 0 || glyph_index >= glyph_count do return false long_offsets := (flags & 1) != 0 num_points := i32(len(points_x)) // Get offset to this glyph's variation data var_off, var_end: u32 if long_offsets { off_arr := data[20:] var_off = gvar_data_off + ttULONG(off_arr[u32(glyph_index) * 4:]) var_end = gvar_data_off + ttULONG(off_arr[u32(glyph_index + 1) * 4:]) } else { off_arr := data[20:] var_off = gvar_data_off + u32(ttUSHORT(off_arr[u32(glyph_index) * 2:])) * 2 var_end = gvar_data_off + u32(ttUSHORT(off_arr[u32(glyph_index + 1) * 2:])) * 2 } if var_off == var_end do return false // no variation data if var_off >= var_end do return false // Parse glyph variation data header gd := data[var_off:] tuple_count_raw := ttUSHORT(gd) data_offset := u32(ttUSHORT(gd[2:])) tuple_count := i32(tuple_count_raw & 0x0FFF) has_shared_points := (tuple_count_raw & 0x8000) != 0 serialized := gd[data_offset:] // packed deltas start here ser_off: u32 = 0 // Parse shared points if present shared_points: [256]i32 shared_point_count: i32 = 0 if has_shared_points { shared_point_count, ser_off = unpack_points(serialized, shared_points[:]) } // Process each tuple hdr_off: u32 = 4 for ti in 0.. 0 { for i in 0..= 0 && idx < num_points { points_x[idx] += f32(x_deltas[di]) * scalar points_y[idx] += f32(y_deltas[di]) * scalar } } ser_off += var_data_size } return true } // ============================================================================ // Internal helpers // ============================================================================ @(private) compute_tuple_scalar :: proc( coords: ^Var_Coords, peak: ^[MAX_VAR_AXES]f32, has_intermediate: bool, start_tuple: ^[MAX_VAR_AXES]f32, end_tuple: ^[MAX_VAR_AXES]f32, axis_count: i32, ) -> f32 { scalar: f32 = 1.0 for ai in 0.. e do return 0 if v < p { if p != s do scalar *= (v - s) / (p - s) } else { if p != e do scalar *= (e - v) / (e - p) } } else { if v == 0 do return 0 if (v < 0 && p > 0) || (v > 0 && p < 0) do return 0 if (v < 0 && v < p) || (v > 0 && v > p) do return 0 scalar *= v / p } } return scalar } // Unpack run-length encoded point indices. // Returns (count, bytes_consumed). Count=0 means "all points". @(private) unpack_points :: proc(data: [^]u8, points: []i32) -> (i32, u32) { count: i32 off: u32 = 0 // First byte(s): total count b0 := data[0] off = 1 if b0 & 0x80 != 0 { count = (i32(b0 & 0x7F) << 8) | i32(data[1]) off = 2 } else { count = i32(b0) } if count == 0 do return 0, off // "all points" n: i32 = 0 for n < count && n < i32(len(points)) { ctrl := data[off] off += 1 run_count := i32(ctrl & 0x7F) + 1 is_words := (ctrl & 0x80) != 0 for ri in 0..= i32(len(points)) do break if is_words { val := i32(ttUSHORT(data[off:])) off += 2 points[n] = val + (points[n - 1] if n > 0 else 0) } else { val := i32(data[off]) off += 1 points[n] = val + (points[n - 1] if n > 0 else 0) } n += 1 } } return count, off } // Unpack run-length encoded deltas. Returns bytes consumed. @(private) unpack_deltas :: proc(data: [^]u8, deltas: []i16) -> i32 { off: i32 = 0 n: i32 = 0 count := i32(len(deltas)) for n < count { ctrl := data[off] off += 1 run_count := i32(ctrl & 0x3F) + 1 if ctrl & 0x80 != 0 { // DELTAS_ARE_ZERO for ri in 0..= count do break deltas[n] = 0 n += 1 } } else if ctrl & 0x40 != 0 { // DELTAS_ARE_WORDS (i16) for ri in 0..= count do break deltas[n] = i16(ttUSHORT(data[off:])) off += 2 n += 1 } } else { // DELTAS_ARE_BYTES (i8) for ri in 0..= count do break deltas[n] = i16(i8(data[off])) off += 1 n += 1 } } } return off }