use crate::parser::{read_u16, read_u32};
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
const ENTRY_FORMAT_RESERVED_MASK: u16 = 0xFFC0;
const ENTRY_FORMAT_INNER_BITS_MASK: u16 = 0x000F;
const ENTRY_FORMAT_SIZE_MASK: u16 = 0x0030;
#[derive(Debug, Clone)]
pub struct HvarTable {
ivs: ItemVariationStore,
advance_map: Option<DeltaSetIndexMap>,
lsb_map: Option<DeltaSetIndexMap>,
rsb_map: Option<DeltaSetIndexMap>,
}
#[derive(Debug, Clone)]
pub struct DeltaSetIndexMap {
entries: Vec<(u16, u16)>,
}
impl HvarTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 20 {
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 awm_off = read_u32(bytes, 8)? as usize;
let lsb_off = read_u32(bytes, 12)? as usize;
let rsb_off = read_u32(bytes, 16)? as usize;
if major != 1 {
return Err(Error::BadStructure("HVAR majorVersion != 1"));
}
if ivs_off == 0 || ivs_off > bytes.len() {
return Err(Error::BadOffset);
}
let ivs = ItemVariationStore::parse(&bytes[ivs_off..])?;
let advance_map = if awm_off != 0 {
if awm_off > bytes.len() {
return Err(Error::BadOffset);
}
Some(DeltaSetIndexMap::parse(&bytes[awm_off..])?)
} else {
None
};
let lsb_map = if lsb_off != 0 {
if lsb_off > bytes.len() {
return Err(Error::BadOffset);
}
Some(DeltaSetIndexMap::parse(&bytes[lsb_off..])?)
} else {
None
};
let rsb_map = if rsb_off != 0 {
if rsb_off > bytes.len() {
return Err(Error::BadOffset);
}
Some(DeltaSetIndexMap::parse(&bytes[rsb_off..])?)
} else {
None
};
Ok(Self {
ivs,
advance_map,
lsb_map,
rsb_map,
})
}
pub fn item_variation_store(&self) -> &ItemVariationStore {
&self.ivs
}
pub fn has_advance_width_map(&self) -> bool {
self.advance_map.is_some()
}
pub fn has_lsb_map(&self) -> bool {
self.lsb_map.is_some()
}
pub fn has_rsb_map(&self) -> bool {
self.rsb_map.is_some()
}
pub fn advance_width_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let (outer, inner) = self.resolve_indices(glyph_id, self.advance_map.as_ref());
self.ivs.delta(outer, inner, normalised_coords)
}
pub fn lsb_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let map = self.lsb_map.as_ref()?;
let (outer, inner) = resolve_with_map(glyph_id, map);
self.ivs.delta(outer, inner, normalised_coords)
}
pub fn rsb_delta(&self, glyph_id: u16, normalised_coords: &[f32]) -> Option<f32> {
let map = self.rsb_map.as_ref()?;
let (outer, inner) = resolve_with_map(glyph_id, map);
self.ivs.delta(outer, inner, normalised_coords)
}
fn resolve_indices(&self, 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) {
if map.entries.is_empty() {
return (0, glyph_id);
}
let idx = (glyph_id as usize).min(map.entries.len() - 1);
map.entries[idx]
}
impl DeltaSetIndexMap {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let entry_format = read_u16(bytes, 0)?;
let map_count = read_u16(bytes, 2)?;
if entry_format & ENTRY_FORMAT_RESERVED_MASK != 0 {
return Err(Error::BadStructure(
"HVAR DeltaSetIndexMap entryFormat reserved bits set",
));
}
let inner_bits = (entry_format & ENTRY_FORMAT_INNER_BITS_MASK) + 1;
let entry_size = (((entry_format & ENTRY_FORMAT_SIZE_MASK) >> 4) + 1) as usize;
debug_assert!((1..=4).contains(&entry_size));
let need = 4usize
.checked_add(
(map_count as usize)
.checked_mul(entry_size)
.ok_or(Error::BadOffset)?,
)
.ok_or(Error::BadOffset)?;
if need > bytes.len() {
return Err(Error::UnexpectedEof);
}
let inner_mask: u32 = (1u32 << inner_bits) - 1;
let mut entries = Vec::with_capacity(map_count as usize);
for i in 0..map_count as usize {
let off = 4 + i * entry_size;
let mut raw: u32 = 0;
for b in &bytes[off..off + entry_size] {
raw = (raw << 8) | *b as u32;
}
let outer = (raw >> inner_bits) as u16;
let inner = (raw & inner_mask) as u16;
entries.push((outer, inner));
}
Ok(Self { entries })
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[(u16, u16)] {
&self.entries
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_minimal_hvar_no_map() -> Vec<u8> {
let mut b = vec![0u8; 20 + 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[4..8].copy_from_slice(&20u32.to_be_bytes()); let ivs = 20usize;
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_hvar_no_map();
let h = HvarTable::parse(&raw).expect("parse");
assert!(!h.has_advance_width_map());
assert!(!h.has_lsb_map());
assert!(!h.has_rsb_map());
}
#[test]
fn implicit_advance_width_uses_gid_as_inner_index() {
let raw = build_minimal_hvar_no_map();
let h = HvarTable::parse(&raw).unwrap();
let d = h.advance_width_delta(0, &[0.0]).expect("gid 0 in range");
assert!(d.abs() < 1e-5, "got {d}");
let d = h.advance_width_delta(0, &[1.0]).expect("gid 0 in range");
assert!((d - (-50.0)).abs() < 1e-5, "got {d}");
}
#[test]
fn implicit_advance_width_out_of_range_gid_returns_none() {
let raw = build_minimal_hvar_no_map();
let h = HvarTable::parse(&raw).unwrap();
assert!(h.advance_width_delta(5, &[1.0]).is_none());
}
#[test]
fn lsb_rsb_without_maps_returns_none() {
let raw = build_minimal_hvar_no_map();
let h = HvarTable::parse(&raw).unwrap();
assert!(h.lsb_delta(0, &[1.0]).is_none());
assert!(h.rsb_delta(0, &[1.0]).is_none());
}
fn build_hvar_with_advance_map() -> Vec<u8> {
let mut b = vec![0u8; 20 + 10 + 32];
b[0..2].copy_from_slice(&1u16.to_be_bytes());
let ivs_off: u32 = 30;
b[4..8].copy_from_slice(&ivs_off.to_be_bytes());
let awm_off: u32 = 20;
b[8..12].copy_from_slice(&awm_off.to_be_bytes());
b[20..22].copy_from_slice(&0x0013u16.to_be_bytes());
b[22..24].copy_from_slice(&3u16.to_be_bytes());
let ivs = 30usize;
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_hvar_with_advance_map();
let h = HvarTable::parse(&raw).expect("parse");
assert!(h.has_advance_width_map());
for gid in 0..3 {
let d = h.advance_width_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_hvar_with_advance_map();
let h = HvarTable::parse(&raw).unwrap();
let d = h.advance_width_delta(99, &[1.0]).expect("clamped");
assert!((d - (-30.0)).abs() < 1e-5);
}
#[test]
fn entry_format_reserved_bits_rejected() {
let mut data = vec![0u8; 4 + 2];
data[0..2].copy_from_slice(&0x4013u16.to_be_bytes()); data[2..4].copy_from_slice(&1u16.to_be_bytes());
assert!(matches!(
DeltaSetIndexMap::parse(&data),
Err(Error::BadStructure(_))
));
}
#[test]
fn entry_format_widths() {
let mut data = vec![0u8; 4 + 2];
data[0..2].copy_from_slice(&0x0003u16.to_be_bytes());
data[2..4].copy_from_slice(&2u16.to_be_bytes());
data[4] = 0b0001_0010;
data[5] = 0b0011_0100;
let m = DeltaSetIndexMap::parse(&data).expect("parse");
assert_eq!(m.len(), 2);
assert_eq!(m.entries()[0], (1, 2));
assert_eq!(m.entries()[1], (3, 4));
}
#[test]
fn entry_format_three_byte_entry_decodes_correctly() {
let mut data = vec![0u8; 4 + 3];
data[0..2].copy_from_slice(&0x0025u16.to_be_bytes());
data[2..4].copy_from_slice(&1u16.to_be_bytes());
data[4] = 0x12;
data[5] = 0x34;
data[6] = 0x56;
let m = DeltaSetIndexMap::parse(&data).expect("parse");
let raw = 0x123456u32;
let inner = raw & ((1u32 << 6) - 1);
let outer = raw >> 6;
assert_eq!(m.entries()[0], (outer as u16, inner as u16));
}
#[test]
fn rejects_truncated_map() {
let mut data = vec![0u8; 4 + 2];
data[0..2].copy_from_slice(&0x0013u16.to_be_bytes());
data[2..4].copy_from_slice(&3u16.to_be_bytes());
assert!(matches!(
DeltaSetIndexMap::parse(&data),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_major_version_other_than_1() {
let mut raw = build_minimal_hvar_no_map();
raw[0..2].copy_from_slice(&2u16.to_be_bytes());
assert!(matches!(
HvarTable::parse(&raw),
Err(Error::BadStructure(_))
));
}
#[test]
fn rejects_null_ivs_offset() {
let mut raw = build_minimal_hvar_no_map();
raw[4..8].copy_from_slice(&0u32.to_be_bytes());
assert!(matches!(HvarTable::parse(&raw), Err(Error::BadOffset)));
}
#[test]
fn rejects_truncated_header() {
let raw = vec![0u8; 12];
assert!(matches!(HvarTable::parse(&raw), Err(Error::UnexpectedEof)));
}
}