const HEAD_TAG: &[u8; 4] = b"head";
const NAME_TAG: &[u8; 4] = b"name";
const SFNT_HEADER_SIZE: usize = 12;
const TABLE_RECORD_SIZE: usize = 16;
const HEAD_CHECKSUM_ADJUSTMENT_OFFSET: usize = 8;
const HEAD_MAGIC: u32 = 0xB1B0_AFBA;
const MAX_TABLES: usize = 4095;
pub fn splice_original_name(subsetted: &[u8], original: &[u8]) -> Result<Vec<u8>, String> {
let Some(name_bytes) = find_table(original, NAME_TAG)? else {
return Ok(subsetted.to_vec());
};
replace_table(subsetted, NAME_TAG, &name_bytes)
}
fn find_table(sfnt: &[u8], tag: &[u8; 4]) -> Result<Option<Vec<u8>>, String> {
let num_tables = read_num_tables(sfnt)?;
for i in 0..num_tables {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
if rec + TABLE_RECORD_SIZE > sfnt.len() {
return Err("table directory overflow".into());
}
if &sfnt[rec..rec + 4] == tag.as_slice() {
let off = read_u32(sfnt, rec + 8) as usize;
let len = read_u32(sfnt, rec + 12) as usize;
if off.checked_add(len).is_none_or(|end| end > sfnt.len()) {
return Err("table data overflow".into());
}
return Ok(Some(sfnt[off..off + len].to_vec()));
}
}
Ok(None)
}
fn replace_table(sfnt: &[u8], tag: &[u8; 4], new_data: &[u8]) -> Result<Vec<u8>, String> {
if sfnt.len() < SFNT_HEADER_SIZE {
return Err("sfnt too short".into());
}
let num_tables = read_num_tables(sfnt)?;
let mut tables: Vec<([u8; 4], Vec<u8>)> = Vec::with_capacity(num_tables);
let mut found = false;
for i in 0..num_tables {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
if rec + TABLE_RECORD_SIZE > sfnt.len() {
return Err("table directory overflow".into());
}
let mut t = [0u8; 4];
t.copy_from_slice(&sfnt[rec..rec + 4]);
let data = if &t == tag {
found = true;
new_data.to_vec()
} else {
let off = read_u32(sfnt, rec + 8) as usize;
let len = read_u32(sfnt, rec + 12) as usize;
if off.checked_add(len).is_none_or(|end| end > sfnt.len()) {
return Err("table data overflow".into());
}
sfnt[off..off + len].to_vec()
};
tables.push((t, data));
}
if !found {
tables.push((*tag, new_data.to_vec()));
}
rebuild_sfnt(&sfnt[0..4], tables)
}
fn rebuild_sfnt(version: &[u8], mut tables: Vec<([u8; 4], Vec<u8>)>) -> Result<Vec<u8>, String> {
if tables.is_empty() {
return Err("cannot build an SFNT with no tables".into());
}
if tables.len() > MAX_TABLES {
return Err(format!(
"{} tables exceeds the {MAX_TABLES} a table directory can describe",
tables.len()
));
}
let n = tables.len() as u16;
tables.sort_by_key(|t| t.0);
let entry_selector = 15 - n.leading_zeros() as u16;
let search_range = (1u16 << entry_selector) * 16;
let range_shift = n * 16 - search_range;
let dir_size = SFNT_HEADER_SIZE + tables.len() * TABLE_RECORD_SIZE;
let mut offsets = Vec::with_capacity(tables.len());
let mut cur = dir_size;
for (_, data) in &tables {
offsets.push(cur);
cur += data.len();
cur = (cur + 3) & !3; }
let total_size = cur;
let mut out = vec![0u8; total_size];
out[0..4].copy_from_slice(version);
out[4..6].copy_from_slice(&n.to_be_bytes());
out[6..8].copy_from_slice(&search_range.to_be_bytes());
out[8..10].copy_from_slice(&entry_selector.to_be_bytes());
out[10..12].copy_from_slice(&range_shift.to_be_bytes());
for (i, ((t, data), &off)) in tables.iter().zip(offsets.iter()).enumerate() {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
out[rec..rec + 4].copy_from_slice(t);
out[rec + 8..rec + 12].copy_from_slice(&(off as u32).to_be_bytes());
out[rec + 12..rec + 16].copy_from_slice(&(data.len() as u32).to_be_bytes());
out[off..off + data.len()].copy_from_slice(data);
}
let head_adj_pos = match tables.iter().position(|(t, _)| t == HEAD_TAG) {
Some(i) => {
if tables[i].1.len() < HEAD_CHECKSUM_ADJUSTMENT_OFFSET + 4 {
return Err("head table too short for checksumAdjustment".into());
}
let pos = offsets[i] + HEAD_CHECKSUM_ADJUSTMENT_OFFSET;
out[pos..pos + 4].copy_from_slice(&0u32.to_be_bytes());
Some(pos)
}
None => None,
};
for (i, ((_, data), &off)) in tables.iter().zip(offsets.iter()).enumerate() {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
let padded_len = (data.len() + 3) & !3;
let checksum = sfnt_checksum(&out[off..off + padded_len]);
out[rec + 4..rec + 8].copy_from_slice(&checksum.to_be_bytes());
}
if let Some(pos) = head_adj_pos {
let total = sfnt_checksum(&out);
let adjustment = HEAD_MAGIC.wrapping_sub(total);
out[pos..pos + 4].copy_from_slice(&adjustment.to_be_bytes());
}
Ok(out)
}
fn read_num_tables(sfnt: &[u8]) -> Result<usize, String> {
if sfnt.len() < SFNT_HEADER_SIZE {
return Err("sfnt too short".into());
}
Ok(u16::from_be_bytes([sfnt[4], sfnt[5]]) as usize)
}
fn read_u32(buf: &[u8], offset: usize) -> u32 {
u32::from_be_bytes([
buf[offset],
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
])
}
fn sfnt_checksum(data: &[u8]) -> u32 {
let mut sum: u32 = 0;
let chunks = data.chunks_exact(4);
let rem = chunks.remainder();
for c in chunks {
sum = sum.wrapping_add(u32::from_be_bytes([c[0], c[1], c[2], c[3]]));
}
if !rem.is_empty() {
let mut padded = [0u8; 4];
padded[..rem.len()].copy_from_slice(rem);
sum = sum.wrapping_add(u32::from_be_bytes(padded));
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
use skia_safe::{Data, FontMgr, FontStyle};
#[test]
fn splice_preserves_family_name() {
let mgr = FontMgr::new();
let Some(tf) = mgr.match_family_style("Carlito", FontStyle::normal()) else {
return;
};
let Some((bytes, _)) = tf.to_font_data() else {
return;
};
let unicodes: Vec<u32> = (0x20u32..0x7Fu32).collect();
let subsetted = match fontcull::subset_font_data_unicode(&bytes, &unicodes, &[]) {
Ok(s) => s,
Err(_) => return,
};
let pre = mgr
.new_from_data(&Data::new_copy(&subsetted), 0)
.expect("subsetted parses");
assert_eq!(
pre.family_name(),
"",
"fontcull is expected to wipe the name table — if this changes, the splice is unnecessary"
);
let spliced = splice_original_name(&subsetted, &bytes).expect("splice");
let post = mgr
.new_from_data(&Data::new_copy(&spliced), 0)
.expect("spliced parses");
assert_eq!(post.family_name(), "Carlito");
}
fn tbl(tag: &[u8; 4], len: usize) -> ([u8; 4], Vec<u8>) {
(*tag, vec![0u8; len])
}
fn many(n: usize) -> Vec<([u8; 4], Vec<u8>)> {
(0..n)
.map(|i| {
let mut t = [0u8; 4];
t.copy_from_slice(&format!("{i:04x}").as_bytes()[..4]);
(t, vec![0u8; 4])
})
.collect()
}
fn dir_fields(sfnt: &[u8]) -> (u16, u16, u16, u16) {
let g = |o: usize| u16::from_be_bytes([sfnt[o], sfnt[o + 1]]);
(g(4), g(6), g(8), g(10)) }
#[test]
fn directory_search_fields_follow_the_spec_formulas() {
for n in [1usize, 2, 3, 4, 7, 8, 15, 16, 17, 100, 4095] {
let out = rebuild_sfnt(b"\x00\x01\x00\x00", many(n)).expect("n={n}");
let (num, search_range, entry_selector, range_shift) = dir_fields(&out);
let expected_sel = (usize::BITS - 1 - n.leading_zeros()) as u16; let expected_range = (1u32 << expected_sel) * 16;
assert_eq!(num as usize, n, "numTables for n={n}");
assert_eq!(entry_selector, expected_sel, "entrySelector for n={n}");
assert_eq!(search_range as u32, expected_range, "searchRange for n={n}");
assert_eq!(
range_shift as u32,
n as u32 * 16 - expected_range,
"rangeShift for n={n}"
);
}
}
#[test]
fn the_table_count_bound_is_exact() {
assert!(
rebuild_sfnt(b"\x00\x01\x00\x00", many(MAX_TABLES)).is_ok(),
"{MAX_TABLES} tables must still build"
);
let err = rebuild_sfnt(b"\x00\x01\x00\x00", many(MAX_TABLES + 1))
.expect_err("one past the bound must be declined, not wrapped");
assert!(err.contains("exceeds"), "got: {err}");
}
#[test]
fn an_empty_table_set_is_declined_not_underflowed() {
let err = rebuild_sfnt(b"\x00\x01\x00\x00", Vec::new()).expect_err("must decline");
assert!(err.contains("no tables"), "got: {err}");
}
#[test]
fn tables_are_sorted_by_tag_and_four_byte_aligned() {
let tables = vec![tbl(b"zzzz", 5), tbl(b"aaaa", 3), tbl(b"mmmm", 4)];
let out = rebuild_sfnt(b"\x00\x01\x00\x00", tables.clone()).expect("build");
let n = u16::from_be_bytes([out[4], out[5]]) as usize;
assert_eq!(n, 3);
let mut seen = Vec::new();
for i in 0..n {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
let tag = &out[rec..rec + 4];
let off = read_u32(&out, rec + 8) as usize;
let len = read_u32(&out, rec + 12) as usize;
assert_eq!(off % 4, 0, "table {i} is not 4-byte aligned");
assert!(off + len <= out.len(), "table {i} runs past the buffer");
seen.push(tag.to_vec());
let expected = tables.iter().find(|(t, _)| t == tag).expect("known tag");
assert_eq!(len, expected.1.len(), "length for {:?}", tag);
}
let mut sorted = seen.clone();
sorted.sort();
assert_eq!(seen, sorted, "directory must be sorted by tag");
}
#[test]
fn replacing_an_absent_table_appends_it() {
let original = rebuild_sfnt(b"\x00\x01\x00\x00", vec![tbl(b"head", 54)]).expect("build");
let out = replace_table(&original, NAME_TAG, &[1, 2, 3, 4]).expect("replace");
let found = find_table(&out, NAME_TAG)
.expect("scan")
.expect("name present");
assert_eq!(found, vec![1, 2, 3, 4]);
assert!(
find_table(&out, HEAD_TAG).expect("scan").is_some(),
"the existing table must survive"
);
}
fn stored_and_actual(sfnt: &[u8], tag: &[u8; 4]) -> Option<(u32, u32)> {
let n = u16::from_be_bytes([sfnt[4], sfnt[5]]) as usize;
(0..n).find_map(|i| {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
(&sfnt[rec..rec + 4] == tag).then(|| {
let stored = read_u32(sfnt, rec + 4);
let off = read_u32(sfnt, rec + 8) as usize;
let len = read_u32(sfnt, rec + 12) as usize;
let padded = (len + 3) & !3;
(stored, sfnt_checksum(&sfnt[off..off + padded]))
})
})
}
#[test]
fn the_whole_file_sums_to_the_head_magic() {
let mut head = vec![0u8; 54];
head[8..12].copy_from_slice(&0xDEAD_BEEFu32.to_be_bytes());
let out = rebuild_sfnt(
b"\x00\x01\x00\x00",
vec![(*b"head", head), (*b"name", vec![1, 2, 3, 4, 5])],
)
.expect("build");
assert_eq!(sfnt_checksum(&out), HEAD_MAGIC);
}
#[test]
fn head_directory_checksum_is_taken_with_the_adjustment_zeroed() {
let mut head = vec![0u8; 54];
head[8..12].copy_from_slice(&0xDEAD_BEEFu32.to_be_bytes());
let out = rebuild_sfnt(
b"\x00\x01\x00\x00",
vec![(*b"head", head), (*b"name", vec![9, 9, 9, 9])],
)
.expect("build");
let (stored, _) = stored_and_actual(&out, HEAD_TAG).expect("head present");
let n = u16::from_be_bytes([out[4], out[5]]) as usize;
let (off, len) = (0..n)
.find_map(|i| {
let rec = SFNT_HEADER_SIZE + i * TABLE_RECORD_SIZE;
(&out[rec..rec + 4] == HEAD_TAG).then(|| {
(
read_u32(&out, rec + 8) as usize,
read_u32(&out, rec + 12) as usize,
)
})
})
.expect("head record");
let mut zeroed = out[off..off + ((len + 3) & !3)].to_vec();
zeroed[HEAD_CHECKSUM_ADJUSTMENT_OFFSET..HEAD_CHECKSUM_ADJUSTMENT_OFFSET + 4]
.copy_from_slice(&0u32.to_be_bytes());
assert_eq!(stored, sfnt_checksum(&zeroed), "§5.2 head checksum");
assert_ne!(
stored, 0xDEAD_BEEF,
"must not be the caller's incoming adjustment"
);
}
#[test]
fn non_head_directory_checksums_match_their_bytes() {
let out = rebuild_sfnt(
b"\x00\x01\x00\x00",
vec![
(*b"head", vec![0u8; 54]),
(*b"name", vec![7; 13]),
(*b"cmap", vec![3; 8]),
],
)
.expect("build");
for tag in [b"name", b"cmap"] {
let (stored, actual) = stored_and_actual(&out, tag).expect("table present");
assert_eq!(
stored,
actual,
"checksum for {:?}",
std::str::from_utf8(tag)
);
}
}
#[test]
fn a_font_without_head_still_builds() {
let out = rebuild_sfnt(b"\x00\x01\x00\x00", vec![(*b"name", vec![1, 2, 3, 4])])
.expect("build without head");
let (stored, actual) = stored_and_actual(&out, b"name").expect("name present");
assert_eq!(stored, actual);
}
#[test]
fn a_truncated_head_is_declined_not_written_past() {
let err = rebuild_sfnt(
b"\x00\x01\x00\x00",
vec![(*b"head", vec![0u8; 8]), (*b"name", vec![1, 2, 3, 4])],
)
.expect_err("must decline");
assert!(err.contains("head table too short"), "got: {err}");
}
}