use crate::parser::{read_i16, read_u16, read_u32};
use crate::Error;
pub const VHEA_VERSION_1_0: u32 = 0x0001_0000;
pub const VHEA_VERSION_1_1: u32 = 0x0001_1000;
#[derive(Debug, Clone, Copy)]
pub struct VheaTable {
version_raw: u32,
pub vert_typo_ascender: i16,
pub vert_typo_descender: i16,
pub vert_typo_line_gap: i16,
pub advance_height_max: i16,
pub min_top_side_bearing: i16,
pub min_bottom_side_bearing: i16,
pub y_max_extent: i16,
pub caret_slope_rise: i16,
pub caret_slope_run: i16,
pub caret_offset: i16,
pub num_long_ver_metrics: u16,
}
impl VheaTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 36 {
return Err(Error::UnexpectedEof);
}
let version_raw = read_u32(bytes, 0)?;
match version_raw {
VHEA_VERSION_1_0 | VHEA_VERSION_1_1 => {}
_ => return Err(Error::BadStructure("vhea: unrecognised version Fixed")),
}
let vert_typo_ascender = read_i16(bytes, 4)?;
let vert_typo_descender = read_i16(bytes, 6)?;
let vert_typo_line_gap = read_i16(bytes, 8)?;
let advance_height_max = read_i16(bytes, 10)?;
let min_top_side_bearing = read_i16(bytes, 12)?;
let min_bottom_side_bearing = read_i16(bytes, 14)?;
let y_max_extent = read_i16(bytes, 16)?;
let caret_slope_rise = read_i16(bytes, 18)?;
let caret_slope_run = read_i16(bytes, 20)?;
let caret_offset = read_i16(bytes, 22)?;
let num_long_ver_metrics = read_u16(bytes, 34)?;
if num_long_ver_metrics == 0 {
return Err(Error::BadStructure("vhea: numOfLongVerMetrics == 0"));
}
Ok(Self {
version_raw,
vert_typo_ascender,
vert_typo_descender,
vert_typo_line_gap,
advance_height_max,
min_top_side_bearing,
min_bottom_side_bearing,
y_max_extent,
caret_slope_rise,
caret_slope_run,
caret_offset,
num_long_ver_metrics,
})
}
pub fn version_raw(&self) -> u32 {
self.version_raw
}
pub fn is_v1_1(&self) -> bool {
self.version_raw == VHEA_VERSION_1_1
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_v10(num_metrics: u16) -> Vec<u8> {
let mut b = vec![0u8; 36];
b[0..4].copy_from_slice(&VHEA_VERSION_1_0.to_be_bytes());
b[4..6].copy_from_slice(&(1024i16).to_be_bytes()); b[6..8].copy_from_slice(&(-1024i16).to_be_bytes()); b[8..10].copy_from_slice(&(0i16).to_be_bytes()); b[10..12].copy_from_slice(&(2079i16).to_be_bytes()); b[12..14].copy_from_slice(&(-342i16).to_be_bytes()); b[14..16].copy_from_slice(&(-333i16).to_be_bytes()); b[16..18].copy_from_slice(&(2036i16).to_be_bytes()); b[18..20].copy_from_slice(&(0i16).to_be_bytes()); b[20..22].copy_from_slice(&(1i16).to_be_bytes()); b[22..24].copy_from_slice(&(0i16).to_be_bytes()); b[34..36].copy_from_slice(&num_metrics.to_be_bytes());
b
}
#[test]
fn parses_v10_example_from_spec() {
let b = make_v10(258);
let v = VheaTable::parse(&b).unwrap();
assert_eq!(v.version_raw(), VHEA_VERSION_1_0);
assert!(!v.is_v1_1());
assert_eq!(v.vert_typo_ascender, 1024);
assert_eq!(v.vert_typo_descender, -1024);
assert_eq!(v.vert_typo_line_gap, 0);
assert_eq!(v.advance_height_max, 2079);
assert_eq!(v.min_top_side_bearing, -342);
assert_eq!(v.min_bottom_side_bearing, -333);
assert_eq!(v.y_max_extent, 2036);
assert_eq!(v.caret_slope_rise, 0);
assert_eq!(v.caret_slope_run, 1);
assert_eq!(v.caret_offset, 0);
assert_eq!(v.num_long_ver_metrics, 258);
}
#[test]
fn parses_v11_with_renamed_typographic_fields() {
let mut b = make_v10(4);
b[0..4].copy_from_slice(&VHEA_VERSION_1_1.to_be_bytes());
let v = VheaTable::parse(&b).unwrap();
assert!(v.is_v1_1());
assert_eq!(v.version_raw(), VHEA_VERSION_1_1);
assert_eq!(v.vert_typo_ascender, 1024);
assert_eq!(v.num_long_ver_metrics, 4);
}
#[test]
fn rejects_unrecognised_version() {
let mut b = make_v10(1);
b[0..4].copy_from_slice(&0x0002_0000u32.to_be_bytes());
assert!(VheaTable::parse(&b).is_err());
}
#[test]
fn rejects_zero_metrics() {
let b = make_v10(0);
assert!(VheaTable::parse(&b).is_err());
}
#[test]
fn rejects_short_slice() {
let b = vec![0u8; 35];
assert!(VheaTable::parse(&b).is_err());
}
#[test]
fn tolerates_non_zero_reserved_bytes() {
let mut b = make_v10(1);
b[24] = 0xAB;
b[33] = 0xCD;
let v = VheaTable::parse(&b).unwrap();
assert_eq!(v.num_long_ver_metrics, 1);
}
}