Packages

Native PDF generation for Elixir: react-pdf-style ~PDF templates, flexbox layout, and a pure-Rust render backend. No browser, no external binaries.

Current section

Files

Jump to
mead_pdf native mead_nif src ttc.rs
Raw

native/mead_nif/src/ttc.rs

//! Face extraction from TrueType/OpenType collections (`ttcf`).
//!
//! A `FontSet` declaration can name a face inside a collection by
//! index. fontique's `register_fonts` scans a whole blob and would
//! register *every* face of a collection under the one declared
//! family/weight/style, so the declared face is sliced into a
//! standalone sfnt at `FontSet` build time instead — krilla, fontique,
//! and the SVG fontdb all then see a plain single-face font (index 0).
use read_fonts::FontRef;
/// True when the bytes are a font collection (`ttcf` header).
pub fn is_collection(data: &[u8]) -> bool {
data.get(..4) == Some(b"ttcf".as_slice())
}
/// Copies one face of a collection into a standalone font binary.
///
/// Table bytes are copied verbatim, so tables shared between faces get
/// duplicated into the extracted font — the result is the size the face
/// would be as a standalone file. The `head` checksum-adjustment is
/// left untouched (none of our consumers verify whole-file checksums;
/// per-table checksums stay valid because the bytes are unchanged).
pub fn extract_face(data: &[u8], index: u32) -> Result<Vec<u8>, String> {
let font = FontRef::from_index(data, index)
.map_err(|e| format!("cannot read face {index} of the collection: {e}"))?;
let dir = font.table_directory();
let records = dir.table_records();
let num = records.len();
let tables: Vec<([u8; 4], u32, &[u8])> = records
.iter()
.map(|rec| {
let tag = rec.tag();
let table = font
.table_data(tag)
.ok_or_else(|| format!("face {index}: table {tag} data out of bounds"))?;
Ok((tag.to_be_bytes(), rec.checksum(), table.as_bytes()))
})
.collect::<Result<_, String>>()?;
let header_len = 12 + 16 * num;
let total: usize = header_len + tables.iter().map(|(_, _, t)| pad4(t.len())).sum::<usize>();
let mut out = Vec::with_capacity(total);
// Offset-table header: sfntVersion, numTables, then the binary-search
// helpers the spec derives from numTables.
let log2 = usize::BITS - 1 - num.leading_zeros(); // floor(log2(num)); num >= 1
let search_range = (1u16 << log2) * 16;
out.extend_from_slice(&dir.sfnt_version().to_be_bytes());
out.extend_from_slice(&(num as u16).to_be_bytes());
out.extend_from_slice(&search_range.to_be_bytes());
out.extend_from_slice(&(log2 as u16).to_be_bytes());
out.extend_from_slice(&(num as u16 * 16 - search_range).to_be_bytes());
let mut offset = header_len as u32;
for (tag, checksum, table) in &tables {
out.extend_from_slice(tag);
out.extend_from_slice(&checksum.to_be_bytes());
out.extend_from_slice(&offset.to_be_bytes());
out.extend_from_slice(&(table.len() as u32).to_be_bytes());
offset += pad4(table.len()) as u32;
}
for (_, _, table) in &tables {
out.extend_from_slice(table);
out.resize(pad4(out.len()), 0);
}
debug_assert_eq!(out.len(), total);
Ok(out)
}
/// Next multiple of 4 (sfnt tables are 4-byte aligned).
fn pad4(n: usize) -> usize {
n.div_ceil(4) * 4
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_ttc() -> Vec<u8> {
std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../test/support/fixtures/weasyprint-subsets.ttc"
))
.expect("ttc fixture present")
}
// Faces come out as standalone fonts that a fresh parse accepts,
// with each face's distinct coverage intact (face 0 = ascii subset,
// face 1 = greek subset).
#[test]
fn extracts_parseable_faces_with_distinct_coverage() {
let data = fixture_ttc();
assert!(is_collection(&data));
let ascii = extract_face(&data, 0).unwrap();
let greek = extract_face(&data, 1).unwrap();
assert!(!is_collection(&ascii));
let a = FontRef::new(&ascii).expect("extracted face 0 parses");
let g = FontRef::new(&greek).expect("extracted face 1 parses");
use read_fonts::TableProvider;
let pi = 0x03C0_u32;
let a_pi = a.cmap().unwrap().map_codepoint(pi);
let g_pi = g.cmap().unwrap().map_codepoint(pi);
assert!(a_pi.is_none(), "ascii face must not cover π");
assert!(g_pi.is_some(), "greek face must cover π");
// krilla accepts the extracted bytes too (the real consumer).
assert!(krilla::text::Font::new(krilla::Data::from(ascii), 0).is_some());
assert!(krilla::text::Font::new(krilla::Data::from(greek), 0).is_some());
}
#[test]
fn out_of_range_face_index_errors() {
let data = fixture_ttc();
assert!(extract_face(&data, 7).is_err());
}
#[test]
fn plain_fonts_are_not_collections() {
let data = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../test/support/fixtures/weasyprint.otf"
))
.unwrap();
assert!(!is_collection(&data));
}
}