use std::path::{Path, PathBuf};
use std::sync::Arc;
use pdfrum_object::{Name, Object, names};
use pdfrum_parser::{Document, LoadOptions, load};
fn oracle_checkout() -> PathBuf {
let checkout = std::env::var_os("PDFRUM_ORACLE_CHECKOUT").map_or_else(
|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../pdfium-c++"),
PathBuf::from,
);
if !checkout.is_dir() {
static SAID: std::sync::Once = std::sync::Once::new();
SAID.call_once(|| {
eprintln!(
"skipping the oracle-corpus cases: no checkout at {} \
(set PDFRUM_ORACLE_CHECKOUT)",
checkout.display()
);
});
}
checkout
}
fn resources() -> Option<PathBuf> {
let path = oracle_checkout().join("testing/resources");
path.is_dir().then_some(path)
}
fn open(name: &str) -> Option<Document> {
let bytes: Arc<[u8]> = Arc::from(std::fs::read(resources()?.join(name)).ok()?);
load(bytes, &LoadOptions::default()).ok()
}
fn page_dicts(doc: &Document) -> Vec<pdfrum_object::Dict> {
(0..doc.page_count())
.filter_map(|i| doc.page(i).ok().map(|p| p.dict))
.collect()
}
#[test]
fn cloning_a_resources_dictionary_with_indirect_xobject_streams() {
let Some(doc) = open("embedded_images.pdf") else {
return;
};
let pages = page_dicts(&doc);
assert!(!pages.is_empty(), "the file has a page");
let mut streams_seen = 0;
for page in &pages {
let Some(resources) = page.dict(names::RESOURCES, &doc) else {
continue;
};
let xobject_refs = resources.dict(&Name::from("XObject"), &doc).map_or(0, |x| {
x.iter().filter(|(_, v)| v.as_ref_id().is_some()).count()
});
assert!(
xobject_refs > 0,
"embedded_images.pdf's /XObject entries are indirect"
);
let flattened = Object::Dict(resources).clone_direct(&doc);
let xobject = flattened
.as_dict()
.and_then(|d| d.raw(&Name::from("XObject")))
.and_then(Object::as_dict)
.expect("the /XObject sub-dictionary survives flattening");
for (key, value) in xobject.iter() {
let stream = value.as_stream().unwrap_or_else(|| {
panic!(
"/{} flattened to {value:?}, not a stream",
key.as_str().unwrap_or("?")
)
});
assert!(!stream.data.is_empty(), "the stream kept its raw bytes");
streams_seen += 1;
}
assert_eq!(
xobject.len(),
xobject_refs,
"no /XObject entry was lost to the flattening"
);
}
assert!(streams_seen > 0, "at least one image stream was flattened");
}
#[test]
fn flattening_page_resources_never_panics() {
let Some(dir) = resources() else {
return;
};
let mut files = 0;
let mut flattened = 0;
let Ok(entries) = std::fs::read_dir(&dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().is_none_or(|e| e != "pdf") {
continue;
}
let Ok(bytes) = std::fs::read(&path) else {
continue;
};
let Ok(doc) = load(Arc::from(bytes), &LoadOptions::default()) else {
continue;
};
files += 1;
for page in page_dicts(&doc) {
let _ = Object::Dict(page).clone_direct(&doc);
flattened += 1;
}
}
assert!(files > 0, "the resource corpus was found and read");
assert!(flattened > 0, "at least one page was flattened");
}