package font // ============================================================================ // VARIABLE FONTS (fvar/avar) // ============================================================================ // A variation axis defined in the fvar table Variation_Axis :: struct { tag: [4]u8, // 4-byte axis tag (e.g. "wght", "wdth", "ital") min_value: f32, def_value: f32, max_value: f32, name_id: u16, hidden: bool, } // A named instance defined in the fvar table Named_Instance :: struct { name_id: u16, coords: [8]f32, // normalized coordinates (up to 8 axes) } // Get the number of variation axes in this font. Returns 0 for non-variable fonts. get_variation_axis_count :: proc(info: ^Font_Info) -> i32 { fvar := find_table(info.data, u32(info.fontstart), "fvar") if fvar == 0 do return 0 data := info.data[fvar:] version := ttULONG(data) if version != 0x00010000 do return 0 return i32(ttUSHORT(data[8:])) } // Get information about a specific variation axis. Returns false if index is out of range. get_variation_axis :: proc(info: ^Font_Info, axis_index: i32, axis: ^Variation_Axis) -> bool { fvar := find_table(info.data, u32(info.fontstart), "fvar") if fvar == 0 do return false data := info.data[fvar:] version := ttULONG(data) if version != 0x00010000 do return false axes_offset := u32(ttUSHORT(data[4:])) axis_count := i32(ttUSHORT(data[8:])) axis_size := i32(ttUSHORT(data[10:])) if axis_index < 0 || axis_index >= axis_count do return false rec := data[axes_offset + u32(axis_index) * u32(axis_size):] axis.tag[0] = rec[0] axis.tag[1] = rec[1] axis.tag[2] = rec[2] axis.tag[3] = rec[3] axis.min_value = fixed_to_f32(ttULONG(rec[4:])) axis.def_value = fixed_to_f32(ttULONG(rec[8:])) axis.max_value = fixed_to_f32(ttULONG(rec[12:])) flags := ttUSHORT(rec[16:]) axis.name_id = ttUSHORT(rec[18:]) axis.hidden = (flags >> 3) & 1 == 1 // Clamp to ensure min <= def <= max if axis.min_value > axis.def_value do axis.min_value = axis.def_value if axis.max_value < axis.def_value do axis.max_value = axis.def_value return true } // Get all variation axes. Returns the number of axes written. get_variation_axes :: proc(info: ^Font_Info, axes: []Variation_Axis) -> i32 { count := get_variation_axis_count(info) n := min(count, i32(len(axes))) for i in 0.. f32 { v := clamp(value, axis.min_value, axis.max_value) if v == axis.def_value { return 0 } else if v < axis.def_value { if axis.def_value == axis.min_value do return 0 return (v - axis.def_value) / (axis.def_value - axis.min_value) } else { if axis.max_value == axis.def_value do return 0 return (v - axis.def_value) / (axis.max_value - axis.def_value) } } // Check if this font is a variable font (has an fvar table with axes) is_variable_font :: proc(info: ^Font_Info) -> bool { return get_variation_axis_count(info) > 0 } // Get the number of named instances in the font get_named_instance_count :: proc(info: ^Font_Info) -> i32 { fvar := find_table(info.data, u32(info.fontstart), "fvar") if fvar == 0 do return 0 data := info.data[fvar:] version := ttULONG(data) if version != 0x00010000 do return 0 return i32(ttUSHORT(data[6:])) } // Convert 16.16 fixed-point to f32 @(private) fixed_to_f32 :: proc(v: u32) -> f32 { return f32(i32(v)) / 65536.0 } // Maximum number of variation axes supported MAX_VAR_AXES :: 8 // Normalized variation coordinates (one per axis, range [-1, 1]) Var_Coords :: [MAX_VAR_AXES]f32 // Set variation coordinates from user-space axis values (e.g. weight=700). // Applies fvar normalization and avar remapping. // Returns the number of axes set. set_variation :: proc(info: ^Font_Info, coords: ^Var_Coords, tags: []string, values: []f32) -> i32 { axis_count := get_variation_axis_count(info) if axis_count == 0 do return 0 // Reset to defaults for i in 0..= 2 { // Convert normalized coord to 2.14 fixed-point for mapping value := i16(clamp(coords[ai], -1, 1) * 16384.0) mapped := avar_map_value(data[off:], pair_count, value) coords[ai] = f32(mapped) / 16384.0 } off += u32(pair_count) * 4 // each pair is 4 bytes (2x i16) } } @(private) avar_map_value :: proc(pairs: [^]u8, count: i32, value: i16) -> i16 { // pairs are (fromCoord, toCoord) as F2DOT14 (i16) from0 := i16(ttUSHORT(pairs[0:])) to0 := i16(ttUSHORT(pairs[2:])) if value <= from0 { return value - from0 + to0 } for i in 1..