use crate::parser::{read_i16, read_u16, read_u32};
use crate::tables::hvar::DeltaSetIndexMap;
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
const MAX_SEGMENTS: u16 = 256;
#[derive(Debug, Clone, Default)]
pub struct AvarTable {
segments: Vec<Vec<(f32, f32)>>,
axis_index_map: Option<DeltaSetIndexMap>,
axis_index_map_unsupported: bool,
var_store: Option<ItemVariationStore>,
}
impl AvarTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
if major != 1 && major != 2 {
return Ok(Self::default());
}
let axis_count = read_u16(bytes, 6)?;
let mut off = 8usize;
let mut segments = Vec::with_capacity(axis_count as usize);
for _ in 0..axis_count {
if off + 2 > bytes.len() {
return Err(Error::UnexpectedEof);
}
let n = read_u16(bytes, off)?;
off += 2;
if n > MAX_SEGMENTS {
return Err(Error::BadStructure("avar segment count exceeds cap"));
}
let need = (n as usize).checked_mul(4).ok_or(Error::BadOffset)?;
if off + need > bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut list = Vec::with_capacity(n as usize);
let mut prev_from = f32::NEG_INFINITY;
for _ in 0..n {
let from = f2dot14(read_i16(bytes, off)?);
let to = f2dot14(read_i16(bytes, off + 2)?);
off += 4;
if from < prev_from {
return Err(Error::BadStructure("avar fromCoord not ascending"));
}
prev_from = from;
list.push((from, to));
}
segments.push(list);
}
let mut table = Self {
segments,
..Self::default()
};
if major == 2 {
if off + 8 > bytes.len() {
return Err(Error::UnexpectedEof);
}
let axis_index_map_off = read_u32(bytes, off)? as usize;
let var_store_off = read_u32(bytes, off + 4)? as usize;
if axis_index_map_off != 0 {
if axis_index_map_off >= bytes.len() {
return Err(Error::BadOffset);
}
match DeltaSetIndexMap::parse(&bytes[axis_index_map_off..]) {
Ok(map) => table.axis_index_map = Some(map),
Err(_) => table.axis_index_map_unsupported = true,
}
}
if var_store_off != 0 {
if var_store_off >= bytes.len() {
return Err(Error::BadOffset);
}
table.var_store = Some(ItemVariationStore::parse(&bytes[var_store_off..])?);
}
}
Ok(table)
}
pub fn remap_normalised(&self, axis_index: usize, n: f32) -> f32 {
let n = n.clamp(-1.0, 1.0);
let segs = match self.segments.get(axis_index) {
Some(s) if !s.is_empty() => s,
_ => return n,
};
if n <= segs[0].0 {
return segs[0].1;
}
if n >= segs[segs.len() - 1].0 {
return segs[segs.len() - 1].1;
}
for w in segs.windows(2) {
let (f0, t0) = w[0];
let (f1, t1) = w[1];
if n >= f0 && n <= f1 {
if (f1 - f0).abs() < f32::EPSILON {
return t0;
}
let alpha = (n - f0) / (f1 - f0);
return t0 + alpha * (t1 - t0);
}
}
n
}
pub fn remap_vector(&self, initial: &[f32]) -> Vec<f32> {
let intermediate: Vec<f32> = initial
.iter()
.enumerate()
.map(|(i, &n)| self.remap_normalised(i, n))
.collect();
let Some(store) = self.var_store.as_ref() else {
return intermediate;
};
if self.axis_index_map_unsupported {
return intermediate;
}
intermediate
.iter()
.enumerate()
.map(|(i, &v)| {
let (outer, inner) = match self.axis_index_map.as_ref() {
Some(map) if !map.is_empty() => {
let entries = map.entries();
entries[i.min(entries.len() - 1)]
}
_ => ((i >> 16) as u16, (i & 0xFFFF) as u16),
};
let delta = store.delta(outer, inner, &intermediate).unwrap_or(0.0);
((v * 16384.0 + delta.round()).clamp(-16384.0, 16384.0)) / 16384.0
})
.collect()
}
pub fn has_cross_axis_mapping(&self) -> bool {
self.var_store.is_some() && !self.axis_index_map_unsupported
}
pub fn axis_index_map_unsupported(&self) -> bool {
self.axis_index_map_unsupported
}
pub fn axis_count(&self) -> usize {
self.segments.len()
}
}
#[inline]
fn f2dot14(raw: i16) -> f32 {
raw as f32 / 16384.0
}
#[cfg(test)]
mod tests {
use super::*;
fn build_empty(axis_count: u16) -> Vec<u8> {
let mut b = vec![0u8; 8 + (axis_count as usize) * 2];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[6..8].copy_from_slice(&axis_count.to_be_bytes());
b
}
fn push_two_axis_ivs(b: &mut Vec<u8>, peak_axis: usize, rows: &[i16]) -> u32 {
let ivs = b.len() as u32;
b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&12u32.to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&28u32.to_be_bytes()); b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); for a in 0..2usize {
let peak: i16 = if a == peak_axis { 16384 } else { 0 };
b.extend_from_slice(&0i16.to_be_bytes());
b.extend_from_slice(&peak.to_be_bytes());
b.extend_from_slice(&peak.to_be_bytes());
}
b.extend_from_slice(&(rows.len() as u16).to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); for &d in rows {
b.extend_from_slice(&d.to_be_bytes());
}
ivs
}
fn build_v2(peak_axis: usize, rows: &[i16], map: Option<&[u8]>) -> Vec<u8> {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&2u16.to_be_bytes()); b[6..8].copy_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); let map_slot = b.len();
b.extend_from_slice(&0u32.to_be_bytes()); let store_slot = b.len();
b.extend_from_slice(&0u32.to_be_bytes()); if let Some(map_bytes) = map {
let off = b.len() as u32;
b.extend_from_slice(map_bytes);
b[map_slot..map_slot + 4].copy_from_slice(&off.to_be_bytes());
}
let ivs = push_two_axis_ivs(&mut b, peak_axis, rows);
b[store_slot..store_slot + 4].copy_from_slice(&ivs.to_be_bytes());
b
}
#[test]
fn avar_remap_identity_when_no_segments() {
let raw = build_empty(2);
let a = AvarTable::parse(&raw).expect("parse");
for &v in &[-1.0f32, -0.5, 0.0, 0.25, 1.0] {
assert_eq!(a.remap_normalised(0, v), v);
assert_eq!(a.remap_normalised(1, v), v);
}
assert_eq!(a.remap_normalised(99, 0.5), 0.5);
assert_eq!(a.remap_vector(&[0.25, -0.5]), vec![0.25, -0.5]);
assert!(!a.has_cross_axis_mapping());
}
#[test]
fn avar_remap_identity_segments() {
let mut b = vec![0u8; 8 + 2 + 12];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
b[8..10].copy_from_slice(&3u16.to_be_bytes());
for (i, &v) in [-16384i16, -16384, 0, 0, 16384, 16384].iter().enumerate() {
let off = 10 + i * 2;
b[off..off + 2].copy_from_slice(&v.to_be_bytes());
}
let a = AvarTable::parse(&b).unwrap();
for &v in &[-1.0f32, -0.5, 0.0, 0.25, 1.0] {
assert!((a.remap_normalised(0, v) - v).abs() < 1e-6);
}
}
#[test]
fn avar_remap_piecewise_linear() {
let mut b = vec![0u8; 8 + 2 + 16];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
b[8..10].copy_from_slice(&4u16.to_be_bytes());
let pairs: [(i16, i16); 4] = [
(-16384, -16384),
(0, 0),
(16384 / 2, 16384 / 4),
(16384, 16384),
];
for (i, (f, t)) in pairs.iter().enumerate() {
let off = 10 + i * 4;
b[off..off + 2].copy_from_slice(&f.to_be_bytes());
b[off + 2..off + 4].copy_from_slice(&t.to_be_bytes());
}
let a = AvarTable::parse(&b).unwrap();
assert!(a.remap_normalised(0, 0.0).abs() < 1e-6);
assert!((a.remap_normalised(0, 0.25) - 0.125).abs() < 1e-4);
assert!((a.remap_normalised(0, 0.75) - 0.625).abs() < 1e-4);
assert!((a.remap_normalised(0, 1.0) - 1.0).abs() < 1e-4);
}
#[test]
fn avar_unknown_major_falls_back_to_identity() {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&3u16.to_be_bytes()); let a = AvarTable::parse(&b).expect("parse");
assert_eq!(a.remap_normalised(0, 0.5), 0.5);
assert_eq!(a.remap_vector(&[0.5, -0.25]), vec![0.5, -0.25]);
}
#[test]
fn avar_v2_cross_axis_delta() {
let b = build_v2(1, &[-8192, 0], None);
let a = AvarTable::parse(&b).expect("parse");
assert!(a.has_cross_axis_mapping());
assert!(!a.axis_index_map_unsupported());
let out = a.remap_vector(&[0.5, 0.0]);
assert!((out[0] - 0.5).abs() < 1e-6 && out[1].abs() < 1e-6);
let out = a.remap_vector(&[0.5, 1.0]);
assert!((out[0] - 0.0).abs() < 1e-6, "{out:?}");
assert!((out[1] - 1.0).abs() < 1e-6);
let out = a.remap_vector(&[0.5, 0.5]);
assert!((out[0] - 0.25).abs() < 1e-4, "{out:?}");
}
#[test]
fn avar_v2_self_reference_and_clamp() {
let b = build_v2(0, &[16384, 0], None);
let a = AvarTable::parse(&b).expect("parse");
let out = a.remap_vector(&[0.5, 0.0]);
assert!((out[0] - 1.0).abs() < 1e-6, "{out:?}");
let out = a.remap_vector(&[1.0, 0.0]);
assert!((out[0] - 1.0).abs() < 1e-6, "{out:?}");
}
#[test]
fn avar_v2_stage3_uses_stage2_intermediate_coords() {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&2u16.to_be_bytes());
b[6..8].copy_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&3u16.to_be_bytes()); for (f, t) in [(-16384i16, -16384i16), (0, 0), (16384, 8192)] {
b.extend_from_slice(&f.to_be_bytes());
b.extend_from_slice(&t.to_be_bytes());
}
let map_slot = b.len();
b.extend_from_slice(&0u32.to_be_bytes());
let store_slot = b.len();
b.extend_from_slice(&0u32.to_be_bytes());
let _ = map_slot; let ivs = push_two_axis_ivs(&mut b, 1, &[-8192, 0]);
b[store_slot..store_slot + 4].copy_from_slice(&ivs.to_be_bytes());
let a = AvarTable::parse(&b).expect("parse");
let out = a.remap_vector(&[0.5, 1.0]);
assert!((out[1] - 0.5).abs() < 1e-6, "{out:?}");
assert!((out[0] - 0.25).abs() < 1e-4, "{out:?}");
}
#[test]
fn avar_v2_axis_index_map_routes_and_clamps() {
let map: Vec<u8> = {
let mut m = Vec::new();
m.extend_from_slice(&0x003Fu16.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&1u32.to_be_bytes()); m
};
let b = build_v2(1, &[8192, -4096], Some(&map));
let a = AvarTable::parse(&b).expect("parse");
let out = a.remap_vector(&[0.0, 1.0]);
assert!((out[0] - (-0.25)).abs() < 1e-4, "{out:?}");
assert!((out[1] - 0.75).abs() < 1e-4, "{out:?}");
}
#[test]
fn avar_v2_format1_axis_index_map_routes_stage3() {
let map: Vec<u8> = vec![0x01, 0x00, 0, 0, 0, 1, 0x00];
let b = build_v2(1, &[-8192, 0], Some(&map));
let a = AvarTable::parse(&b).expect("parse");
assert!(!a.axis_index_map_unsupported());
assert!(a.has_cross_axis_mapping());
let out = a.remap_vector(&[0.5, 1.0]);
assert!((out[0] - 0.0).abs() < 1e-4, "{out:?}");
assert!((out[1] - 0.5).abs() < 1e-4, "{out:?}");
}
#[test]
fn avar_v2_unknown_map_format_degrades_to_v1() {
let map: Vec<u8> = vec![0x02, 0x00, 0, 1, 0x00];
let b = build_v2(1, &[-8192, 0], Some(&map));
let a = AvarTable::parse(&b).expect("parse");
assert!(a.axis_index_map_unsupported());
assert!(!a.has_cross_axis_mapping());
let out = a.remap_vector(&[0.5, 1.0]);
assert_eq!(out, vec![0.5, 1.0], "no stage-3 movement");
}
#[test]
fn avar_v2_without_store_is_stage2_only() {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&2u16.to_be_bytes());
b[6..8].copy_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&0u32.to_be_bytes());
b.extend_from_slice(&0u32.to_be_bytes());
let a = AvarTable::parse(&b).expect("parse");
assert!(!a.has_cross_axis_mapping());
assert_eq!(a.remap_vector(&[0.5, -1.0]), vec![0.5, -1.0]);
}
#[test]
fn avar_v2_truncated_offsets_rejected() {
let mut b = vec![0u8; 8 + 2];
b[0..2].copy_from_slice(&2u16.to_be_bytes());
b[6..8].copy_from_slice(&1u16.to_be_bytes());
assert!(matches!(AvarTable::parse(&b), Err(Error::UnexpectedEof)));
}
}