use crate::font::FontError;
pub(super) fn read_u16(d: &[u8], off: usize) -> u16 {
u16::from_be_bytes([d[off], d[off + 1]])
}
pub(super) fn read_u32(d: &[u8], off: usize) -> u32 {
u32::from_be_bytes([d[off], d[off + 1], d[off + 2], d[off + 3]])
}
pub(super) fn read_i16(d: &[u8], off: usize) -> i16 {
i16::from_be_bytes([d[off], d[off + 1]])
}
pub(super) fn table_checksum(data: &[u8]) -> u32 {
let mut sum = 0u32;
let mut i = 0;
while i < data.len() {
let mut buf = [0u8; 4];
let n = (data.len() - i).min(4);
buf[..n].copy_from_slice(&data[i..i + n]);
sum = sum.wrapping_add(u32::from_be_bytes(buf));
i += 4;
}
sum
}
pub(super) fn sfnt_search_params(count: u16, unit_size: u16) -> (u16, u16, u16) {
let mut entry_selector = 0u16;
while (1u16 << (entry_selector + 1)) <= count {
entry_selector += 1;
}
let search_range = (1u16 << entry_selector).wrapping_mul(unit_size);
let range_shift = count.wrapping_mul(unit_size).wrapping_sub(search_range);
(search_range, entry_selector, range_shift)
}
pub(super) fn build_sfnt(tables: &[(&[u8; 4], Vec<u8>)]) -> Result<Vec<u8>, FontError> {
let num_tables = u16::try_from(tables.len()).map_err(|_| FontError::MalformedFont)?;
let (search_range, entry_selector, range_shift) = sfnt_search_params(num_tables, 16);
let mut out = Vec::new();
out.extend_from_slice(&0x0001_0000u32.to_be_bytes());
out.extend_from_slice(&num_tables.to_be_bytes());
out.extend_from_slice(&search_range.to_be_bytes());
out.extend_from_slice(&entry_selector.to_be_bytes());
out.extend_from_slice(&range_shift.to_be_bytes());
let dir_start = out.len();
out.resize(dir_start + tables.len() * 16, 0);
let mut records = Vec::with_capacity(tables.len());
for (tag, data) in tables {
let offset = u32::try_from(out.len()).map_err(|_| FontError::MalformedFont)?;
let checksum = table_checksum(data);
let len = u32::try_from(data.len()).map_err(|_| FontError::MalformedFont)?;
records.push((*tag, checksum, offset, len));
out.extend_from_slice(data);
let pad = (4 - data.len() % 4) % 4;
out.extend(std::iter::repeat_n(0u8, pad));
}
for (i, (tag, checksum, offset, len)) in records.iter().enumerate() {
let pos = dir_start + i * 16;
out[pos..pos + 4].copy_from_slice(*tag);
out[pos + 4..pos + 8].copy_from_slice(&checksum.to_be_bytes());
out[pos + 8..pos + 12].copy_from_slice(&offset.to_be_bytes());
out[pos + 12..pos + 16].copy_from_slice(&len.to_be_bytes());
}
let head_idx = tables.iter().position(|(t, _)| **t == *b"head").expect("head table always present");
let head_offset = usize::try_from(records[head_idx].2).map_err(|_| FontError::MalformedFont)?;
out[head_offset + 8..head_offset + 12].copy_from_slice(&0u32.to_be_bytes());
let file_checksum = table_checksum(&out);
let adjustment = 0xB1B0_AFBAu32.wrapping_sub(file_checksum);
out[head_offset + 8..head_offset + 12].copy_from_slice(&adjustment.to_be_bytes());
Ok(out)
}