use std::process::Command;
use std::fs;
use std::path::Path;
fn run_pdf_cli(args: &[&str]) -> (String, String, bool) {
let output = Command::new("cargo")
.args(["run", "--"])
.args(args)
.current_dir(env!("CARGO_MANIFEST_DIR"))
.output()
.expect("Failed to execute pdf-cli");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(stdout, stderr, output.status.success())
}
fn roundtrip_test(md_path: &str, label: &str) {
let base = env!("CARGO_MANIFEST_DIR");
let md_file = format!("{}/{}", base, md_path);
let pdf_file = format!("{}/target/test_output/{}.pdf", base, label);
let out_md_file = format!("{}/target/test_output/{}_roundtrip.md", base, label);
fs::create_dir_all(format!("{}/target/test_output", base)).unwrap();
let original_md = fs::read_to_string(&md_file)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", md_file, e));
println!("=== Roundtrip Test: {} ===", label);
println!("Input: {}", md_file);
println!("Original MD length: {} bytes, {} lines", original_md.len(), original_md.lines().count());
let (stdout, stderr, success) = run_pdf_cli(&["md-to-pdf", &md_file, &pdf_file]);
println!("[MD->PDF] stdout: {}", stdout.trim());
if !stderr.is_empty() {
println!("[MD->PDF] stderr: {}", stderr.trim());
}
assert!(success, "md-to-pdf failed for {}", label);
assert!(Path::new(&pdf_file).exists(), "PDF file not created: {}", pdf_file);
let pdf_size = fs::metadata(&pdf_file).unwrap().len();
println!("[MD->PDF] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 0, "PDF file is empty");
let (stdout, stderr, success) = run_pdf_cli(&["pdf-to-md", &pdf_file, &out_md_file]);
println!("[PDF->MD] stdout: {}", stdout.trim());
if !stderr.is_empty() {
println!("[PDF->MD] stderr: {}", stderr.trim());
}
assert!(success, "pdf-to-md failed for {}", label);
assert!(Path::new(&out_md_file).exists(), "Output MD file not created: {}", out_md_file);
let roundtrip_md = fs::read_to_string(&out_md_file).unwrap();
println!("[PDF->MD] Roundtrip MD length: {} bytes, {} lines", roundtrip_md.len(), roundtrip_md.lines().count());
let original_lines: Vec<&str> = original_md.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.filter(|l| !l.starts_with("```"))
.filter(|l| !l.starts_with("---"))
.filter(|l| !l.starts_with("|"))
.filter(|l| !l.starts_with("["))
.filter(|l| !l.starts_with("- ["))
.collect();
let original_words: Vec<String> = original_lines.iter()
.flat_map(|line| {
let clean = line
.replace("**", "")
.replace("*", "")
.replace("`", "")
.replace("#", "")
.replace("- ", "")
.replace("> ", "");
clean.split_whitespace()
.filter(|w| w.len() > 3)
.map(|w| w.to_string())
.collect::<Vec<_>>()
})
.collect();
let mut found = 0;
let mut checked = 0;
for word in &original_words {
if word.contains('|') || word.contains('[') || word.contains(']')
|| word.contains('(') || word.contains(')') || word.len() < 4 {
continue;
}
checked += 1;
if roundtrip_md.contains(word.as_str()) {
found += 1;
}
}
let recovery_rate = if checked > 0 { (found as f64 / checked as f64) * 100.0 } else { 0.0 };
println!("[Validation] Words checked: {}, found: {}, recovery rate: {:.1}%", checked, found, recovery_rate);
println!("\n--- Roundtrip output (first 20 lines) ---");
for (i, line) in roundtrip_md.lines().take(20).enumerate() {
println!(" {:3}: {}", i + 1, line);
}
println!("--- end ---\n");
assert!(roundtrip_md.len() > 0, "Roundtrip output is empty for {}", label);
println!("=== PASSED: {} ===\n", label);
}
#[test]
fn test_roundtrip_complex_report() {
roundtrip_test("examples/complex_report.md", "complex_report");
}
#[test]
fn test_roundtrip_technical_spec() {
roundtrip_test("examples/technical_spec.md", "technical_spec");
}
#[test]
fn test_roundtrip_mixed_content() {
roundtrip_test("examples/mixed_content.md", "mixed_content");
}
#[test]
fn test_roundtrip_existing_test_md() {
roundtrip_test("test.md", "test_md");
}
#[test]
fn test_roundtrip_existing_roundtrip_md() {
roundtrip_test("roundtrip_test.md", "roundtrip_test_md");
}
#[test]
fn test_roundtrip_enhanced_features() {
roundtrip_test("examples/enhanced_features.md", "enhanced_features");
}
#[test]
fn test_roundtrip_landscape() {
let base = env!("CARGO_MANIFEST_DIR");
let md_file = format!("{}/examples/enhanced_features.md", base);
let pdf_file = format!("{}/target/test_output/landscape.pdf", base);
let out_md_file = format!("{}/target/test_output/landscape_roundtrip.md", base);
fs::create_dir_all(format!("{}/target/test_output", base)).unwrap();
let (_, _, success) = run_pdf_cli(&["md-to-pdf", &md_file, &pdf_file, "--landscape"]);
assert!(success, "md-to-pdf --landscape failed");
assert!(Path::new(&pdf_file).exists());
let pdf_size = fs::metadata(&pdf_file).unwrap().len();
println!("[Landscape] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 0);
let (_, _, success) = run_pdf_cli(&["pdf-to-md", &pdf_file, &out_md_file]);
assert!(success, "pdf-to-md failed for landscape");
let roundtrip = fs::read_to_string(&out_md_file).unwrap();
println!("[Landscape] Roundtrip MD: {} bytes, {} lines", roundtrip.len(), roundtrip.lines().count());
assert!(roundtrip.len() > 100, "Landscape roundtrip output too short");
assert!(roundtrip.contains("Enhanced Markdown Features Demo"));
println!("=== PASSED: landscape ===");
}
#[test]
fn test_merge_pdfs() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_a = format!("{}/test.md", base);
let md_b = format!("{}/roundtrip_test.md", base);
let pdf_a = format!("{}/merge_a.pdf", out_dir);
let pdf_b = format!("{}/merge_b.pdf", out_dir);
let merged = format!("{}/merged_test.pdf", out_dir);
let merged_md = format!("{}/merged_test_roundtrip.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_a, &pdf_a]);
assert!(ok, "Failed to create PDF A");
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_b, &pdf_b]);
assert!(ok, "Failed to create PDF B");
let (stdout, _, ok) = run_pdf_cli(&["merge", &pdf_a, &pdf_b, "-o", &merged]);
assert!(ok, "Merge failed");
println!("[merge] {}", stdout.trim());
assert!(Path::new(&merged).exists());
assert!(fs::metadata(&merged).unwrap().len() > 0);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &merged, &merged_md]);
assert!(ok, "pdf-to-md on merged failed");
let text = fs::read_to_string(&merged_md).unwrap();
assert!(text.contains("Test Document") || text.contains("Features"), "Merged PDF missing content from source A");
println!("=== PASSED: merge ===");
}
#[test]
fn test_split_pdf() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/enhanced_features.md", base);
let pdf_src = format!("{}/split_source.pdf", out_dir);
let pdf_split = format!("{}/split_page1.pdf", out_dir);
let split_md = format!("{}/split_page1_roundtrip.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_src]);
assert!(ok, "Failed to create source PDF");
let (stdout, _, ok) = run_pdf_cli(&["split", &pdf_src, "-o", &pdf_split, "--start", "1", "--end", "1"]);
assert!(ok, "Split failed");
println!("[split] {}", stdout.trim());
assert!(Path::new(&pdf_split).exists());
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_split, &split_md]);
assert!(ok, "pdf-to-md on split failed");
let text = fs::read_to_string(&split_md).unwrap();
assert!(text.len() > 10, "Split output too short");
println!("=== PASSED: split ===");
}
#[test]
fn test_metadata_pdf() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/test.md", base);
let pdf_out = format!("{}/meta_test.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&[
"md-to-pdf-meta", &md_src, &pdf_out,
"--title", "Integration Test",
"--author", "pdf-cli-test",
"--subject", "Testing metadata embedding",
"--keywords", "test,pdf,metadata",
]);
assert!(ok, "md-to-pdf-meta failed");
assert!(Path::new(&pdf_out).exists());
let raw_bytes = fs::read(&pdf_out).unwrap();
let raw = String::from_utf8_lossy(&raw_bytes);
assert!(raw.contains("/Title (Integration Test)"), "Title not found in PDF");
assert!(raw.contains("/Author (pdf-cli-test)"), "Author not found in PDF");
assert!(raw.contains("/Producer (pdf-cli)"), "Producer not found in PDF");
println!("=== PASSED: metadata ===");
}
#[test]
fn test_rotate_pdf() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/test.md", base);
let pdf_src = format!("{}/rotate_source.pdf", out_dir);
let pdf_rotated = format!("{}/rotated_90.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_src]);
assert!(ok, "Failed to create source PDF");
let (stdout, _, ok) = run_pdf_cli(&["rotate", &pdf_src, "-o", &pdf_rotated, "--angle", "90"]);
assert!(ok, "Rotate failed");
println!("[rotate] {}", stdout.trim());
assert!(Path::new(&pdf_rotated).exists());
let raw = fs::read(&pdf_rotated).unwrap();
let content = String::from_utf8_lossy(&raw);
assert!(content.contains("/Rotate 90"), "Rotation not found in PDF");
let out_md = format!("{}/rotated_roundtrip.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_rotated, &out_md]);
assert!(ok, "pdf-to-md on rotated PDF failed");
let text = fs::read_to_string(&out_md).unwrap();
assert!(text.len() > 10, "Rotated PDF text extraction too short");
println!("=== PASSED: rotate ===");
}
#[test]
fn test_watermark_pdf() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/test.md", base);
let pdf_src = format!("{}/watermark_source.pdf", out_dir);
let pdf_wm = format!("{}/watermarked.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_src]);
assert!(ok, "Failed to create source PDF");
let (stdout, _, ok) = run_pdf_cli(&["watermark", &pdf_src, "-o", &pdf_wm, "--text", "CONFIDENTIAL"]);
assert!(ok, "Watermark failed");
println!("[watermark] {}", stdout.trim());
assert!(Path::new(&pdf_wm).exists());
let raw = fs::read(&pdf_wm).unwrap();
let content = String::from_utf8_lossy(&raw);
assert!(content.contains("CONFIDENTIAL"), "Watermark text not found in PDF");
println!("=== PASSED: watermark ===");
}
#[test]
fn test_reorder_pdf() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_a = format!("{}/test.md", base);
let md_b = format!("{}/roundtrip_test.md", base);
let pdf_a = format!("{}/reorder_a.pdf", out_dir);
let pdf_b = format!("{}/reorder_b.pdf", out_dir);
let merged = format!("{}/reorder_merged.pdf", out_dir);
let reordered = format!("{}/reordered.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_a, &pdf_a]);
assert!(ok);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_b, &pdf_b]);
assert!(ok);
let (_, _, ok) = run_pdf_cli(&["merge", &pdf_a, &pdf_b, "-o", &merged]);
assert!(ok, "Merge failed");
let (stdout, _, ok) = run_pdf_cli(&["reorder", &merged, "-o", &reordered, "--pages", "2,1"]);
assert!(ok, "Reorder failed");
println!("[reorder] {}", stdout.trim());
assert!(Path::new(&reordered).exists());
assert!(fs::metadata(&reordered).unwrap().len() > 0);
println!("=== PASSED: reorder ===");
}
#[test]
fn test_full_features_roundtrip() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/full_features.md", base);
let pdf_out = format!("{}/full_features.pdf", out_dir);
let md_out = format!("{}/full_features_roundtrip.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_out]);
assert!(ok, "md-to-pdf failed for full_features");
let pdf_size = fs::metadata(&pdf_out).unwrap().len();
println!("[full_features] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 5000, "PDF too small: {} bytes", pdf_size);
let raw = fs::read(&pdf_out).unwrap();
let raw_str = String::from_utf8_lossy(&raw);
assert!(raw_str.starts_with("%PDF-"), "Missing PDF header");
assert!(raw_str.contains("/Type /Catalog"), "Missing catalog");
assert!(raw_str.contains("/Type /Pages"), "Missing pages tree");
assert!(raw_str.contains("%%EOF"), "Missing EOF marker");
let page_count = raw_str.matches("/Type /Page\n").count()
+ raw_str.matches("/Type /Page\r").count();
println!("[full_features] Page objects found: {}", page_count);
assert!(page_count >= 4, "Expected at least 4 pages, got {}", page_count);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_out, &md_out]);
assert!(ok, "pdf-to-md failed for full_features");
let roundtrip = fs::read_to_string(&md_out).unwrap();
let must_contain = vec![
"Full Feature Showcase",
"Text Formatting",
"Lists",
"exercises every supported element type",
"bold text",
"italic text",
"First item at depth zero",
"Nested item at depth one",
"Deep nested item",
"First numbered item",
"Second numbered item",
"[x] Completed task one",
"[ ] Pending task three",
"fibonacci",
"quicksort",
"main",
"def",
"Feature",
"Status",
"Priority",
"PDF Generation",
"Done",
"simple blockquote",
"Triple nested",
"Rust",
"systems programming language",
"Portable Document Format",
"first footnote",
"second footnote",
"rust-lang.org",
"adobe.com",
"Rust Logo",
"rust-logo.png",
"PDF Icon",
"Content above the rule",
"Content below the rule",
"new page after the break",
"Initialize the project",
"cargo test",
];
let mut missing = Vec::new();
for s in &must_contain {
if !roundtrip.contains(s) {
missing.push(*s);
}
}
assert!(
missing.is_empty(),
"Round-trip missing {} items: {:?}",
missing.len(),
missing
);
println!("[full_features] All {} content checks passed", must_contain.len());
println!("=== PASSED: full_features_roundtrip ===");
}
#[test]
fn test_full_features_landscape_metadata() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/full_features.md", base);
let pdf_out = format!("{}/full_features_landscape_meta.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&[
"md-to-pdf-meta", &md_src, &pdf_out,
"--title", "Full Features Showcase",
"--author", "pdf-rs test suite",
"--subject", "Integration testing",
"--keywords", "pdf,rust,test,library",
"--landscape",
]);
assert!(ok, "md-to-pdf-meta --landscape failed");
let raw = fs::read(&pdf_out).unwrap();
let content = String::from_utf8_lossy(&raw);
assert!(content.contains("792"), "Missing landscape width 792");
assert!(content.contains("612"), "Missing landscape height 612");
assert!(content.contains("/Title (Full Features Showcase)"), "Title missing");
assert!(content.contains("/Author (pdf-rs test suite)"), "Author missing");
assert!(content.contains("/Subject (Integration testing)"), "Subject missing");
assert!(content.contains("/Producer (pdf-cli)"), "Producer missing");
assert!(content.contains("fibonacci"), "Code block content missing");
assert!(content.contains("quicksort"), "Python code missing");
println!("[landscape_meta] PDF size: {} bytes", raw.len());
println!("=== PASSED: full_features_landscape_metadata ===");
}
#[test]
fn test_full_features_watermark() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/full_features.md", base);
let pdf_src = format!("{}/full_features_wm_src.pdf", out_dir);
let pdf_wm = format!("{}/full_features_watermarked.pdf", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_src]);
assert!(ok, "Source PDF generation failed");
let (_, _, ok) = run_pdf_cli(&[
"watermark", &pdf_src, "-o", &pdf_wm,
"--text", "DRAFT",
"--size", "60",
"--opacity", "0.2",
]);
assert!(ok, "Watermark failed");
let raw = fs::read(&pdf_wm).unwrap();
let content = String::from_utf8_lossy(&raw);
assert!(content.contains("DRAFT"), "Watermark text not found");
assert!(content.contains("fibonacci"), "Original content lost after watermark");
println!("[watermark] Watermarked PDF size: {} bytes", raw.len());
println!("=== PASSED: full_features_watermark ===");
}
#[test]
fn test_library_api_generate_validate() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_content = fs::read_to_string(format!("{}/examples/full_features.md", base)).unwrap();
let elements = pdfrs::elements::parse_markdown(&md_content);
println!("[lib_api] Parsed {} elements from full_features.md", elements.len());
assert!(elements.len() > 50, "Expected many elements, got {}", elements.len());
let has_heading = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::Heading { .. }));
let has_paragraph = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::Paragraph { .. }));
let has_list = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::UnorderedListItem { .. }));
let has_ordered = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::OrderedListItem { .. }));
let has_task = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::TaskListItem { .. }));
let has_code = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::CodeBlock { .. }));
let has_table = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::TableRow { .. }));
let has_quote = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::BlockQuote { .. }));
let has_def = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::DefinitionItem { .. }));
let has_footnote = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::Footnote { .. }));
let has_link = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::Link { .. }));
let has_image = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::Image { .. }));
let has_hr = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::HorizontalRule));
let has_pagebreak = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::PageBreak));
let has_empty = elements.iter().any(|e| matches!(e, pdfrs::elements::Element::EmptyLine));
assert!(has_heading, "Missing Heading elements");
assert!(has_paragraph, "Missing Paragraph elements");
assert!(has_list, "Missing UnorderedListItem elements");
assert!(has_ordered, "Missing OrderedListItem elements");
assert!(has_task, "Missing TaskListItem elements");
assert!(has_code, "Missing CodeBlock elements");
assert!(has_table, "Missing TableRow elements");
assert!(has_quote, "Missing BlockQuote elements");
assert!(has_def, "Missing DefinitionItem elements");
assert!(has_footnote, "Missing Footnote elements");
assert!(has_link, "Missing Link elements");
assert!(has_image, "Missing Image elements");
assert!(has_hr, "Missing HorizontalRule elements");
assert!(has_pagebreak, "Missing PageBreak elements");
assert!(has_empty, "Missing EmptyLine elements");
println!("[lib_api] All 15 element types found in parsed output");
let layout = pdfrs::pdf_generator::PageLayout::portrait();
let pdf_bytes = pdfrs::pdf_generator::generate_pdf_bytes(
&elements, "Helvetica", 12.0, layout,
).expect("generate_pdf_bytes failed");
println!("[lib_api] Generated {} bytes of PDF", pdf_bytes.len());
assert!(pdf_bytes.len() > 5000, "PDF too small: {} bytes", pdf_bytes.len());
let validation = pdfrs::pdf::validate_pdf_bytes(&pdf_bytes);
println!("[lib_api] Validation: valid={}, pages={}, objects={}, errors={:?}, warnings={:?}",
validation.valid, validation.page_count, validation.object_count,
validation.errors, validation.warnings);
assert!(validation.valid, "PDF validation failed: {:?}", validation.errors);
assert!(validation.page_count >= 4, "Expected >= 4 pages, got {}", validation.page_count);
assert!(validation.object_count > 10, "Expected many objects, got {}", validation.object_count);
let content = String::from_utf8_lossy(&pdf_bytes);
let content_checks = vec![
"Full Feature Showcase", "fibonacci", "quicksort",
"Completed task", "Pending task",
"First item at depth zero", "Deep nested",
"systems programming language",
"rust-lang.org", "Rust Logo",
"new page after the break",
];
for s in &content_checks {
assert!(content.contains(s), "PDF bytes missing content: '{}'", s);
}
println!("[lib_api] All {} content checks passed", content_checks.len());
let out_path = format!("{}/lib_api_generated.pdf", out_dir);
fs::write(&out_path, &pdf_bytes).unwrap();
println!("[lib_api] Written to {} for inspection", out_path);
println!("=== PASSED: library_api_generate_validate ===");
}
#[test]
fn test_technical_report_complex_roundtrip() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/technical_report_complex.md", base);
let pdf_out = format!("{}/technical_report_complex.pdf", out_dir);
let md_out = format!("{}/technical_report_complex_rt.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_out]);
assert!(ok, "md-to-pdf failed for technical_report_complex");
let pdf_size = fs::metadata(&pdf_out).unwrap().len();
println!("[tech_report] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 15000, "PDF too small: {} bytes", pdf_size);
let raw = fs::read(&pdf_out).unwrap();
let validation = pdfrs::pdf::validate_pdf_bytes(&raw);
println!("[tech_report] valid={}, pages={}, objects={}, errors={:?}",
validation.valid, validation.page_count, validation.object_count, validation.errors);
assert!(validation.valid, "PDF validation failed: {:?}", validation.errors);
assert!(validation.page_count >= 6, "Expected >= 6 pages, got {}", validation.page_count);
assert!(validation.object_count > 15, "Expected many objects, got {}", validation.object_count);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_out, &md_out]);
assert!(ok, "pdf-to-md failed for technical_report_complex");
let roundtrip = fs::read_to_string(&md_out).unwrap();
let must_contain = vec![
"Distributed Systems Performance Analysis",
"Executive Summary",
"System Architecture Overview",
"Benchmark Methodology",
"Capacity Planning",
"Recommendations",
"2.4 million requests per second",
"99.97%",
"47%",
"gRPC", "Kafka", "NATS", "Redis",
"API Gateway", "Order Engine", "Inventory", "Pricing",
"847,000", "2,482,000",
"BM-001", "BM-005",
"LoadGenerator", "Semaphore", "BenchmarkResult",
"target_rps", "concurrency",
"LatencyDistribution", "confidence_interval",
"np.percentile",
"HorizontalPodAutoscaler", "order-engine",
"minReplicas", "maxReplicas",
"Gateway Layer", "Core Business Logic", "Data Pipeline",
"API Gateway with rate limiting",
"Event ingestion",
"Circuit breaker activation",
"Cross-region failover",
"Key Finding",
"Critical Issue",
"50th percentile",
"Service Level Agreement",
"Command Query Responsibility Segregation",
"latency measurements",
"Cost projections",
"dist-sys-patterns",
"sre.google",
"kafka.apache.org",
"Engineering Team",
"February 2026",
"Raw Benchmark Data",
"142,000", "318,000",
];
let mut missing = Vec::new();
for s in &must_contain {
if !roundtrip.contains(s) {
missing.push(*s);
}
}
assert!(
missing.is_empty(),
"[tech_report] Round-trip missing {} items: {:?}",
missing.len(), missing
);
println!("[tech_report] All {} content checks passed", must_contain.len());
println!("=== PASSED: technical_report_complex_roundtrip ===");
}
#[test]
fn test_api_reference_complex_roundtrip() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/api_reference_complex.md", base);
let pdf_out = format!("{}/api_reference_complex.pdf", out_dir);
let md_out = format!("{}/api_reference_complex_rt.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_out]);
assert!(ok, "md-to-pdf failed for api_reference_complex");
let pdf_size = fs::metadata(&pdf_out).unwrap().len();
println!("[api_ref] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 20000, "PDF too small: {} bytes", pdf_size);
let raw = fs::read(&pdf_out).unwrap();
let validation = pdfrs::pdf::validate_pdf_bytes(&raw);
println!("[api_ref] valid={}, pages={}, objects={}, errors={:?}",
validation.valid, validation.page_count, validation.object_count, validation.errors);
assert!(validation.valid, "PDF validation failed: {:?}", validation.errors);
assert!(validation.page_count >= 8, "Expected >= 8 pages, got {}", validation.page_count);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_out, &md_out]);
assert!(ok, "pdf-to-md failed for api_reference_complex");
let roundtrip = fs::read_to_string(&md_out).unwrap();
let must_contain = vec![
"PDF-RS Library API Reference",
"Module Overview",
"Elements Module",
"PDF Module",
"PDF Generator Module",
"PDF Operations Module",
"Markdown Module",
"Error Handling",
"Usage Examples",
"Heading", "Paragraph", "CodeBlock", "BlockQuote",
"UnorderedListItem", "OrderedListItem", "TaskListItem",
"InlineCode", "Link", "Image", "StyledText",
"TableRow", "DefinitionItem", "Footnote",
"HorizontalRule", "PageBreak", "EmptyLine",
"parse_markdown", "strip_inline_formatting",
"validate_pdf", "validate_pdf_bytes",
"generate_pdf_bytes", "extract_text",
"PdfDocument", "PdfValidation", "PageLayout", "Color",
"TextAlign", "PdfMetadata",
"TextAnnotation", "LinkAnnotation", "HighlightAnnotation",
"elements", "pdf_generator", "pdf_ops", "markdown",
"compression", "security",
"Helvetica", "portrait",
"assert!", "validation.valid",
"612 x 792", "792 x 612",
"positioned text note",
"clickable rectangular region",
"colored highlight overlay",
"17 element variants",
"In-memory validation",
"FlateDecode", "DCTDecode",
"primary library API",
"Best Practice",
"pdf-rs v0.1.0",
];
let mut missing = Vec::new();
for s in &must_contain {
if !roundtrip.contains(s) {
missing.push(*s);
}
}
assert!(
missing.is_empty(),
"[api_ref] Round-trip missing {} items: {:?}",
missing.len(), missing
);
println!("[api_ref] All {} content checks passed", must_contain.len());
println!("=== PASSED: api_reference_complex_roundtrip ===");
}
#[test]
fn test_complex_examples_library_api_batch() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let examples = vec![
("full_features.md", 50, 4),
("technical_report_complex.md", 80, 6),
("api_reference_complex.md", 100, 8),
];
for (filename, min_elements, min_pages) in &examples {
let md_path = format!("{}/examples/{}", base, filename);
let md_content = fs::read_to_string(&md_path)
.unwrap_or_else(|_| panic!("Failed to read {}", md_path));
let elements = pdfrs::elements::parse_markdown(&md_content);
println!("[batch:{}] Parsed {} elements", filename, elements.len());
assert!(
elements.len() >= *min_elements,
"{}: expected >= {} elements, got {}", filename, min_elements, elements.len()
);
let layout_p = pdfrs::pdf_generator::PageLayout::portrait();
let bytes_p = pdfrs::pdf_generator::generate_pdf_bytes(
&elements, "Helvetica", 12.0, layout_p,
).unwrap_or_else(|e| panic!("{}: generate_pdf_bytes portrait failed: {}", filename, e));
let val_p = pdfrs::pdf::validate_pdf_bytes(&bytes_p);
assert!(val_p.valid, "{} portrait validation failed: {:?}", filename, val_p.errors);
assert!(
val_p.page_count >= *min_pages,
"{}: expected >= {} pages, got {}", filename, min_pages, val_p.page_count
);
println!("[batch:{}] Portrait: {} bytes, {} pages, {} objects",
filename, bytes_p.len(), val_p.page_count, val_p.object_count);
let layout_l = pdfrs::pdf_generator::PageLayout::landscape();
let bytes_l = pdfrs::pdf_generator::generate_pdf_bytes(
&elements, "Times-Roman", 11.0, layout_l,
).unwrap_or_else(|e| panic!("{}: generate_pdf_bytes landscape failed: {}", filename, e));
let val_l = pdfrs::pdf::validate_pdf_bytes(&bytes_l);
assert!(val_l.valid, "{} landscape validation failed: {:?}", filename, val_l.errors);
println!("[batch:{}] Landscape: {} bytes, {} pages, {} objects",
filename, bytes_l.len(), val_l.page_count, val_l.object_count);
let stem = filename.replace(".md", "");
fs::write(format!("{}/batch_{}_portrait.pdf", out_dir, stem), &bytes_p).unwrap();
fs::write(format!("{}/batch_{}_landscape.pdf", out_dir, stem), &bytes_l).unwrap();
}
println!("=== PASSED: complex_examples_library_api_batch ===");
}
#[test]
fn test_math_and_formulas_roundtrip() {
let base = env!("CARGO_MANIFEST_DIR");
let out_dir = format!("{}/target/test_output", base);
fs::create_dir_all(&out_dir).unwrap();
let md_src = format!("{}/examples/math_and_formulas.md", base);
let pdf_out = format!("{}/math_and_formulas.pdf", out_dir);
let md_out = format!("{}/math_and_formulas_rt.md", out_dir);
let (_, _, ok) = run_pdf_cli(&["md-to-pdf", &md_src, &pdf_out]);
assert!(ok, "md-to-pdf failed for math_and_formulas");
let pdf_size = fs::metadata(&pdf_out).unwrap().len();
println!("[math] PDF size: {} bytes", pdf_size);
assert!(pdf_size > 15000, "PDF too small: {} bytes", pdf_size);
let raw = fs::read(&pdf_out).unwrap();
let validation = pdfrs::pdf::validate_pdf_bytes(&raw);
println!("[math] valid={}, pages={}, objects={}, errors={:?}",
validation.valid, validation.page_count, validation.object_count, validation.errors);
assert!(validation.valid, "PDF validation failed: {:?}", validation.errors);
assert!(validation.page_count >= 5, "Expected >= 5 pages, got {}", validation.page_count);
let (_, _, ok) = run_pdf_cli(&["pdf-to-md", &pdf_out, &md_out]);
assert!(ok, "pdf-to-md failed for math_and_formulas");
let roundtrip = fs::read_to_string(&md_out).unwrap();
let must_contain = vec![
"Mathematical Foundations",
"Linear Algebra",
"Calculus and Optimization",
"Probability and Statistics",
"Neural Network",
"Information Theory",
"Advanced Topics",
"SUM",
"sqrt",
"GradientDescent",
"learning_rate",
"max_iterations",
"tolerance",
"svd_compress",
"np.linalg.svd",
"gradient_descent",
"Sigmoid", "Tanh", "ReLU", "Softmax",
"Gradient Descent", "Backpropagation",
"Bayes", "Cross-Entropy", "Attention",
"KL Divergence", "Fourier",
"Optimization",
"Transformers",
"Signal",
"standard mathematical notation",
"Goodfellow",
];
let mut missing = Vec::new();
for s in &must_contain {
if !roundtrip.contains(s) {
missing.push(*s);
}
}
assert!(
missing.is_empty(),
"[math] Round-trip missing {} items: {:?}",
missing.len(), missing
);
println!("[math] All {} content checks passed", must_contain.len());
println!("=== PASSED: math_and_formulas_roundtrip ===");
}
#[test]
fn test_math_parsing_library_api() {
let md = r#"
# Math Test
$E = mc^2$
Block math:
$$
\frac{a}{b} + \sqrt{c}
$$
Multi-line block:
$$
\sum_{i=1}^{n} x_i
\int_{0}^{\infty} e^{-x} dx
$$
Regular paragraph after math.
"#;
let elements = pdfrs::elements::parse_markdown(md);
let math_inline_count = elements.iter().filter(|e| matches!(e, pdfrs::elements::Element::MathInline { .. })).count();
let math_block_count = elements.iter().filter(|e| matches!(e, pdfrs::elements::Element::MathBlock { .. })).count();
println!("[math_api] Parsed {} elements, {} inline math, {} block math",
elements.len(), math_inline_count, math_block_count);
assert!(math_inline_count >= 1, "Expected at least 1 MathInline, got {}. Elements: {:?}", math_inline_count, elements);
assert!(math_block_count >= 2, "Expected at least 2 MathBlock, got {}", math_block_count);
let has_inline_emc2 = elements.iter().any(|e| {
if let pdfrs::elements::Element::MathInline { expression } = e {
expression.contains("E = mc")
} else { false }
});
assert!(has_inline_emc2, "MathInline with E=mc^2 not found");
let has_block_frac = elements.iter().any(|e| {
if let pdfrs::elements::Element::MathBlock { expression } = e {
expression.contains("frac")
} else { false }
});
assert!(has_block_frac, "MathBlock with frac not found");
let has_block_sum = elements.iter().any(|e| {
if let pdfrs::elements::Element::MathBlock { expression } = e {
expression.contains("sum")
} else { false }
});
assert!(has_block_sum, "MathBlock with sum not found");
let layout = pdfrs::pdf_generator::PageLayout::portrait();
let bytes = pdfrs::pdf_generator::generate_pdf_bytes(
&elements, "Helvetica", 12.0, layout,
).unwrap();
let val = pdfrs::pdf::validate_pdf_bytes(&bytes);
assert!(val.valid, "Math PDF validation failed: {:?}", val.errors);
println!("[math_api] Generated {} bytes, {} pages", bytes.len(), val.page_count);
let content = String::from_utf8_lossy(&bytes);
assert!(content.contains("SUM"), "Rendered SUM not found in PDF");
assert!(content.contains("sqrt"), "Rendered sqrt not found in PDF");
println!("=== PASSED: math_parsing_library_api ===");
}