//! Generate document-model test fixtures. //! //! This program creates 15 PDF test fixtures for document model integration tests. //! //! FIXTURE PASSWORDS: //! - All encrypted fixtures use user password "test" (NOT secret - these are test fixtures) //! - Owner password is empty string for all encrypted fixtures // NOTE: lopdf is no longer a dependency. This fixture generator is disabled. // Use existing fixture files or regenerate with a different tool. use std::fs::File; use std::io::Write; use std::process::Command; // Stub types to allow compilation type Dictionary = (); type Object = (); type Stream = (); type Document = (); struct StringFormat; fn create_minimal_page(content: &str) -> (Dictionary, Object) { let mut page_dict = Dictionary::new(); page_dict.set(b"Type", "Page"); page_dict.set(b"MediaBox", Object::Array(vec![ Object::Real(0.0), Object::Real(0.0), Object::Real(612.0), Object::Real(792.0) ])); let mut font_dict = Dictionary::new(); font_dict.set(b"Type", "Font"); font_dict.set(b"Subtype", "Type1"); font_dict.set(b"BaseFont", "Helvetica"); let mut resources = Dictionary::new(); let mut fonts = Dictionary::new(); fonts.set(b"F1", Object::Dictionary(font_dict)); resources.set(b"Font", Object::Dictionary(fonts)); page_dict.set(b"Resources", Object::Dictionary(resources)); let content_bytes = format!("BT\n/F1 12 Tf\n100 700 Td\n({}) Tj\nET\n", content); let mut stream_dict = Dictionary::new(); stream_dict.set(b"Length", Object::Integer(content_bytes.len() as i64)); let content_stream = Stream::new(stream_dict, content_bytes.as_bytes().to_vec()); (page_dict, Object::Stream(content_stream)) } fn create_simple_base_pdf() -> Document { let mut doc = Document::with_version("1.4"); let (page1_dict, content1) = create_minimal_page("Page 1"); let (page2_dict, content2) = create_minimal_page("Page 2"); let mut pages_dict = Dictionary::new(); pages_dict.set(b"Type", "Pages"); pages_dict.set(b"Count", Object::Integer(2 as i64)); pages_dict.set(b"Kids", Object::Array(vec![ Object::Reference((1, 0).into()), Object::Reference((2, 0).into()) ])); let mut page1_dict = page1_dict; page1_dict.set(b"Parent", Object::Reference((0, 0).into())); page1_dict.set(b"Contents", Object::Reference((3, 0).into())); let mut page2_dict = page2_dict; page2_dict.set(b"Parent", Object::Reference((0, 0).into())); page2_dict.set(b"Contents", Object::Reference((4, 0).into())); let mut catalog_dict = Dictionary::new(); catalog_dict.set(b"Type", "Catalog"); catalog_dict.set(b"Pages", Object::Reference((0, 0).into())); doc.objects.insert((0, 0).into(), Object::Dictionary(pages_dict)); doc.objects.insert((1, 0).into(), Object::Dictionary(page1_dict)); doc.objects.insert((2, 0).into(), Object::Dictionary(page2_dict)); doc.objects.insert((3, 0).into(), content1); doc.objects.insert((4, 0).into(), content2); doc.objects.insert((5, 0).into(), Object::Dictionary(catalog_dict)); doc.trailer.set(b"Root", Object::Reference((5, 0))); let id = b"test-pdf-id-12345\0\0\0\0\0\0\0\0\0\0\0\0"; doc.trailer.set(b"ID", Object::Array(vec![ Object::String(id.to_vec(), StringFormat::Literal), Object::String(id.to_vec(), StringFormat::Literal), ])); doc } fn save_pdf(doc: &mut Document, filename: &str) { let mut buffer = Vec::new(); doc.save_to(&mut buffer).unwrap(); let mut file = File::create(filename).unwrap(); file.write_all(&buffer).unwrap(); } fn encrypt_pdf(input: &str, output: &str, r_value: &str) { // Use qpdf to encrypt the PDF // R=2: RC4-40, R=3: RC4-128, R=4: AES-128, R=6: AES-256 let result = Command::new("qpdf") .args(["--encrypt", "test", "", r_value, "--", input, output]) .output(); match result { Ok(result) => { if result.status.success() { println!("Created {} (encrypted with R={}, password: 'test')", output, r_value); } else { eprintln!("qpdf failed: {}", String::from_utf8_lossy(&result.stderr)); eprintln!("Copy {} manually and encrypt with qpdf", input); } } Err(e) => { eprintln!("qpdf not found: {}. Copy {} manually and encrypt", e, input); // Copy the unencrypted version as fallback let _ = std::fs::copy(input, output); } } } fn create_encrypted_rc4_pdf() { let mut doc = create_simple_base_pdf(); save_pdf(&mut doc, "tests/document_model/fixtures/_temp_rc4.pdf"); encrypt_pdf("tests/document_model/fixtures/_temp_rc4.pdf", "tests/document_model/fixtures/encrypted_rc4_test.pdf", "2"); let _ = std::fs::remove_file("tests/document_model/fixtures/_temp_rc4.pdf"); } fn create_encrypted_aes128_pdf() { let mut doc = create_simple_base_pdf(); save_pdf(&mut doc, "tests/document_model/fixtures/_temp_aes128.pdf"); encrypt_pdf("tests/document_model/fixtures/_temp_aes128.pdf", "tests/document_model/fixtures/encrypted_aes128_test.pdf", "4"); let _ = std::fs::remove_file("tests/document_model/fixtures/_temp_aes128.pdf"); } fn create_encrypted_aes256_pdf() { let mut doc = Document::with_version("2.0"); let (page1_dict, content1) = create_minimal_page("Page 1"); let (page2_dict, content2) = create_minimal_page("Page 2"); let mut pages_dict = Dictionary::new(); pages_dict.set(b"Type", "Pages"); pages_dict.set(b"Count", Object::Integer(2 as i64)); pages_dict.set(b"Kids", Object::Array(vec![ Object::Reference((1, 0).into()), Object::Reference((2, 0).into()) ])); let mut page1_dict = page1_dict; page1_dict.set(b"Parent", Object::Reference((0, 0).into())); page1_dict.set(b"Contents", Object::Reference((3, 0).into())); let mut page2_dict = page2_dict; page2_dict.set(b"Parent", Object::Reference((0, 0).into())); page2_dict.set(b"Contents", Object::Reference((4, 0).into())); let mut catalog_dict = Dictionary::new(); catalog_dict.set(b"Type", "Catalog"); catalog_dict.set(b"Pages", Object::Reference((0, 0).into())); doc.objects.insert((0, 0).into(), Object::Dictionary(pages_dict)); doc.objects.insert((1, 0).into(), Object::Dictionary(page1_dict)); doc.objects.insert((2, 0).into(), Object::Dictionary(page2_dict)); doc.objects.insert((3, 0).into(), content1); doc.objects.insert((4, 0).into(), content2); doc.objects.insert((5, 0).into(), Object::Dictionary(catalog_dict)); doc.trailer.set(b"Root", Object::Reference((5, 0))); let id = b"test-pdf-id-12345\0\0\0\0\0\0\0\0\0\0\0\0"; doc.trailer.set(b"ID", Object::Array(vec![ Object::String(id.to_vec(), StringFormat::Literal), Object::String(id.to_vec(), StringFormat::Literal), ])); save_pdf(&mut doc, "tests/document_model/fixtures/_temp_aes256.pdf"); encrypt_pdf("tests/document_model/fixtures/_temp_aes256.pdf", "tests/document_model/fixtures/encrypted_aes256_test.pdf", "6"); let _ = std::fs::remove_file("tests/document_model/fixtures/_temp_aes256.pdf"); } fn create_encrypted_empty_password_pdf() { let mut doc = create_simple_base_pdf(); save_pdf(&mut doc, "tests/document_model/fixtures/_temp_empty.pdf"); // Empty password uses same command - qpdf treats empty owner password as "" encrypt_pdf("tests/document_model/fixtures/_temp_empty.pdf", "tests/document_model/fixtures/encrypted_empty_password.pdf", "2"); let _ = std::fs::remove_file("tests/document_model/fixtures/_temp_empty.pdf"); } fn create_encrypted_unknown_handler_pdf() { // For unsupported handler, create a simple PDF with a fake /Encrypt dict let mut doc = create_simple_base_pdf(); // Get the PDF data let mut buffer = Vec::new(); doc.save_to(&mut buffer).unwrap(); let pdf_str = String::from_utf8_lossy(&buffer); // Insert a custom encryption dict before the xref table let encrypt_dict = "1 0 obj\n<>\nendobj\n"; // Find the trailer let trailer_pos = pdf_str.find("trailer").unwrap_or(pdf_str.len()); let mut result = pdf_str.to_string(); result.insert_str(trailer_pos, encrypt_dict); result = result.replace("1 0 obj", "2 0 obj"); // Shift object numbers // Add Encrypt reference to trailer result = result.replace("trailer\n<<", "trailer\n< 1 B "#; let mut metadata_dict = Dictionary::new(); metadata_dict.set(b"Type", "Metadata"); metadata_dict.set(b"Subtype", "XML"); let metadata_stream = Stream::new(metadata_dict, xmp_metadata.as_bytes().to_vec()); let mut catalog_dict = Dictionary::new(); catalog_dict.set(b"Type", "Catalog"); catalog_dict.set(b"Pages", Object::Reference((0, 0).into())); catalog_dict.set(b"Metadata", Object::Reference((6, 0).into())); doc.objects.insert((6, 0).into(), Object::Stream(metadata_stream)); doc.objects.insert((7, 0).into(), Object::Dictionary(catalog_dict)); doc.trailer.set(b"Root", Object::Reference((7, 0))); save_pdf(&mut doc, "tests/document_model/fixtures/pdfa_1b_conformance.pdf"); println!("Created pdfa_1b_conformance.pdf (XMP PDF/A-1B metadata)"); } fn create_page_labels_roman_arabic_pdf() { let mut doc = create_simple_base_pdf(); // Add page 3 and 4 let (page3_dict, content3) = create_minimal_page("Page 3"); let (page4_dict, content4) = create_minimal_page("Page 4"); let mut page3_dict = page3_dict; page3_dict.set(b"Parent", Object::Reference((0, 0).into())); page3_dict.set(b"Contents", Object::Reference((8, 0).into())); let mut page4_dict = page4_dict; page4_dict.set(b"Parent", Object::Reference((0, 0).into())); page4_dict.set(b"Contents", Object::Reference((9, 0).into())); // Add /PageLabels number tree // Pages 0-3: roman numerals (i, ii, iii, iv) // Pages 4+: arabic (1, 2, 3, ...) let mut page_labels = Dictionary::new(); page_labels.set(b"Nums", Object::Array(vec![ Object::Integer(0 as i64), Object::Dictionary({ let mut d = Dictionary::new(); d.set(b"S", "r"); d.set(b"St", Object::Integer(1 as i64)); d }), Object::Integer(4 as i64), Object::Dictionary({ let mut d = Dictionary::new(); d.set(b"S", "D"); d.set(b"St", Object::Integer(1 as i64)); d }) ])); let mut catalog_dict = Dictionary::new(); catalog_dict.set(b"Type", "Catalog"); catalog_dict.set(b"Pages", Object::Reference((0, 0).into())); catalog_dict.set(b"PageLabels", Object::Reference((10, 0).into())); // Update pages count to 4 let mut pages_dict = Dictionary::new(); pages_dict.set(b"Type", "Pages"); pages_dict.set(b"Count", Object::Integer(4 as i64)); pages_dict.set(b"Kids", Object::Array(vec![ Object::Reference((1, 0).into()), Object::Reference((2, 0).into()), Object::Reference((3, 0).into()), Object::Reference((4, 0).into()) ])); doc.objects.insert((0, 0).into(), Object::Dictionary(pages_dict)); doc.objects.insert((3, 0).into(), Object::Dictionary(page3_dict)); doc.objects.insert((4, 0).into(), Object::Dictionary(page4_dict)); doc.objects.insert((8, 0).into(), content3); doc.objects.insert((9, 0).into(), content4); doc.objects.insert((10, 0).into(), Object::Dictionary(page_labels)); doc.objects.insert((11, 0).into(), Object::Dictionary(catalog_dict)); doc.trailer.set(b"Root", Object::Reference((11, 0))); save_pdf(&mut doc, "tests/document_model/fixtures/page_labels_roman_arabic.pdf"); println!("Created page_labels_roman_arabic.pdf (roman 0-3, arabic 4+)"); } fn main() { println!("Generating document-model test fixtures..."); create_encrypted_rc4_pdf(); create_encrypted_aes128_pdf(); create_encrypted_aes256_pdf(); create_encrypted_empty_password_pdf(); create_encrypted_unknown_handler_pdf(); create_tagged_3_level_outline_pdf(); create_ocg_default_off_pdf(); create_multi_revision_3_pdf(); create_inheritance_grandparent_mediabox_pdf(); create_missing_mediabox_pdf(); create_partial_resource_override_pdf(); create_js_in_openaction_pdf(); create_xfa_form_pdf(); create_pdfa_1b_conformance_pdf(); create_page_labels_roman_arabic_pdf(); println!("\nAll 15 document-model fixtures generated successfully!"); println!("\nNote: Encrypted fixtures require qpdf to be installed."); println!("If qpdf is not available, encrypted fixtures will be unencrypted placeholders."); }