use rpdfium_core::{Name, PdfSource};
use rpdfium_parser::{Object, ObjectStore};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PageMode {
#[default]
Unknown,
UseNone,
UseOutlines,
UseThumbs,
FullScreen,
UseOC,
UseAttachments,
}
impl PageMode {
pub fn from_pdf_name(name: &str) -> Self {
match name {
"UseNone" => Self::UseNone,
"UseOutlines" => Self::UseOutlines,
"UseThumbs" => Self::UseThumbs,
"FullScreen" => Self::FullScreen,
"UseOC" => Self::UseOC,
"UseAttachments" => Self::UseAttachments,
_ => Self::Unknown,
}
}
}
pub fn is_tagged<S: PdfSource>(catalog: &Object, store: &ObjectStore<S>) -> bool {
let catalog_dict = match store
.deep_resolve(catalog)
.ok()
.and_then(|o| o.as_dict().cloned())
{
Some(d) => d,
None => return false,
};
let mark_info_obj = match catalog_dict.get(&Name::mark_info()) {
Some(obj) => obj,
None => return false,
};
let mark_info = match store.deep_resolve(mark_info_obj).ok() {
Some(o) => o,
None => return false,
};
let mark_info_dict = match mark_info.as_dict() {
Some(d) => d,
None => return false,
};
mark_info_dict
.get(&Name::marked())
.and_then(|obj| store.deep_resolve(obj).ok())
.and_then(|obj| obj.as_bool())
.unwrap_or(false)
}
pub fn page_mode<S: PdfSource>(catalog: &Object, store: &ObjectStore<S>) -> PageMode {
let catalog_dict = match store
.deep_resolve(catalog)
.ok()
.and_then(|o| o.as_dict().cloned())
{
Some(d) => d,
None => return PageMode::Unknown,
};
catalog_dict
.get(&Name::page_mode())
.and_then(|obj| store.deep_resolve(obj).ok())
.and_then(|obj| {
obj.as_name()
.map(|n| PageMode::from_pdf_name(n.as_str().as_ref()))
})
.unwrap_or(PageMode::Unknown)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PdfFormType {
#[default]
None,
AcroForm,
XfaFull,
XfaForeground,
}
pub fn pdf_form_type<S: PdfSource>(catalog: &Object, store: &ObjectStore<S>) -> PdfFormType {
let catalog_dict = match store
.deep_resolve(catalog)
.ok()
.and_then(|o| o.as_dict().cloned())
{
Some(d) => d,
None => return PdfFormType::None,
};
let acroform_obj = match catalog_dict.get(&Name::acro_form()) {
Some(obj) => obj,
None => return PdfFormType::None,
};
let acroform = match store.deep_resolve(acroform_obj).ok() {
Some(o) => o,
None => return PdfFormType::None,
};
let acroform_dict = match acroform.as_dict() {
Some(d) => d,
None => return PdfFormType::None,
};
if acroform_dict.get(&Name::xfa()).is_none() {
return PdfFormType::AcroForm;
}
let needs_rendering = catalog_dict
.get(&Name::needs_rendering())
.and_then(|o| store.deep_resolve(o).ok())
.and_then(|o| o.as_bool())
.unwrap_or(false);
if needs_rendering {
PdfFormType::XfaFull
} else {
PdfFormType::XfaForeground
}
}
#[inline]
pub fn get_form_type<S: PdfSource>(catalog: &Object, store: &ObjectStore<S>) -> PdfFormType {
pdf_form_type(catalog, store)
}
#[deprecated(note = "use `get_form_type()` — matches upstream `FPDF_GetFormType`")]
#[inline]
pub fn get_pdf_form_type<S: PdfSource>(catalog: &Object, store: &ObjectStore<S>) -> PdfFormType {
pdf_form_type(catalog, store)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use rpdfium_core::{Name, ParsingMode};
use rpdfium_parser::object::Object;
use rpdfium_parser::store::ObjectStore;
use super::*;
fn build_minimal_pdf() -> Vec<u8> {
let mut pdf = Vec::new();
pdf.extend_from_slice(b"%PDF-1.4\n");
let obj1_offset = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let obj2_offset = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
let xref_offset = pdf.len();
pdf.extend_from_slice(b"xref\n0 3\n");
pdf.extend_from_slice(b"0000000000 65535 f \r\n");
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj1_offset).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj2_offset).as_bytes());
pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
pdf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_offset).as_bytes());
pdf
}
fn make_store() -> ObjectStore<Vec<u8>> {
let pdf = build_minimal_pdf();
ObjectStore::open(pdf, ParsingMode::Lenient).unwrap()
}
fn make_arc_store_with_catalog(
catalog_extra: &[u8],
) -> (ObjectStore<Arc<[u8]>>, rpdfium_core::error::ObjectId) {
let mut pdf = Vec::new();
pdf.extend_from_slice(b"%PDF-1.4\n");
let obj1_offset = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R ");
pdf.extend_from_slice(catalog_extra);
pdf.extend_from_slice(b">>\nendobj\n");
let obj2_offset = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
let xref_offset = pdf.len();
pdf.extend_from_slice(b"xref\n0 3\n");
pdf.extend_from_slice(b"0000000000 65535 f \r\n");
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj1_offset).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj2_offset).as_bytes());
pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
pdf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_offset).as_bytes());
let arc: Arc<[u8]> = Arc::from(pdf);
let store = ObjectStore::open(arc, ParsingMode::Lenient).unwrap();
let cat_id = store.trailer().root;
(store, cat_id)
}
#[test]
fn test_is_tagged_no_mark_info_returns_false() {
let store = make_store();
let catalog = Object::Dictionary(HashMap::new());
assert!(!is_tagged(&catalog, &store));
}
#[test]
fn test_is_tagged_mark_info_marked_true() {
let store = make_store();
let mut mark_info: HashMap<Name, Object> = HashMap::new();
mark_info.insert(Name::marked(), Object::Boolean(true));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::mark_info(), Object::Dictionary(mark_info));
let obj = Object::Dictionary(catalog);
assert!(is_tagged(&obj, &store));
}
#[test]
fn test_is_tagged_mark_info_marked_false() {
let store = make_store();
let mut mark_info: HashMap<Name, Object> = HashMap::new();
mark_info.insert(Name::marked(), Object::Boolean(false));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::mark_info(), Object::Dictionary(mark_info));
let obj = Object::Dictionary(catalog);
assert!(!is_tagged(&obj, &store));
}
#[test]
fn test_is_tagged_mark_info_no_marked_entry() {
let store = make_store();
let mark_info: HashMap<Name, Object> = HashMap::new();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::mark_info(), Object::Dictionary(mark_info));
let obj = Object::Dictionary(catalog);
assert!(!is_tagged(&obj, &store));
}
#[test]
fn test_is_tagged_from_real_pdf_bytes() {
let (store, cat_id) = make_arc_store_with_catalog(b"/MarkInfo << /Marked true >>");
let cat = store.resolve(cat_id).unwrap();
assert!(is_tagged(cat, &store));
}
#[test]
fn test_is_tagged_untagged_pdf() {
let (store, cat_id) = make_arc_store_with_catalog(b"");
let cat = store.resolve(cat_id).unwrap();
assert!(!is_tagged(cat, &store));
}
#[test]
fn test_page_mode_no_entry_returns_unknown() {
let store = make_store();
let catalog = Object::Dictionary(HashMap::new());
assert_eq!(page_mode(&catalog, &store), PageMode::Unknown);
}
#[test]
fn test_page_mode_use_none() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::page_mode(), Object::Name(Name::from("UseNone")));
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::UseNone);
}
#[test]
fn test_page_mode_use_outlines() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::page_mode(), Object::Name(Name::from("UseOutlines")));
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::UseOutlines);
}
#[test]
fn test_page_mode_use_thumbs() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::page_mode(), Object::Name(Name::from("UseThumbs")));
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::UseThumbs);
}
#[test]
fn test_page_mode_full_screen() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::page_mode(), Object::Name(Name::from("FullScreen")));
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::FullScreen);
}
#[test]
fn test_page_mode_use_oc() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::page_mode(), Object::Name(Name::from("UseOC")));
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::UseOC);
}
#[test]
fn test_page_mode_use_attachments() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(
Name::page_mode(),
Object::Name(Name::from("UseAttachments")),
);
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::UseAttachments);
}
#[test]
fn test_page_mode_unrecognised_returns_unknown() {
let store = make_store();
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(
Name::page_mode(),
Object::Name(Name::from("SomeFutureMode")),
);
let obj = Object::Dictionary(catalog);
assert_eq!(page_mode(&obj, &store), PageMode::Unknown);
}
#[test]
fn test_page_mode_from_real_pdf_bytes() {
let (store, cat_id) = make_arc_store_with_catalog(b"/PageMode /UseOutlines");
let cat = store.resolve(cat_id).unwrap();
assert_eq!(page_mode(cat, &store), PageMode::UseOutlines);
}
#[test]
fn test_page_mode_default_is_unknown() {
assert_eq!(PageMode::default(), PageMode::Unknown);
}
#[test]
fn test_page_mode_from_pdf_name_all_variants() {
assert_eq!(PageMode::from_pdf_name("UseNone"), PageMode::UseNone);
assert_eq!(
PageMode::from_pdf_name("UseOutlines"),
PageMode::UseOutlines
);
assert_eq!(PageMode::from_pdf_name("UseThumbs"), PageMode::UseThumbs);
assert_eq!(PageMode::from_pdf_name("FullScreen"), PageMode::FullScreen);
assert_eq!(PageMode::from_pdf_name("UseOC"), PageMode::UseOC);
assert_eq!(
PageMode::from_pdf_name("UseAttachments"),
PageMode::UseAttachments
);
assert_eq!(PageMode::from_pdf_name("Unknown"), PageMode::Unknown);
}
#[test]
fn test_form_type_no_acroform_returns_none() {
let store = make_store();
let catalog = Object::Dictionary(HashMap::new());
assert_eq!(pdf_form_type(&catalog, &store), PdfFormType::None);
}
#[test]
fn test_form_type_acroform_no_xfa_returns_acroform() {
let store = make_store();
let mut acroform: HashMap<Name, Object> = HashMap::new();
acroform.insert(Name::fields(), Object::Array(Vec::new()));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::acro_form(), Object::Dictionary(acroform));
let obj = Object::Dictionary(catalog);
assert_eq!(pdf_form_type(&obj, &store), PdfFormType::AcroForm);
}
#[test]
fn test_form_type_xfa_without_needs_rendering_returns_xfa_foreground() {
let store = make_store();
let mut acroform: HashMap<Name, Object> = HashMap::new();
acroform.insert(Name::xfa(), Object::Array(Vec::new()));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::acro_form(), Object::Dictionary(acroform));
let obj = Object::Dictionary(catalog);
assert_eq!(pdf_form_type(&obj, &store), PdfFormType::XfaForeground);
}
#[test]
fn test_form_type_xfa_with_needs_rendering_true_returns_xfa_full() {
let store = make_store();
let mut acroform: HashMap<Name, Object> = HashMap::new();
acroform.insert(Name::xfa(), Object::Array(Vec::new()));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::acro_form(), Object::Dictionary(acroform));
catalog.insert(Name::needs_rendering(), Object::Boolean(true));
let obj = Object::Dictionary(catalog);
assert_eq!(pdf_form_type(&obj, &store), PdfFormType::XfaFull);
}
#[test]
fn test_form_type_xfa_with_needs_rendering_false_returns_xfa_foreground() {
let store = make_store();
let mut acroform: HashMap<Name, Object> = HashMap::new();
acroform.insert(Name::xfa(), Object::Array(Vec::new()));
let mut catalog: HashMap<Name, Object> = HashMap::new();
catalog.insert(Name::acro_form(), Object::Dictionary(acroform));
catalog.insert(Name::needs_rendering(), Object::Boolean(false));
let obj = Object::Dictionary(catalog);
assert_eq!(pdf_form_type(&obj, &store), PdfFormType::XfaForeground);
}
#[test]
fn test_form_type_default_is_none() {
assert_eq!(PdfFormType::default(), PdfFormType::None);
}
#[test]
fn test_form_type_from_real_pdf_bytes_no_acroform() {
let (store, cat_id) = make_arc_store_with_catalog(b"");
let cat = store.resolve(cat_id).unwrap();
assert_eq!(pdf_form_type(cat, &store), PdfFormType::None);
}
#[test]
fn test_form_type_from_real_pdf_bytes_with_acroform() {
let (store, cat_id) = make_arc_store_with_catalog(b"/AcroForm << /Fields [] >>");
let cat = store.resolve(cat_id).unwrap();
assert_eq!(pdf_form_type(cat, &store), PdfFormType::AcroForm);
}
}