use super::gvar::{decode_packed_deltas, decode_packed_points, f2dot14, tuple_scalar};
use crate::parser::{read_i16, read_u16};
use crate::Error;
const TI_EMBEDDED_PEAK: u16 = 0x8000;
const TI_INTERMEDIATE: u16 = 0x4000;
const TI_PRIVATE_POINTS: u16 = 0x2000;
const COUNT_MASK: u16 = 0x0FFF;
const MAX_TUPLES: u16 = 4095;
#[derive(Debug, Clone)]
pub struct CvarTable<'a> {
bytes: &'a [u8],
major_version: u16,
minor_version: u16,
tuple_count: u16,
data_offset: usize,
}
impl<'a> CvarTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let major_version = read_u16(bytes, 0)?;
let minor_version = read_u16(bytes, 2)?;
if major_version != 1 {
return Err(Error::BadStructure("cvar majorVersion != 1"));
}
let packed = read_u16(bytes, 4)?;
let tuple_count = packed & COUNT_MASK;
if tuple_count > MAX_TUPLES {
return Err(Error::BadStructure("cvar tupleVariationCount > cap"));
}
let data_offset = read_u16(bytes, 6)? as usize;
if data_offset > bytes.len() {
return Err(Error::BadOffset);
}
Ok(Self {
bytes,
major_version,
minor_version,
tuple_count,
data_offset,
})
}
pub fn major_version(&self) -> u16 {
self.major_version
}
pub fn minor_version(&self) -> u16 {
self.minor_version
}
pub fn tuple_count(&self) -> u16 {
self.tuple_count
}
pub fn cvt_deltas(
&self,
axis_count: u16,
cvt_count: u16,
coords: &[f32],
) -> Result<Vec<i32>, Error> {
let mut out = vec![0i32; cvt_count as usize];
if self.tuple_count == 0 {
return Ok(out);
}
let bytes = self.bytes;
let axis_count = axis_count as usize;
let mut hdr_off = 8usize;
let mut data_cursor = self.data_offset;
let shared_points: Option<Vec<u16>> = if self.data_offset < bytes.len() {
let (pts, used) = decode_packed_points(&bytes[self.data_offset..], cvt_count)?;
data_cursor = self.data_offset + used;
Some(pts)
} else {
None
};
for _ in 0..self.tuple_count {
if hdr_off + 4 > bytes.len() {
return Err(Error::BadStructure("cvar tuple header truncated"));
}
let var_data_size = read_u16(bytes, hdr_off)? as usize;
let tuple_index = read_u16(bytes, hdr_off + 2)?;
hdr_off += 4;
let peak: Option<Vec<f32>> = if tuple_index & TI_EMBEDDED_PEAK != 0 {
let need = axis_count * 2;
if hdr_off + need > bytes.len() {
return Err(Error::BadStructure("cvar embedded peak truncated"));
}
let mut p = Vec::with_capacity(axis_count);
for ai in 0..axis_count {
p.push(f2dot14(read_i16(bytes, hdr_off + ai * 2)?));
}
hdr_off += need;
Some(p)
} else {
None
};
let (start_t, end_t) = if tuple_index & TI_INTERMEDIATE != 0 {
let need = axis_count * 4;
if hdr_off + need > bytes.len() {
return Err(Error::BadStructure("cvar intermediate region truncated"));
}
let mut s = Vec::with_capacity(axis_count);
let mut e = Vec::with_capacity(axis_count);
for ai in 0..axis_count {
s.push(f2dot14(read_i16(bytes, hdr_off + ai * 2)?));
}
for ai in 0..axis_count {
e.push(f2dot14(read_i16(bytes, hdr_off + axis_count * 2 + ai * 2)?));
}
hdr_off += need;
(Some(s), Some(e))
} else {
(None, None)
};
if data_cursor + var_data_size > bytes.len() {
return Err(Error::BadStructure("cvar tuple data overruns"));
}
let tuple_data = &bytes[data_cursor..data_cursor + var_data_size];
data_cursor += var_data_size;
let peak = match peak {
Some(p) => p,
None => continue,
};
let scalar = tuple_scalar(coords, &peak, start_t.as_deref(), end_t.as_deref());
if scalar == 0.0 {
continue;
}
let mut td_off = 0usize;
let points = if tuple_index & TI_PRIVATE_POINTS != 0 {
let (pts, used) = decode_packed_points(tuple_data, cvt_count)?;
td_off += used;
pts
} else {
shared_points
.clone()
.unwrap_or_else(|| (0..cvt_count).collect())
};
let n = points.len();
let deltas = decode_packed_deltas(tuple_data, &mut td_off, n)?;
for (i, &cvt_idx) in points.iter().enumerate() {
let ci = cvt_idx as usize;
if ci >= out.len() {
continue;
}
out[ci] += (deltas[i] as f32 * scalar).round() as i32;
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn packed_deltas_i8(vals: &[i8]) -> Vec<u8> {
let mut b = Vec::new();
b.push((vals.len() as u8 - 1) & 0x3F);
for &v in vals {
b.push(v as u8);
}
b
}
fn build_cvar_one_tuple(deltas: &[i8]) -> Vec<u8> {
let header_len = 8;
let tvh_len = 4 + 2; let data_off = header_len + tvh_len;
let delta_bytes = packed_deltas_i8(deltas);
let mut b = vec![0u8; data_off];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[2..4].copy_from_slice(&0u16.to_be_bytes()); b[4..6].copy_from_slice(&1u16.to_be_bytes()); b[6..8].copy_from_slice(&(data_off as u16).to_be_bytes()); b[8..10].copy_from_slice(&(delta_bytes.len() as u16).to_be_bytes());
b[10..12].copy_from_slice(&TI_EMBEDDED_PEAK.to_be_bytes()); b[12..14].copy_from_slice(&0x4000u16.to_be_bytes()); b.push(0x00);
b.extend_from_slice(&delta_bytes);
b
}
#[test]
fn parses_header() {
let raw = build_cvar_one_tuple(&[1, 2, 3]);
let cvar = CvarTable::parse(&raw).expect("parse");
assert_eq!(cvar.major_version(), 1);
assert_eq!(cvar.minor_version(), 0);
assert_eq!(cvar.tuple_count(), 1);
}
#[test]
fn rejects_bad_major_version() {
let mut raw = build_cvar_one_tuple(&[1, 2, 3]);
raw[0..2].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(
CvarTable::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_truncated_header() {
assert!(matches!(
CvarTable::parse(&[0u8; 4]),
Err(Error::UnexpectedEof)
));
}
#[test]
fn full_scalar_applies_all_deltas() {
let raw = build_cvar_one_tuple(&[10, -5, 20]);
let cvar = CvarTable::parse(&raw).expect("parse");
let d = cvar.cvt_deltas(1, 3, &[1.0]).expect("deltas");
assert_eq!(d, vec![10, -5, 20]);
}
#[test]
fn half_scalar_interpolates_deltas() {
let raw = build_cvar_one_tuple(&[10, -5, 20]);
let cvar = CvarTable::parse(&raw).expect("parse");
let d = cvar.cvt_deltas(1, 3, &[0.5]).expect("deltas");
assert_eq!(d[0], 5);
assert_eq!(d[2], 10);
assert_eq!(d[1], -3);
}
#[test]
fn zero_coord_yields_no_deltas() {
let raw = build_cvar_one_tuple(&[10, -5, 20]);
let cvar = CvarTable::parse(&raw).expect("parse");
let d = cvar.cvt_deltas(1, 3, &[0.0]).expect("deltas");
assert_eq!(d, vec![0, 0, 0]);
}
#[test]
fn deltas_padded_to_cvt_count() {
let raw = build_cvar_one_tuple(&[10, 20, 30]);
let cvar = CvarTable::parse(&raw).expect("parse");
let d = cvar.cvt_deltas(1, 3, &[1.0]).expect("deltas");
assert_eq!(d.len(), 3);
assert_eq!(d, vec![10, 20, 30]);
}
}