use crate::parser::{read_u16, read_u32};
use crate::tables::hvar::DeltaSetIndexMap;
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
#[derive(Debug, Clone)]
pub struct VvarTable {
ivs: ItemVariationStore,
advance_map: Option<DeltaSetIndexMap>,
tsb_map: Option<DeltaSetIndexMap>,
bsb_map: Option<DeltaSetIndexMap>,
vorg_map: Option<DeltaSetIndexMap>,
}
impl VvarTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 24 {
return Err(Error::UnexpectedEof);
}
let major = read_u16(bytes, 0)?;
let _minor = read_u16(bytes, 2)?;
let ivs_off = read_u32(bytes, 4)? as usize;
let ahm_off = read_u32(bytes, 8)? as usize;
let tsb_off = read_u32(bytes, 12)? as usize;
let bsb_off = read_u32(bytes, 16)? as usize;
let vorg_off = read_u32(bytes, 20)? as usize;
if major != 1 {
return Err(Error::BadStructure("VVAR majorVersion != 1"));
}
if ivs_off == 0 || ivs_off > bytes.len() {
return Err(Error::BadOffset);
}
let ivs = ItemVariationStore::parse(&bytes[ivs_off..])?;
let advance_map = parse_optional_map(bytes, ahm_off)?;
let tsb_map = parse_optional_map(bytes, tsb_off)?;
let bsb_map = parse_optional_map(bytes, bsb_off)?;
let vorg_map = parse_optional_map(bytes, vorg_off)?;
Ok(Self {
ivs,
advance_map,
tsb_map,
bsb_map,
vorg_map,
})
}
pub fn item_variation_store(&self) -> &ItemVariationStore {
&self.ivs
}
pub fn has_advance_height_map(&self) -> bool {
self.advance_map.is_some()
}
pub fn has_tsb_map(&self) -> bool {
self.tsb_map.is_some()
}
pub fn has_bsb_map(&self) -> bool {
self.bsb_map.is_some()
}
pub fn has_vorg_map(&self) -> bool {
self.vorg_map.is_some()
}
pub fn advance_height_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let (outer, inner) = resolve_indices(glyph_id, self.advance_map.as_ref());
self.ivs.delta(outer, inner, normalised_coords)
}
pub fn tsb_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let map = self.tsb_map.as_ref()?;
let (outer, inner) = resolve_with_map(glyph_id, map);
self.ivs.delta(outer, inner, normalised_coords)
}
pub fn bsb_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let map = self.bsb_map.as_ref()?;
let (outer, inner) = resolve_with_map(glyph_id, map);
self.ivs.delta(outer, inner, normalised_coords)
}
pub fn vorg_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let map = self.vorg_map.as_ref()?;
let (outer, inner) = resolve_with_map(glyph_id, map);
self.ivs.delta(outer, inner, normalised_coords)
}
}
fn parse_optional_map(bytes: &[u8], offset: usize) -> Result<Option<DeltaSetIndexMap>, Error> {
if offset == 0 {
return Ok(None);
}
if offset > bytes.len() {
return Err(Error::BadOffset);
}
Ok(Some(DeltaSetIndexMap::parse(&bytes[offset..])?))
}
fn resolve_indices(glyph_id: u16, map: Option<&DeltaSetIndexMap>) -> (u16, u16) {
match map {
Some(m) => resolve_with_map(glyph_id, m),
None => (0, glyph_id),
}
}
fn resolve_with_map(glyph_id: u16, map: &DeltaSetIndexMap) -> (u16, u16) {
let entries = map.entries();
if entries.is_empty() {
return (0, glyph_id);
}
let idx = (glyph_id as usize).min(entries.len() - 1);
entries[idx]
}
#[cfg(test)]
mod tests {
use super::*;
fn build_minimal_vvar_no_maps() -> Vec<u8> {
let mut b = vec![0u8; 24 + 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[4..8].copy_from_slice(&24u32.to_be_bytes()); let ivs = 24usize;
b[ivs..ivs + 2].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 2..ivs + 6].copy_from_slice(&12u32.to_be_bytes());
b[ivs + 6..ivs + 8].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 8..ivs + 12].copy_from_slice(&22u32.to_be_bytes());
let rl = ivs + 12;
b[rl..rl + 2].copy_from_slice(&1u16.to_be_bytes()); b[rl + 2..rl + 4].copy_from_slice(&1u16.to_be_bytes()); b[rl + 4..rl + 6].copy_from_slice(&0i16.to_be_bytes());
b[rl + 6..rl + 8].copy_from_slice(&16384i16.to_be_bytes());
b[rl + 8..rl + 10].copy_from_slice(&16384i16.to_be_bytes());
let ivd = ivs + 22;
b[ivd..ivd + 2].copy_from_slice(&1u16.to_be_bytes()); b[ivd + 2..ivd + 4].copy_from_slice(&1u16.to_be_bytes()); b[ivd + 4..ivd + 6].copy_from_slice(&1u16.to_be_bytes()); b[ivd + 6..ivd + 8].copy_from_slice(&0u16.to_be_bytes()); b[ivd + 8..ivd + 10].copy_from_slice(&(-50i16).to_be_bytes());
b
}
#[test]
fn parses_header_without_maps() {
let raw = build_minimal_vvar_no_maps();
let v = VvarTable::parse(&raw).expect("parse");
assert!(!v.has_advance_height_map());
assert!(!v.has_tsb_map());
assert!(!v.has_bsb_map());
assert!(!v.has_vorg_map());
}
#[test]
fn implicit_advance_height_uses_gid_as_inner_index() {
let raw = build_minimal_vvar_no_maps();
let v = VvarTable::parse(&raw).unwrap();
let d = v.advance_height_delta(0, &[0.0]).expect("gid 0 in range");
assert!(d.abs() < 1e-5, "got {d}");
let d = v.advance_height_delta(0, &[1.0]).expect("gid 0 in range");
assert!((d - (-50.0)).abs() < 1e-5, "got {d}");
}
#[test]
fn implicit_advance_height_out_of_range_gid_returns_none() {
let raw = build_minimal_vvar_no_maps();
let v = VvarTable::parse(&raw).unwrap();
assert!(v.advance_height_delta(5, &[1.0]).is_none());
}
#[test]
fn tsb_bsb_vorg_without_maps_return_none() {
let raw = build_minimal_vvar_no_maps();
let v = VvarTable::parse(&raw).unwrap();
assert!(v.tsb_delta(0, &[1.0]).is_none());
assert!(v.bsb_delta(0, &[1.0]).is_none());
assert!(v.vorg_delta(0, &[1.0]).is_none());
}
fn build_vvar_with_advance_map() -> Vec<u8> {
let mut b = vec![0u8; 24 + 10 + 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
let ivs_off: u32 = 34;
b[4..8].copy_from_slice(&ivs_off.to_be_bytes());
let ahm_off: u32 = 24;
b[8..12].copy_from_slice(&ahm_off.to_be_bytes());
b[24..26].copy_from_slice(&0x0013u16.to_be_bytes());
b[26..28].copy_from_slice(&3u16.to_be_bytes());
let ivs = 34usize;
b[ivs..ivs + 2].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 2..ivs + 6].copy_from_slice(&12u32.to_be_bytes());
b[ivs + 6..ivs + 8].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 8..ivs + 12].copy_from_slice(&22u32.to_be_bytes());
let rl = ivs + 12;
b[rl..rl + 2].copy_from_slice(&1u16.to_be_bytes());
b[rl + 2..rl + 4].copy_from_slice(&1u16.to_be_bytes());
b[rl + 6..rl + 8].copy_from_slice(&16384i16.to_be_bytes());
b[rl + 8..rl + 10].copy_from_slice(&16384i16.to_be_bytes());
let ivd = ivs + 22;
b[ivd..ivd + 2].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 2..ivd + 4].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 4..ivd + 6].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 6..ivd + 8].copy_from_slice(&0u16.to_be_bytes());
b[ivd + 8..ivd + 10].copy_from_slice(&(-30i16).to_be_bytes());
b
}
#[test]
fn advance_map_routes_all_glyphs_to_same_delta() {
let raw = build_vvar_with_advance_map();
let v = VvarTable::parse(&raw).expect("parse");
assert!(v.has_advance_height_map());
for gid in 0..3 {
let d = v.advance_height_delta(gid, &[1.0]).expect("in range");
assert!((d - (-30.0)).abs() < 1e-5, "gid {gid} got {d}");
}
}
#[test]
fn advance_map_oob_glyph_clamps_to_last_entry() {
let raw = build_vvar_with_advance_map();
let v = VvarTable::parse(&raw).unwrap();
let d = v.advance_height_delta(99, &[1.0]).expect("clamped");
assert!((d - (-30.0)).abs() < 1e-5);
}
fn build_vvar_with_vorg_map() -> Vec<u8> {
let mut b = vec![0u8; 24 + 8 + 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
let ivs_off: u32 = 32;
b[4..8].copy_from_slice(&ivs_off.to_be_bytes());
let vorg_off: u32 = 24;
b[20..24].copy_from_slice(&vorg_off.to_be_bytes());
b[24..26].copy_from_slice(&0x0013u16.to_be_bytes());
b[26..28].copy_from_slice(&2u16.to_be_bytes());
let ivs = 32usize;
b[ivs..ivs + 2].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 2..ivs + 6].copy_from_slice(&12u32.to_be_bytes());
b[ivs + 6..ivs + 8].copy_from_slice(&1u16.to_be_bytes());
b[ivs + 8..ivs + 12].copy_from_slice(&22u32.to_be_bytes());
let rl = ivs + 12;
b[rl..rl + 2].copy_from_slice(&1u16.to_be_bytes());
b[rl + 2..rl + 4].copy_from_slice(&1u16.to_be_bytes());
b[rl + 6..rl + 8].copy_from_slice(&16384i16.to_be_bytes());
b[rl + 8..rl + 10].copy_from_slice(&16384i16.to_be_bytes());
let ivd = ivs + 22;
b[ivd..ivd + 2].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 2..ivd + 4].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 4..ivd + 6].copy_from_slice(&1u16.to_be_bytes());
b[ivd + 6..ivd + 8].copy_from_slice(&0u16.to_be_bytes());
b[ivd + 8..ivd + 10].copy_from_slice(&17i16.to_be_bytes());
b
}
#[test]
fn vorg_map_decoded_and_queried() {
let raw = build_vvar_with_vorg_map();
let v = VvarTable::parse(&raw).expect("parse");
assert!(v.has_vorg_map());
let d = v.vorg_delta(0, &[1.0]).expect("gid 0");
assert!((d - 17.0).abs() < 1e-5, "gid 0 got {d}");
let d = v.vorg_delta(1, &[1.0]).expect("gid 1");
assert!((d - 17.0).abs() < 1e-5);
let d = v.advance_height_delta(0, &[1.0]).expect("implicit");
assert!((d - 17.0).abs() < 1e-5);
}
#[test]
fn rejects_major_version_other_than_1() {
let mut raw = build_minimal_vvar_no_maps();
raw[0..2].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(
VvarTable::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_null_ivs_offset() {
let mut raw = build_minimal_vvar_no_maps();
raw[4..8].copy_from_slice(&0u32.to_be_bytes());
assert!(matches!(VvarTable::parse(&raw), Err(Error::BadOffset)));
}
#[test]
fn rejects_truncated_header() {
let raw = vec![0u8; 20];
assert!(matches!(VvarTable::parse(&raw), Err(Error::UnexpectedEof)));
}
#[test]
fn rejects_ivs_offset_past_end_of_table() {
let mut raw = build_minimal_vvar_no_maps();
let too_far: u32 = (raw.len() + 1) as u32;
raw[4..8].copy_from_slice(&too_far.to_be_bytes());
assert!(matches!(VvarTable::parse(&raw), Err(Error::BadOffset)));
}
#[test]
fn rejects_map_offset_past_end_of_table() {
let mut raw = build_minimal_vvar_no_maps();
let too_far: u32 = (raw.len() + 1) as u32;
raw[12..16].copy_from_slice(&too_far.to_be_bytes());
assert!(matches!(VvarTable::parse(&raw), Err(Error::BadOffset)));
}
}