use crate::parser::{read_i16, read_u16};
use crate::Error;
#[derive(Debug, Clone, Copy)]
pub struct HheaTable {
pub ascent: i16,
pub descent: i16,
pub line_gap: i16,
pub advance_width_max: u16,
pub min_left_side_bearing: i16,
pub min_right_side_bearing: i16,
pub x_max_extent: i16,
pub caret_slope_rise: i16,
pub caret_slope_run: i16,
pub caret_offset: i16,
pub metric_data_format: i16,
pub num_long_hor_metrics: u16,
}
impl HheaTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 36 {
return Err(Error::UnexpectedEof);
}
let num_long_hor_metrics = read_u16(bytes, 34)?;
if num_long_hor_metrics == 0 {
return Err(Error::BadStructure("hhea.numberOfHMetrics == 0"));
}
Ok(Self {
ascent: read_i16(bytes, 4)?,
descent: read_i16(bytes, 6)?,
line_gap: read_i16(bytes, 8)?,
advance_width_max: read_u16(bytes, 10)?,
min_left_side_bearing: read_i16(bytes, 12)?,
min_right_side_bearing: read_i16(bytes, 14)?,
x_max_extent: read_i16(bytes, 16)?,
caret_slope_rise: read_i16(bytes, 18)?,
caret_slope_run: read_i16(bytes, 20)?,
caret_offset: read_i16(bytes, 22)?,
metric_data_format: read_i16(bytes, 32)?,
num_long_hor_metrics,
})
}
pub fn caret_is_vertical(&self) -> bool {
self.caret_slope_run == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_minimal() {
let mut b = vec![0u8; 36];
b[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
b[4..6].copy_from_slice(&(1900i16).to_be_bytes());
b[6..8].copy_from_slice(&(-500i16).to_be_bytes());
b[8..10].copy_from_slice(&(0i16).to_be_bytes());
b[10..12].copy_from_slice(&(2048u16).to_be_bytes());
b[34..36].copy_from_slice(&(1u16).to_be_bytes());
let h = HheaTable::parse(&b).unwrap();
assert_eq!(h.ascent, 1900);
assert_eq!(h.descent, -500);
assert_eq!(h.advance_width_max, 2048);
assert_eq!(h.num_long_hor_metrics, 1);
}
#[test]
fn parses_full_fields_and_caret_slope() {
let mut b = vec![0u8; 36];
b[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
b[4..6].copy_from_slice(&(1900i16).to_be_bytes());
b[6..8].copy_from_slice(&(-500i16).to_be_bytes());
b[10..12].copy_from_slice(&(2048u16).to_be_bytes());
b[12..14].copy_from_slice(&(-64i16).to_be_bytes()); b[14..16].copy_from_slice(&(-32i16).to_be_bytes()); b[16..18].copy_from_slice(&(2100i16).to_be_bytes()); b[18..20].copy_from_slice(&(1i16).to_be_bytes()); b[20..22].copy_from_slice(&(0i16).to_be_bytes()); b[22..24].copy_from_slice(&(0i16).to_be_bytes()); b[32..34].copy_from_slice(&(0i16).to_be_bytes()); b[34..36].copy_from_slice(&(3u16).to_be_bytes());
let h = HheaTable::parse(&b).unwrap();
assert_eq!(h.min_left_side_bearing, -64);
assert_eq!(h.min_right_side_bearing, -32);
assert_eq!(h.x_max_extent, 2100);
assert_eq!(h.caret_slope_rise, 1);
assert_eq!(h.caret_slope_run, 0);
assert!(h.caret_is_vertical());
assert_eq!(h.metric_data_format, 0);
assert_eq!(h.num_long_hor_metrics, 3);
}
#[test]
fn italic_caret_slope_is_not_vertical() {
let mut b = vec![0u8; 36];
b[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
b[18..20].copy_from_slice(&(20i16).to_be_bytes()); b[20..22].copy_from_slice(&(7i16).to_be_bytes()); b[34..36].copy_from_slice(&(1u16).to_be_bytes());
let h = HheaTable::parse(&b).unwrap();
assert!(!h.caret_is_vertical());
}
#[test]
fn rejects_zero_metrics() {
let mut b = vec![0u8; 36];
b[0..4].copy_from_slice(&0x00010000u32.to_be_bytes());
assert!(HheaTable::parse(&b).is_err());
}
}