use super::super::*;
use super::pdf_fixtures::*;
#[test]
fn test_page_inherits_mediabox() {
let mut pdf = b"%PDF-1.4\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 400 600] >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(b"3 0 obj\n<< /Type /Page /Parent 2 0 R >>\nendobj\n");
let xref_off = pdf.len();
pdf.extend_from_slice(b"xref\n0 4\n");
pdf.extend_from_slice(b"0000000000 65535 f \n");
pdf.extend_from_slice(format!("{:010} 00000 n \n", off1).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off2).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off3).as_bytes());
pdf.extend_from_slice(format!("trailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", xref_off).as_bytes());
let doc = PdfDocument::from_bytes(pdf).unwrap();
assert_eq!(doc.page_count().unwrap(), 1);
let page = doc.get_page(0).unwrap();
let page_dict = page.as_dict().unwrap();
assert!(page_dict.contains_key("MediaBox"));
}
#[test]
fn test_page_count_rescued_when_count_is_zero_but_pages_exist() {
let mut pdf = b"%PDF-1.4\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 0 >>\nendobj\n");
let mut offs = vec![off1, off2];
for n in 3..=5u32 {
offs.push(pdf.len());
pdf.extend_from_slice(
format!(
"{} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n",
n
)
.as_bytes(),
);
}
let xref_off = pdf.len();
pdf.extend_from_slice(b"xref\n0 6\n");
pdf.extend_from_slice(b"0000000000 65535 f \n");
for off in &offs {
pdf.extend_from_slice(format!("{:010} 00000 n \n", off).as_bytes());
}
pdf.extend_from_slice(format!("trailer\n<< /Size 6 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", xref_off).as_bytes());
let doc = PdfDocument::from_bytes(pdf).unwrap();
assert_eq!(
doc.get_page_count_standard().unwrap(),
0,
"fixture must drive the standard reader to 0"
);
assert_eq!(doc.page_count().unwrap(), 3, "rescue must enumerate the real pages");
}
#[test]
fn test_deeply_nested_page_tree() {
let mut pdf = b"%PDF-1.4\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(
b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 595 842] /Resources << >> >>\nendobj\n",
);
let off3 = pdf.len();
pdf.extend_from_slice(b"3 0 obj\n<< /Type /Pages /Kids [4 0 R] /Count 1 /Parent 2 0 R >>\nendobj\n");
let off4 = pdf.len();
pdf.extend_from_slice(b"4 0 obj\n<< /Type /Page /Parent 3 0 R >>\nendobj\n");
let xref_off = pdf.len();
pdf.extend_from_slice(b"xref\n0 5\n");
pdf.extend_from_slice(b"0000000000 65535 f \n");
pdf.extend_from_slice(format!("{:010} 00000 n \n", off1).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off2).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off3).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \n", off4).as_bytes());
pdf.extend_from_slice(format!("trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n", xref_off).as_bytes());
let doc = PdfDocument::from_bytes(pdf).unwrap();
assert_eq!(doc.page_count().unwrap(), 1);
let page = doc.get_page(0).unwrap();
assert!(page.as_dict().unwrap().contains_key("MediaBox"));
}
#[test]
fn test_page_text_serializable() {
let page_text = crate::layout::PageText {
spans: Vec::new(),
chars: Vec::new(),
page_width: 612.0,
page_height: 792.0,
};
let json = serde_json::to_string(&page_text).unwrap();
assert!(json.contains("page_width"));
assert!(json.contains("page_height"));
}
#[test]
fn test_is_cm_or_symbol_font() {
assert!(PdfDocument::is_cm_or_symbol_font("ABCDEF+CMSY10"));
assert!(PdfDocument::is_cm_or_symbol_font("CMR12"));
assert!(PdfDocument::is_cm_or_symbol_font("Symbol"));
assert!(!PdfDocument::is_cm_or_symbol_font("ABCDEF+Helvetica"));
assert!(!PdfDocument::is_cm_or_symbol_font("TimesNewRoman"));
}
#[test]
fn test_get_page_rotation_status_135_is_malformed_not_folded_to_valid_zero() {
let doc = PdfDocument::from_bytes(build_pdf_with_rotate_token(Some("135"), false)).unwrap();
assert_eq!(doc.get_page_rotation_status(0).unwrap(), PageRotation::Malformed);
}
#[test]
fn should_return_rather_than_abort_when_page_tree_chain_exceeds_the_depth_cap() {
let levels = MAX_PAGE_TREE_DEPTH as usize + 4;
let doc = PdfDocument::from_bytes(build_page_tree_chain_pdf(levels)).unwrap();
assert_eq!(doc.page_count().unwrap(), 1, "page_count must degrade, not abort");
let page = doc.get_page(0).expect("get_page must degrade to the scanning fallback");
assert!(
!page.as_dict().unwrap().contains_key("MediaBox"),
"the page was recovered by scanning, so the root's inheritable /MediaBox is NOT merged \
- this is what distinguishes the capped walk from the control below"
);
let page_ref_error = doc.get_page_ref(0).expect_err("get_page_ref has no scanning fallback");
assert!(
matches!(page_ref_error, Error::InvalidPdf(_)),
"expected the tree walk to run out of kids, got {:?}",
page_ref_error
);
assert_eq!(
doc.all_page_refs().unwrap(),
Vec::new(),
"all_page_refs must degrade like the other page-tree walkers, not abort"
);
}
#[test]
fn should_resolve_the_page_normally_when_the_chain_stays_under_the_depth_cap() {
let levels = MAX_PAGE_TREE_DEPTH as usize - 1;
let doc = PdfDocument::from_bytes(build_page_tree_chain_pdf(levels)).unwrap();
assert_eq!(doc.page_count().unwrap(), 1);
let page = doc.get_page(0).unwrap();
let page_dict = page.as_dict().unwrap();
assert!(
page_dict.contains_key("MediaBox"),
"the tree walk reached the leaf, so the root's /MediaBox must be inherited"
);
assert!(page_dict.contains_key("Resources"), "/Resources is inheritable too");
assert_eq!(doc.get_page_ref(0).unwrap().id, (2 + levels) as u32);
assert_eq!(doc.all_page_refs().unwrap().len(), 1);
}
fn build_page_inheritance_pdf(intermediate_attrs: &str) -> Vec<u8> {
let mut kids = String::new();
let mut objects = Vec::new();
objects.push((3, b"<< /Type /Pages /Parent 2 0 R /Count 65 /Kids [".to_vec()));
for id in 4..=68 {
kids.push_str(&format!("{id} 0 R "));
let attrs = match id {
4 => "/Resources 93 0 R /MediaBox 94 0 R /CropBox 95 0 R /Rotate 96 0 R",
5 => "/Resources 80 0 R /MediaBox 81 0 R /CropBox 82 0 R /Rotate 83 0 R",
_ => "",
};
objects.push((id, format!("<< /Type /Page /Parent 3 0 R {attrs} >>").into_bytes()));
}
objects[0]
.1
.extend_from_slice(format!("{kids}] {intermediate_attrs} >>").as_bytes());
objects.push((69, b"<< /Type /Page /Parent 2 0 R >>".to_vec()));
objects.extend([
(80, b"<< /Marker 8 >>".to_vec()),
(81, b"[0 0 300 300]".to_vec()),
(82, b"[0 0 280 280]".to_vec()),
(83, b"270".to_vec()),
]);
let refs: Vec<_> = objects.iter().map(|(id, body)| (*id, body.as_slice())).collect();
build_catalog_test_pdf(
b"<< /Type /Catalog /Pages 2 0 R >>",
b"<< /Type /Pages /Kids [3 0 R 69 0 R] /Count 66 /Resources << /Marker 1 >> /MediaBox [0 0 100 100] /CropBox [0 0 90 90] /Rotate 90 >>",
&refs,
)
}
fn assert_inherited_values(doc: &PdfDocument, index: usize, media: f32, crop: i64, rotation: i32, marker: i64) {
let page = doc.get_page(index).unwrap();
let dict = page.as_dict().unwrap();
assert_eq!(
doc.get_page_media_box(index)
.unwrap_or_else(|error| panic!("page {index}: {error:?}; dictionary: {dict:?}")),
(0.0, 0.0, media, media)
);
let crop_object = doc.resolve_obj_ref(dict.get("CropBox").unwrap());
let crop_array = crop_object.as_array().unwrap();
assert_eq!(crop_array[2].as_integer(), Some(crop));
assert_eq!(doc.get_page_rotation(index).unwrap(), rotation);
let resources = doc.resolve_obj_ref(dict.get("Resources").unwrap());
assert_eq!(
resources.as_dict().unwrap().get("Marker").unwrap().as_integer(),
Some(marker)
);
}
#[test]
fn should_fall_back_from_dangling_inheritable_references_in_lazy_and_bulk_page_walks() {
let pdf = build_page_inheritance_pdf("/Resources 97 0 R /MediaBox [0 0 200 200] /CropBox 98 0 R /Rotate 99 0 R");
let lazy = PdfDocument::from_bytes(pdf.clone()).unwrap();
assert!(
lazy.get_page_ref(0).is_ok(),
"tree must be traversable: {:?}",
lazy.get_page_ref(0)
);
assert_eq!(
lazy.load_object(ObjectRef { id: 94, generation: 0 }).unwrap(),
Object::Null
);
assert_inherited_values(&lazy, 0, 200.0, 90, 90, 1);
assert_inherited_values(&lazy, 1, 300.0, 280, 270, 8);
assert_inherited_values(&lazy, 65, 100.0, 90, 90, 1);
let bulk = PdfDocument::from_bytes(pdf).unwrap();
for index in 0..=64 {
bulk.get_page(index).unwrap();
}
assert_eq!(
bulk.page_cache.lock_or_recover().len(),
66,
"bulk walk must cache every page"
);
assert_inherited_values(&bulk, 0, 200.0, 90, 90, 1);
assert_inherited_values(&bulk, 1, 300.0, 280, 270, 8);
assert_inherited_values(&bulk, 65, 100.0, 90, 90, 1);
}
#[test]
fn should_prefer_nearest_valid_ancestor_and_preserve_valid_indirect_leaf_values() {
let pdf = build_page_inheritance_pdf(
"/Resources << /Marker 2 >> /MediaBox [0 0 200 200] /CropBox [0 0 180 180] /Rotate 180",
);
for bulk_walk in [false, true] {
let doc = PdfDocument::from_bytes(pdf.clone()).unwrap();
if bulk_walk {
for index in 0..=64 {
doc.get_page(index).unwrap();
}
assert_eq!(
doc.page_cache.lock_or_recover().len(),
66,
"bulk walk must cache every page"
);
}
assert_inherited_values(&doc, 0, 200.0, 180, 180, 2);
assert_inherited_values(&doc, 1, 300.0, 280, 270, 8);
assert_inherited_values(&doc, 65, 100.0, 90, 90, 1);
let leaf = doc.get_page(1).unwrap();
let leaf = leaf.as_dict().unwrap();
for (attribute, object_id) in [("Resources", 80), ("MediaBox", 81), ("CropBox", 82), ("Rotate", 83)] {
assert_eq!(leaf.get(attribute).unwrap().as_reference().unwrap().id, object_id);
}
}
}