use super::super::{create_basic_test_pdf, iso_test};
use crate::verification::{parser::parse_pdf, VerificationLevel};
use crate::{Document, Font, Page, Result as PdfResult};
iso_test!(
test_indirect_objects_level_3,
"7.315",
VerificationLevel::ContentVerified,
"Indirect objects must follow 'obj_num gen_num obj' format with content verification",
{
let pdf_bytes = create_basic_test_pdf(
"Indirect Objects Level 3 Test",
"Testing indirect object format compliance with content verification",
)?;
let parsed = parse_pdf(&pdf_bytes)?;
let has_sufficient_objects = parsed.object_count >= 5;
let has_catalog = parsed.catalog.is_some();
let has_page_tree = parsed.page_tree.is_some();
let has_sufficient_content = pdf_bytes.len() > 1000;
let has_pdf_header = pdf_bytes.starts_with(b"%PDF-");
let has_eof_marker = pdf_bytes.windows(5).any(|w| w == b"%%EOF");
let has_xref = pdf_bytes.windows(4).any(|w| w == b"xref");
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_obj_start = pdf_string.contains(" obj");
let has_obj_end = pdf_string.contains("endobj");
let has_proper_object_format = pdf_string.lines().any(|line| {
let parts: Vec<&str> = line.trim().split_whitespace().collect();
parts.len() >= 3
&& parts[0].parse::<u32>().is_ok()
&& parts[1].parse::<u32>().is_ok()
&& parts[2] == "obj"
});
let all_checks_passed = has_sufficient_objects
&& has_catalog
&& has_page_tree
&& has_sufficient_content
&& has_pdf_header
&& has_eof_marker
&& has_xref
&& has_obj_start
&& has_obj_end
&& has_proper_object_format;
let passed = all_checks_passed;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Indirect objects fully compliant: {} objects, catalog: {}, page_tree: {}, content: {} bytes, structure: valid",
parsed.object_count, has_catalog, has_page_tree, pdf_bytes.len())
} else {
format!(
"Level 3 verification failed - objects: {}, catalog: {}, content: {} bytes",
parsed.object_count,
has_catalog,
pdf_bytes.len()
)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_object_references_level_3,
"7.3.2",
VerificationLevel::ContentVerified,
"Verify object references use 'obj_num gen_num R' format",
{
let pdf_bytes =
create_basic_test_pdf("Object References Test", "Testing object reference format")?;
let parsed = parse_pdf(&pdf_bytes)?;
let references_valid =
parsed.object_count > 0 && parsed.catalog.is_some() && parsed.page_tree.is_some();
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_references = pdf_string.contains(" R");
let passed = references_valid && has_references;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!(
"Object references valid - {} objects with proper referencing",
parsed.object_count
)
} else {
"Test failed - implementation error".to_string()
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_generation_numbers_level_3,
"7.332",
VerificationLevel::ContentVerified,
"Objects must have generation numbers (typically 0 for new objects) with content verification",
{
let pdf_bytes = create_basic_test_pdf(
"Generation Numbers Level 3 Test",
"Testing object generation number format with content verification",
)?;
let parsed = parse_pdf(&pdf_bytes)?;
let has_sufficient_objects = parsed.object_count >= 5;
let has_catalog = parsed.catalog.is_some();
let has_page_tree = parsed.page_tree.is_some();
let has_sufficient_content = pdf_bytes.len() > 1000;
let has_pdf_header = pdf_bytes.starts_with(b"%PDF-");
let has_eof_marker = pdf_bytes.windows(5).any(|w| w == b"%%EOF");
let has_xref = pdf_bytes.windows(4).any(|w| w == b"xref");
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_gen_numbers = pdf_string.lines().any(|line| {
let parts: Vec<&str> = line.trim().split_whitespace().collect();
parts.len() >= 3
&& parts[0].parse::<u32>().is_ok()
&& parts[1].parse::<u32>().is_ok()
&& parts[2] == "obj"
});
let has_gen_zero = pdf_string.contains(" 0 obj");
let all_checks_passed = has_sufficient_objects
&& has_catalog
&& has_page_tree
&& has_sufficient_content
&& has_pdf_header
&& has_eof_marker
&& has_xref
&& has_gen_numbers
&& has_gen_zero;
let passed = all_checks_passed;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Generation numbers fully compliant: {} objects, catalog: {}, page_tree: {}, content: {} bytes, structure: valid",
parsed.object_count, has_catalog, has_page_tree, pdf_bytes.len())
} else {
format!(
"Level 3 verification failed - objects: {}, catalog: {}, content: {} bytes",
parsed.object_count,
has_catalog,
pdf_bytes.len()
)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_dictionary_objects_level_3,
"7.3.4",
VerificationLevel::ContentVerified,
"Dictionary objects must use << >> delimiters and /Key Value pairs",
{
let pdf_bytes = create_basic_test_pdf(
"Dictionary Objects Test",
"Testing PDF dictionary object format",
)?;
let parsed = parse_pdf(&pdf_bytes)?;
let dict_parsing_works = parsed.catalog.is_some();
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_dict_delimiters = pdf_string.contains("<<") && pdf_string.contains(">>");
let has_key_value_pairs = pdf_string.contains("/Type") || pdf_string.contains("/Pages");
let passed = dict_parsing_works && has_dict_delimiters && has_key_value_pairs;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
"Dictionary objects properly formatted and parseable".to_string()
} else {
"Test failed - implementation error".to_string()
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_array_objects_level_3,
"7.355",
VerificationLevel::ContentVerified,
"Array objects Level 3 content verification with parsing validation",
{
let mut doc = Document::new();
doc.set_title("Array Objects Level 3 Test");
for i in 1..=3 {
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 12.0)
.at(50.0, 750.0)
.write(&format!("Array Objects Verification - Page {}", i))?;
page.text()
.set_font(Font::TimesRoman, 10.0)
.at(50.0, 720.0)
.write("Testing PDF array object structure and compliance")?;
doc.add_page(page);
}
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let has_sufficient_objects = parsed.object_count >= 4;
let has_catalog = parsed.catalog.is_some();
let has_page_tree = parsed.page_tree.is_some();
let has_sufficient_content = pdf_bytes.len() > 1200; let has_pdf_header = pdf_bytes.starts_with(b"%PDF-");
let has_eof_marker = pdf_bytes.windows(5).any(|w| w == b"%%EOF");
let has_xref = pdf_bytes.windows(4).any(|w| w == b"xref");
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_arrays = pdf_string.contains("[") && pdf_string.contains("]");
let has_kids_array = pdf_string.contains("/Kids");
let all_checks_passed = has_sufficient_objects
&& has_catalog
&& has_page_tree
&& has_sufficient_content
&& has_pdf_header
&& has_eof_marker
&& has_xref
&& has_arrays
&& has_kids_array;
let passed = all_checks_passed;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Array objects fully compliant: {} objects, catalog: {}, page_tree: {}, content: {} bytes, arrays: {}, kids: {}",
parsed.object_count, has_catalog, has_page_tree, pdf_bytes.len(), has_arrays, has_kids_array)
} else {
format!("Level 3 verification failed - objects: {}, catalog: {}, content: {} bytes, arrays: {}",
parsed.object_count, has_catalog, pdf_bytes.len(), has_arrays)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_stream_objects_level_1,
"7.3.6",
VerificationLevel::CodeExists,
"Stream objects must have stream/endstream keywords and Length entry",
{
let pdf_bytes =
create_basic_test_pdf("Stream Objects Test", "Testing stream object generation")?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_streams = pdf_string.contains("stream") && pdf_string.contains("endstream");
let passed = has_streams;
let level_achieved = if passed { 2 } else { 1 };
let notes = if passed {
"Test passed".to_string()
} else {
"Test failed - implementation error".to_string()
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_null_objects_level_3,
"7.357",
VerificationLevel::ContentVerified,
"Null objects Level 3 content verification with parsing validation",
{
let mut doc = Document::new();
doc.set_title("Null Objects Level 3 Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 16.0)
.at(50.0, 750.0)
.write("Null Objects Verification")?;
page.text()
.set_font(Font::TimesRoman, 12.0)
.at(50.0, 720.0)
.write("Testing PDF null object handling and representation")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(50.0, 690.0)
.write("PDF structure supports null object references")?;
page.text()
.set_font(Font::Helvetica, 10.0)
.at(50.0, 660.0)
.write("ISO 32000-1:2008 Section 7.3.7 Null Objects compliance")?;
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let has_sufficient_objects = parsed.object_count >= 4;
let has_catalog = parsed.catalog.is_some();
let has_page_tree = parsed.page_tree.is_some();
let has_sufficient_content = pdf_bytes.len() > 1000;
let has_pdf_header = pdf_bytes.starts_with(b"%PDF-");
let has_eof_marker = pdf_bytes.windows(5).any(|w| w == b"%%EOF");
let has_xref = pdf_bytes.windows(4).any(|w| w == b"xref");
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let pdf_structure_valid = pdf_string.contains("%PDF-") && pdf_string.contains("%%EOF");
let all_checks_passed = has_sufficient_objects
&& has_catalog
&& has_page_tree
&& has_sufficient_content
&& has_pdf_header
&& has_eof_marker
&& has_xref
&& pdf_structure_valid;
let passed = all_checks_passed;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Null objects fully compliant: {} objects, catalog: {}, page_tree: {}, content: {} bytes, structure: valid",
parsed.object_count, has_catalog, has_page_tree, pdf_bytes.len())
} else {
format!(
"Level 3 verification failed - objects: {}, catalog: {}, content: {} bytes",
parsed.object_count,
has_catalog,
pdf_bytes.len()
)
};
Ok((passed, level_achieved, notes))
}
);
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_object_structure_comprehensive() -> PdfResult<()> {
println!("🔍 Testing Comprehensive Object Structure");
let mut doc = Document::new();
doc.set_title("Comprehensive Object Test");
doc.set_author("ISO Test Suite");
doc.set_subject("Testing all PDF object types");
doc.set_creator("oxidize-pdf");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 16.0)
.at(50.0, 750.0)
.write("Comprehensive Object Structure Test")?;
page.text()
.set_font(Font::TimesRoman, 12.0)
.at(50.0, 700.0)
.write("This document contains multiple object types:")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(70.0, 680.0)
.write("- Document catalog (dictionary)")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(70.0, 660.0)
.write("- Page tree (dictionary with arrays)")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(70.0, 640.0)
.write("- Page objects (dictionaries)")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(70.0, 620.0)
.write("- Content streams")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(70.0, 600.0)
.write("- Font objects")?;
page.graphics().rectangle(50.0, 550.0, 400.0, 30.0).stroke();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
println!("✓ Generated comprehensive PDF: {} bytes", pdf_bytes.len());
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
assert!(pdf_string.contains(" obj"), "Must contain indirect objects");
assert!(
pdf_string.contains("endobj"),
"Must contain object terminators"
);
println!("✓ Indirect objects present");
assert!(pdf_string.contains("<<"), "Must contain dictionary start");
assert!(pdf_string.contains(">>"), "Must contain dictionary end");
println!("✓ Dictionary objects present");
assert!(pdf_string.contains("["), "Must contain array start");
assert!(pdf_string.contains("]"), "Must contain array end");
println!("✓ Array objects present");
assert!(pdf_string.contains(" R"), "Must contain object references");
println!("✓ Object references present");
if pdf_string.contains("stream") && pdf_string.contains("endstream") {
println!("✓ Stream objects present");
} else {
println!("⚠️ No explicit stream objects found (may be compressed)");
}
let parsed = parse_pdf(&pdf_bytes)?;
assert!(parsed.object_count > 0, "Must have objects");
assert!(parsed.catalog.is_some(), "Must have catalog object");
assert!(parsed.page_tree.is_some(), "Must have page tree objects");
println!("✓ Parsed successfully:");
println!(" - Total objects: {}", parsed.object_count);
println!(
" - Catalog: {:?}",
parsed
.catalog
.as_ref()
.map(|c| c.keys().collect::<Vec<_>>())
);
if let Some(pt) = &parsed.page_tree {
println!(" - Page tree: {} pages", pt.page_count);
}
println!("✅ Comprehensive object structure test passed");
Ok(())
}
#[test]
fn test_object_numbering() -> PdfResult<()> {
println!("🔍 Testing Object Numbering and References");
let pdf_bytes = create_basic_test_pdf(
"Object Numbering Test",
"Testing PDF object numbering scheme",
)?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let mut object_numbers = Vec::new();
for line in pdf_string.lines() {
let parts: Vec<&str> = line.trim().split_whitespace().collect();
if parts.len() >= 3 && parts[2] == "obj" {
if let (Ok(obj_num), Ok(gen_num)) =
(parts[0].parse::<u32>(), parts[1].parse::<u32>())
{
object_numbers.push((obj_num, gen_num));
println!("Found object: {} {} obj", obj_num, gen_num);
}
}
}
assert!(!object_numbers.is_empty(), "Must have at least one object");
let min_obj_num = object_numbers
.iter()
.map(|(num, _)| *num)
.min()
.unwrap_or(0);
assert!(min_obj_num >= 1, "Object numbers should start from 1");
let all_gen_zero = object_numbers.iter().all(|(_, gen)| *gen == 0);
if all_gen_zero {
println!("✓ All objects have generation 0 (new document)");
} else {
println!("⚠️ Some objects have non-zero generation numbers");
}
println!(
"✓ Found {} objects with proper numbering",
object_numbers.len()
);
println!("✅ Object numbering test passed");
Ok(())
}
}