use crate::parser::{read_i16, read_u16, read_u32};
use crate::Error;
#[derive(Debug, Clone)]
pub struct KernTable<'a> {
pairs: Vec<KernPair>,
variant: HeaderVariant,
_phantom: core::marker::PhantomData<&'a ()>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderVariant {
Microsoft,
Apple,
}
#[derive(Debug, Clone, Copy)]
struct KernPair {
key: u32,
value: i16,
}
impl<'a> KernTable<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() < 4 {
return Err(Error::UnexpectedEof);
}
let v0 = read_u16(bytes, 0)?;
let (mut off, n_subtables, variant) = match v0 {
0 => {
let n = read_u16(bytes, 2)?;
(4usize, n as u32, HeaderVariant::Microsoft)
}
1 => {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let v_lo = read_u16(bytes, 2)?;
if v_lo != 0 {
return Err(Error::BadStructure("kern: bad version"));
}
let n = read_u32(bytes, 4)?;
(8usize, n, HeaderVariant::Apple)
}
_ => return Err(Error::BadStructure("kern: bad version")),
};
let mut pairs = Vec::new();
if matches!(variant, HeaderVariant::Apple) {
let _ = n_subtables;
let _ = off;
return Ok(Self {
pairs,
variant,
_phantom: core::marker::PhantomData,
});
}
for _ in 0..n_subtables {
if off + 6 > bytes.len() {
return Err(Error::UnexpectedEof);
}
let _sub_version = read_u16(bytes, off)?;
let length = read_u16(bytes, off + 2)? as usize;
let coverage = read_u16(bytes, off + 4)?;
let format = (coverage >> 8) & 0xFF;
if length < 6 || off + length > bytes.len() {
break;
}
let next_off = off + length;
let horizontal = (coverage & 1) != 0;
let is_kerning = (coverage & 2) == 0;
if format == 0 && horizontal && is_kerning {
parse_format0(bytes, off + 6, &mut pairs)?;
}
off = next_off;
}
pairs.sort_by_key(|p| p.key);
Ok(Self {
pairs,
variant,
_phantom: core::marker::PhantomData,
})
}
pub fn header_variant(&self) -> HeaderVariant {
self.variant
}
pub fn pair_count(&self) -> usize {
self.pairs.len()
}
pub fn lookup(&self, left: u16, right: u16) -> i16 {
let key = ((left as u32) << 16) | right as u32;
match self.pairs.binary_search_by_key(&key, |p| p.key) {
Ok(i) => self.pairs[i].value,
Err(_) => 0,
}
}
}
fn parse_format0(bytes: &[u8], start: usize, out: &mut Vec<KernPair>) -> Result<(), Error> {
if start + 8 > bytes.len() {
return Err(Error::UnexpectedEof);
}
let n_pairs = read_u16(bytes, start)? as usize;
let mut p = start + 8;
for _ in 0..n_pairs {
if p + 6 > bytes.len() {
return Err(Error::UnexpectedEof);
}
let l = read_u16(bytes, p)?;
let r = read_u16(bytes, p + 2)?;
let v = read_i16(bytes, p + 4)?;
out.push(KernPair {
key: ((l as u32) << 16) | r as u32,
value: v,
});
p += 6;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn build_kern_with_one_pair(l: u16, r: u16, v: i16) -> Vec<u8> {
let mut t = vec![0u8; 4];
t[0..2].copy_from_slice(&0u16.to_be_bytes()); t[2..4].copy_from_slice(&1u16.to_be_bytes()); let mut sub = vec![0u8; 20];
sub[0..2].copy_from_slice(&0u16.to_be_bytes()); sub[2..4].copy_from_slice(&20u16.to_be_bytes()); sub[4..6].copy_from_slice(&1u16.to_be_bytes());
sub[6..8].copy_from_slice(&1u16.to_be_bytes());
sub[14..16].copy_from_slice(&l.to_be_bytes());
sub[16..18].copy_from_slice(&r.to_be_bytes());
sub[18..20].copy_from_slice(&v.to_be_bytes());
t.extend_from_slice(&sub);
t
}
#[test]
fn round_trips_one_pair() {
let bytes = build_kern_with_one_pair(38, 57, -100);
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.lookup(38, 57), -100);
assert_eq!(k.lookup(38, 58), 0);
assert_eq!(k.header_variant(), HeaderVariant::Microsoft);
assert_eq!(k.pair_count(), 1);
}
#[test]
fn apple_header_parses_as_empty_table() {
let mut bytes = vec![0u8; 8];
bytes[0..4].copy_from_slice(&0x0001_0000u32.to_be_bytes());
bytes[4..8].copy_from_slice(&0u32.to_be_bytes());
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.header_variant(), HeaderVariant::Apple);
assert_eq!(k.pair_count(), 0);
assert_eq!(k.lookup(38, 57), 0);
assert_eq!(k.lookup(0, 0), 0);
}
#[test]
fn apple_header_with_nonzero_n_tables_parses() {
let mut bytes = vec![0u8; 8];
bytes[0..4].copy_from_slice(&0x0001_0000u32.to_be_bytes());
bytes[4..8].copy_from_slice(&3u32.to_be_bytes());
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.header_variant(), HeaderVariant::Apple);
assert_eq!(k.pair_count(), 0);
}
#[test]
fn apple_header_truncated_returns_eof() {
let mut bytes = vec![0u8; 4];
bytes[0..2].copy_from_slice(&0x0001u16.to_be_bytes());
bytes[2..4].copy_from_slice(&0u16.to_be_bytes()); assert!(matches!(
KernTable::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn unknown_version_rejected() {
let mut bytes = vec![0u8; 8];
bytes[0..2].copy_from_slice(&0x1234u16.to_be_bytes());
let r = KernTable::parse(&bytes);
assert!(matches!(r, Err(Error::BadStructure(_))));
}
#[test]
fn apple_header_with_dirty_low_half_rejected() {
let mut bytes = vec![0u8; 8];
bytes[0..2].copy_from_slice(&0x0001u16.to_be_bytes());
bytes[2..4].copy_from_slice(&0xBEEFu16.to_be_bytes()); bytes[4..8].copy_from_slice(&0u32.to_be_bytes());
assert!(matches!(
KernTable::parse(&bytes),
Err(Error::BadStructure(_))
));
}
}