use lopdf::{dictionary, Document, Object, Stream};
pub(crate) fn be16(v: u16) -> [u8; 2] {
v.to_be_bytes()
}
pub(crate) fn be32(v: u32) -> [u8; 4] {
v.to_be_bytes()
}
pub(crate) struct PageFixture {
pub content: Stream,
pub helvetica_f1: bool,
pub page_extras: Vec<(&'static str, Object)>,
pub media_box: Option<[i64; 4]>,
pub pages_media_box: Option<[i64; 4]>,
}
impl PageFixture {
pub(crate) fn for_content(content_ops: &[u8]) -> Self {
Self {
content: Stream::new(dictionary! {}, content_ops.to_vec()),
helvetica_f1: true,
page_extras: Vec::new(),
media_box: Some([0, 0, 612, 792]),
pages_media_box: None,
}
}
}
pub(crate) fn one_page_pdf(fx: PageFixture) -> Document {
let mut doc = Document::with_version("1.7");
let pages_id = doc.new_object_id();
let mut font_resources = dictionary! {};
if fx.helvetica_f1 {
let font_id = doc.add_object(dictionary! {
"Type" => "Font",
"Subtype" => "Type1",
"BaseFont" => "Helvetica",
"Encoding" => "WinAnsiEncoding",
});
font_resources = dictionary! { "F1" => font_id };
}
let resources_id = doc.add_object(dictionary! { "Font" => font_resources });
let content_id = doc.add_object(fx.content);
let mut page_dict = dictionary! {
"Type" => "Page",
"Parent" => pages_id,
"Contents" => content_id,
"Resources" => resources_id,
};
if let Some([a, b, c, d]) = fx.media_box {
page_dict.set(
"MediaBox",
Object::Array(vec![
Object::Integer(a),
Object::Integer(b),
Object::Integer(c),
Object::Integer(d),
]),
);
}
for (key, value) in fx.page_extras {
page_dict.set(key, value);
}
let page_id = doc.add_object(page_dict);
let mut pages_dict = dictionary! {
"Type" => "Pages",
"Kids" => Object::Array(vec![Object::Reference(page_id)]),
"Count" => 1,
};
if let Some([a, b, c, d]) = fx.pages_media_box {
pages_dict.set(
"MediaBox",
Object::Array(vec![
Object::Integer(a),
Object::Integer(b),
Object::Integer(c),
Object::Integer(d),
]),
);
}
doc.objects.insert(pages_id, Object::Dictionary(pages_dict));
let catalog_id = doc.add_object(dictionary! {
"Type" => "Catalog",
"Pages" => pages_id,
});
doc.trailer.set("Root", catalog_id);
doc
}
pub(crate) fn build_one_page_pdf(content_ops: &[u8]) -> Document {
one_page_pdf(PageFixture::for_content(content_ops))
}
pub(crate) fn build_one_page_pdf_bytes(content_ops: &[u8]) -> Vec<u8> {
pdf_bytes(&build_one_page_pdf(content_ops))
}
pub(crate) fn pdf_bytes(doc: &Document) -> Vec<u8> {
let mut doc = doc.clone();
let mut out = Vec::new();
doc.save_to(&mut out).expect("fixture PDF must save");
out
}
pub(crate) fn build_sfnt(tables: &[([u8; 4], Vec<u8>)]) -> Vec<u8> {
let mut tables = tables.to_vec();
tables.sort_by_key(|(tag, _)| *tag);
let tables = &tables[..];
let num_tables = tables.len() as u16;
let entry_selector = (num_tables as f64).log2().floor() as u16;
let search_range = 16u16.saturating_mul(1u16 << entry_selector.min(15));
let range_shift = (num_tables * 16).saturating_sub(search_range);
let header_len = 12 + tables.len() * 16;
let mut offset = header_len as u32;
let mut records = Vec::new();
let mut body = Vec::new();
for (tag, data) in tables {
records.extend(*tag);
records.extend(be32(0)); records.extend(be32(offset));
records.extend(be32(data.len() as u32));
body.extend_from_slice(data);
let padded_len = (data.len() + 3) & !3;
body.resize(body.len() + (padded_len - data.len()), 0);
offset += padded_len as u32;
}
let mut out = Vec::new();
out.extend(be32(0x0001_0000)); out.extend(be16(num_tables));
out.extend(be16(search_range));
out.extend(be16(entry_selector));
out.extend(be16(range_shift));
out.extend(records);
out.extend(body);
out
}
pub(crate) fn build_test_ttf(fs_type: u16, chars: &[(char, u16)], advance: u16) -> Vec<u8> {
let num_glyphs = chars.iter().map(|(_, g)| *g).max().unwrap_or(0) + 1;
let mut head = Vec::new();
head.extend(be16(1)); head.extend(be16(0)); head.extend(be32(0)); head.extend(be32(0)); head.extend(be32(0x5F0F3CF5)); head.extend(be16(0)); head.extend(be16(1000)); head.extend(be32(0)); head.extend(be32(0)); head.extend(be32(0)); head.extend(be32(0)); head.extend(be16(0)); head.extend(be16(0)); head.extend(be16(0)); head.extend(be16(0)); head.extend(be16(0)); head.extend(be16(8)); head.extend(be16(2)); head.extend(be16(0)); head.extend(be16(0)); assert_eq!(head.len(), 54);
let mut hhea = Vec::new();
hhea.extend(be16(1));
hhea.extend(be16(0));
hhea.extend(be16(800)); hhea.extend(be16((-200i16) as u16)); hhea.extend(be16(0)); hhea.extend(be16(advance)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(advance)); hhea.extend(be16(1)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(0)); hhea.extend(be16(num_glyphs)); assert_eq!(hhea.len(), 36);
let mut maxp = Vec::new();
maxp.extend(be32(0x0001_0000));
maxp.extend(be16(num_glyphs));
maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(2)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); maxp.extend(be16(0)); assert_eq!(maxp.len(), 32);
let mut hmtx = Vec::new();
for _ in 0..num_glyphs {
hmtx.extend(be16(advance));
hmtx.extend(be16(0));
}
let mut loca = Vec::new();
for _ in 0..=num_glyphs {
loca.extend(be16(0));
}
let glyf: Vec<u8> = Vec::new();
let mut mappings: Vec<(u16, u16)> = chars.iter().map(|(c, g)| (*c as u16, *g)).collect();
mappings.sort_by_key(|(code, _)| *code);
let seg_count = mappings.len() + 1; let seg_count_x2 = (seg_count * 2) as u16;
let mut end_codes = Vec::new();
let mut start_codes = Vec::new();
let mut id_deltas = Vec::new();
let mut id_range_offsets = Vec::new();
for (code, gid) in &mappings {
end_codes.push(*code);
start_codes.push(*code);
id_deltas.push(gid.wrapping_sub(*code));
id_range_offsets.push(0u16);
}
end_codes.push(0xFFFF);
start_codes.push(0xFFFF);
id_deltas.push(1);
id_range_offsets.push(0);
let entry_selector = (seg_count as f64).log2().floor() as u16;
let search_range = 2u16.saturating_mul(1u16 << entry_selector.min(15));
let range_shift = seg_count_x2.saturating_sub(search_range);
let mut subtable = Vec::new();
subtable.extend(be16(4)); subtable.extend(be16(0)); subtable.extend(be16(0)); subtable.extend(be16(seg_count_x2));
subtable.extend(be16(search_range));
subtable.extend(be16(entry_selector));
subtable.extend(be16(range_shift));
for v in &end_codes {
subtable.extend(be16(*v));
}
subtable.extend(be16(0)); for v in &start_codes {
subtable.extend(be16(*v));
}
for v in &id_deltas {
subtable.extend(be16(*v));
}
for v in &id_range_offsets {
subtable.extend(be16(*v));
}
let length = subtable.len() as u16;
let length_bytes = be16(length);
subtable[2] = length_bytes[0];
subtable[3] = length_bytes[1];
let mut cmap = Vec::new();
cmap.extend(be16(0)); cmap.extend(be16(1)); cmap.extend(be16(3)); cmap.extend(be16(1)); cmap.extend(be32(12)); cmap.extend(subtable);
let mut os2 = Vec::new();
os2.extend(be16(2)); os2.extend(be16(0)); os2.extend(be16(400)); os2.extend(be16(5)); os2.extend(be16(fs_type)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend(be16(0)); os2.extend([0u8; 10]); os2.extend(be32(0)); os2.extend(be32(0)); os2.extend(be32(0)); os2.extend(be32(0)); os2.extend(*b"TEST"); os2.extend(be16(0x0040)); os2.extend(be16(0)); os2.extend(be16(0xFFFF)); os2.extend(be16(800)); os2.extend(be16((-200i16) as u16)); os2.extend(be16(0)); os2.extend(be16(1000)); os2.extend(be16(200)); os2.extend(be32(0)); os2.extend(be32(0)); os2.extend(be16(500)); os2.extend(be16(700)); os2.extend(be16(0)); os2.extend(be16(32)); os2.extend(be16(1)); assert_eq!(os2.len(), 96);
build_sfnt(&[
(*b"head", head),
(*b"hhea", hhea),
(*b"maxp", maxp),
(*b"hmtx", hmtx),
(*b"loca", loca),
(*b"glyf", glyf),
(*b"cmap", cmap),
(*b"OS/2", os2),
])
}