use crate::parser::{read_i16, read_u16, read_u32};
use crate::Error;
#[derive(Debug, Clone)]
#[doc(hidden)]
pub struct KernTable<'a> {
pairs: Vec<KernPair>,
format2: Vec<Format2Subtable>,
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,
}
#[derive(Debug, Clone)]
struct Format2Subtable {
left: ClassTable,
right: ClassTable,
array: Vec<i16>,
row_width: usize,
}
#[derive(Debug, Clone)]
struct ClassTable {
first_glyph: u16,
values: Vec<u16>,
}
impl ClassTable {
fn value_for(&self, glyph: u16) -> u16 {
if glyph < self.first_glyph {
return 0;
}
let idx = (glyph - self.first_glyph) as usize;
self.values.get(idx).copied().unwrap_or(0)
}
}
impl Format2Subtable {
fn lookup(&self, left: u16, right: u16) -> i16 {
let lo = self.left.value_for(left) as usize;
let ro = self.right.value_for(right) as usize;
if lo == 0 || ro == 0 {
return 0;
}
let byte_off = lo + ro;
if self.row_width == 0 || byte_off % 2 != 0 {
return 0;
}
let idx = byte_off / 2;
self.array.get(idx).copied().unwrap_or(0)
}
}
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,
format2: Vec::new(),
variant,
_phantom: core::marker::PhantomData,
});
}
let mut format2 = Vec::new();
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 horizontal && is_kerning {
match format {
0 => parse_format0(bytes, off + 6, &mut pairs)?,
2 => {
if let Some(sub) = parse_format2(bytes, off, length)? {
format2.push(sub);
}
}
_ => {}
}
}
off = next_off;
}
pairs.sort_by_key(|p| p.key);
Ok(Self {
pairs,
format2,
variant,
_phantom: core::marker::PhantomData,
})
}
pub fn header_variant(&self) -> HeaderVariant {
self.variant
}
pub fn pair_count(&self) -> usize {
self.pairs.len()
}
pub fn format2_subtable_count(&self) -> usize {
self.format2.len()
}
pub fn lookup(&self, left: u16, right: u16) -> i16 {
let key = ((left as u32) << 16) | right as u32;
let mut value: i32 = match self.pairs.binary_search_by_key(&key, |p| p.key) {
Ok(i) => self.pairs[i].value as i32,
Err(_) => 0,
};
for sub in &self.format2 {
value += sub.lookup(left, right) as i32;
}
value.clamp(i16::MIN as i32, i16::MAX as i32) as i16
}
}
fn parse_format2(
bytes: &[u8],
sub_off: usize,
length: usize,
) -> Result<Option<Format2Subtable>, Error> {
let body = sub_off + 6;
if body + 8 > bytes.len() || sub_off + length > bytes.len() {
return Ok(None);
}
let row_width = read_u16(bytes, body)? as usize;
let left_off = read_u16(bytes, body + 2)? as usize;
let right_off = read_u16(bytes, body + 4)? as usize;
let array_off = read_u16(bytes, body + 6)? as usize;
let sub_end = sub_off + length;
let left = match parse_class_table(bytes, sub_off, left_off, sub_end)? {
Some(t) => t,
None => return Ok(None),
};
let right = match parse_class_table(bytes, sub_off, right_off, sub_end)? {
Some(t) => t,
None => return Ok(None),
};
let array_start = sub_off + array_off;
if array_off == 0 || array_start > sub_end {
return Ok(None);
}
let array_bytes = sub_end - array_start;
let cell_count = array_bytes / 2;
let mut array = Vec::with_capacity(cell_count);
for i in 0..cell_count {
array.push(read_i16(bytes, array_start + i * 2)?);
}
Ok(Some(Format2Subtable {
left,
right,
array,
row_width,
}))
}
fn parse_class_table(
bytes: &[u8],
sub_off: usize,
rel_off: usize,
sub_end: usize,
) -> Result<Option<ClassTable>, Error> {
if rel_off == 0 {
return Ok(None);
}
let start = sub_off + rel_off;
if start + 4 > sub_end {
return Ok(None);
}
let first_glyph = read_u16(bytes, start)?;
let n_glyphs = read_u16(bytes, start + 2)? as usize;
let arr = start + 4;
if arr + n_glyphs * 2 > sub_end {
return Ok(None);
}
let mut values = Vec::with_capacity(n_glyphs);
for i in 0..n_glyphs {
values.push(read_u16(bytes, arr + i * 2)?);
}
Ok(Some(ClassTable {
first_glyph,
values,
}))
}
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(_))
));
}
fn build_kern_format2(cell_value: 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; 38];
sub[0..2].copy_from_slice(&0u16.to_be_bytes()); sub[2..4].copy_from_slice(&38u16.to_be_bytes()); sub[4..6].copy_from_slice(&0x0201u16.to_be_bytes()); sub[6..8].copy_from_slice(&4u16.to_be_bytes()); sub[8..10].copy_from_slice(&14u16.to_be_bytes()); sub[10..12].copy_from_slice(&22u16.to_be_bytes()); sub[12..14].copy_from_slice(&30u16.to_be_bytes()); sub[14..16].copy_from_slice(&10u16.to_be_bytes()); sub[16..18].copy_from_slice(&2u16.to_be_bytes()); sub[18..20].copy_from_slice(&4u16.to_be_bytes()); sub[20..22].copy_from_slice(&0u16.to_be_bytes()); sub[22..24].copy_from_slice(&20u16.to_be_bytes()); sub[24..26].copy_from_slice(&2u16.to_be_bytes()); sub[26..28].copy_from_slice(&2u16.to_be_bytes()); sub[28..30].copy_from_slice(&0u16.to_be_bytes()); sub[36..38].copy_from_slice(&cell_value.to_be_bytes());
t.extend_from_slice(&sub);
t
}
#[test]
fn format2_class_array_lookup() {
let bytes = build_kern_format2(-50);
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.header_variant(), HeaderVariant::Microsoft);
assert_eq!(k.pair_count(), 0);
assert_eq!(k.format2_subtable_count(), 1);
assert_eq!(k.lookup(10, 20), -50);
assert_eq!(k.lookup(11, 20), 0);
assert_eq!(k.lookup(10, 21), 0);
assert_eq!(k.lookup(99, 99), 0);
}
#[test]
fn format2_minimum_subtable_skipped() {
let mut bytes = build_kern_format2(-50);
bytes[8..10].copy_from_slice(&0x0203u16.to_be_bytes());
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.format2_subtable_count(), 0);
assert_eq!(k.lookup(10, 20), 0);
}
#[test]
fn format2_and_format0_are_additive() {
let mut t = vec![0u8; 4];
t[0..2].copy_from_slice(&0u16.to_be_bytes());
t[2..4].copy_from_slice(&2u16.to_be_bytes()); let mut f0 = vec![0u8; 20];
f0[2..4].copy_from_slice(&20u16.to_be_bytes());
f0[4..6].copy_from_slice(&1u16.to_be_bytes()); f0[6..8].copy_from_slice(&1u16.to_be_bytes()); f0[14..16].copy_from_slice(&10u16.to_be_bytes());
f0[16..18].copy_from_slice(&20u16.to_be_bytes());
f0[18..20].copy_from_slice(&(-30i16).to_be_bytes());
t.extend_from_slice(&f0);
let f2 = build_kern_format2(-50);
t.extend_from_slice(&f2[4..]);
let k = KernTable::parse(&t).unwrap();
assert_eq!(k.pair_count(), 1);
assert_eq!(k.format2_subtable_count(), 1);
assert_eq!(k.lookup(10, 20), -80);
}
#[test]
fn format2_malformed_offsets_skipped() {
let mut bytes = build_kern_format2(-50);
bytes[16..18].copy_from_slice(&9999u16.to_be_bytes());
let k = KernTable::parse(&bytes).unwrap();
assert_eq!(k.format2_subtable_count(), 0);
}
}