use pdf_oxide::document::PdfDocument;
use pdf_oxide::error::Error;
fn build_minimal_pdf(text_content: Option<&str>) -> Vec<u8> {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
if let Some(text) = text_content {
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = format!("BT /F1 12 Tf 72 720 Td ({}) Tj ET", text);
pdf.extend_from_slice(
format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes(),
);
pdf.extend_from_slice(content.as_bytes());
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
} else {
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3]);
}
pdf
}
fn build_multi_page_pdf(page_count: usize) -> Vec<u8> {
let mut pdf = b"%PDF-1.7\n".to_vec();
let mut offsets = vec![0usize];
let off1 = pdf.len();
offsets.push(off1);
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let kids: Vec<String> = (0..page_count).map(|i| format!("{} 0 R", i + 3)).collect();
let kids_str = kids.join(" ");
let off2 = pdf.len();
offsets.push(off2);
pdf.extend_from_slice(
format!(
"2 0 obj\n<< /Type /Pages /Kids [{}] /Count {} >>\nendobj\n",
kids_str, page_count
)
.as_bytes(),
);
let font_obj_num = page_count + 3;
for i in 0..page_count {
let page_num = i + 3;
let content_num = page_num + page_count;
let off = pdf.len();
offsets.push(off);
pdf.extend_from_slice(
format!(
"{} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {} 0 R /Resources << /Font << /F1 {} 0 R >> >> >>\nendobj\n",
page_num, content_num, font_obj_num
)
.as_bytes(),
);
}
for i in 0..page_count {
let content_num = i + 3 + page_count;
let content = format!("BT /F1 12 Tf 72 720 Td (Page {}) Tj ET", i + 1);
let off = pdf.len();
offsets.push(off);
pdf.extend_from_slice(
format!("{} 0 obj\n<< /Length {} >>\nstream\n", content_num, content.len()).as_bytes(),
);
pdf.extend_from_slice(content.as_bytes());
pdf.extend_from_slice(b"\nendstream\nendobj\n");
}
let font_off = pdf.len();
offsets.push(font_off);
pdf.extend_from_slice(
format!(
"{} 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
font_obj_num
)
.as_bytes(),
);
finalize_pdf_with_size(&mut pdf, &offsets);
pdf
}
fn finalize_pdf(pdf: &mut Vec<u8>, obj_offsets: &[usize]) {
let xref_offset = pdf.len();
let count = obj_offsets.len();
pdf.extend_from_slice(format!("xref\n0 {}\n", count).as_bytes());
pdf.extend_from_slice(b"0000000000 65535 f \r\n");
for &off in &obj_offsets[1..] {
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", off).as_bytes());
}
let trailer = format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n",
count, xref_offset
);
pdf.extend_from_slice(trailer.as_bytes());
}
fn finalize_pdf_with_size(pdf: &mut Vec<u8>, obj_offsets: &[usize]) {
let xref_offset = pdf.len();
let count = obj_offsets.len();
pdf.extend_from_slice(format!("xref\n0 {}\n", count).as_bytes());
pdf.extend_from_slice(b"0000000000 65535 f \r\n");
for &off in &obj_offsets[1..] {
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", off).as_bytes());
}
let trailer = format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n",
count, xref_offset
);
pdf.extend_from_slice(trailer.as_bytes());
}
fn write_temp_pdf(data: &[u8], name: &str) -> std::path::PathBuf {
use std::io::Write;
let dir = std::env::temp_dir().join("pdf_oxide_coverage_tests");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(data).unwrap();
path
}
#[test]
fn test_from_bytes_valid_minimal() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).expect("Should open minimal PDF");
assert_eq!(doc.version(), (1, 7));
assert_eq!(doc.page_count().unwrap(), 1);
}
#[test]
fn test_from_bytes_with_text() {
let pdf = build_minimal_pdf(Some("Hello World"));
let mut doc = PdfDocument::from_bytes(pdf).expect("Should open PDF with text");
assert_eq!(doc.page_count().unwrap(), 1);
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Hello") || text.contains("World"));
}
#[test]
fn test_from_bytes_empty_data() {
let result = PdfDocument::from_bytes(vec![]);
assert!(result.is_err());
}
#[test]
fn test_from_bytes_garbage_data() {
let result = PdfDocument::from_bytes(b"not a pdf file at all".to_vec());
assert!(result.is_err());
}
#[test]
fn test_from_bytes_truncated_header() {
let result = PdfDocument::from_bytes(b"%PDF".to_vec());
assert!(result.is_err());
}
#[test]
fn test_open_nonexistent_file() {
let result = PdfDocument::open("/nonexistent/path/fakefile.pdf");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::Io(_)));
}
#[test]
fn test_open_from_file() {
let pdf = build_minimal_pdf(Some("File test"));
let path = write_temp_pdf(&pdf, "test_open_file.pdf");
let mut doc = PdfDocument::open(&path).unwrap();
assert_eq!(doc.page_count().unwrap(), 1);
let _ = std::fs::remove_file(&path);
}
#[test]
fn test_version_1_4() {
let mut pdf = build_minimal_pdf(None);
pdf[5] = b'1';
pdf[7] = b'4';
let doc = PdfDocument::from_bytes(pdf).expect("Should open 1.4 PDF");
assert_eq!(doc.version(), (1, 4));
}
#[test]
fn test_version_2_0() {
let mut pdf = build_minimal_pdf(None);
pdf[5] = b'2';
pdf[7] = b'0';
let doc = PdfDocument::from_bytes(pdf).expect("Should open 2.0 PDF");
assert_eq!(doc.version(), (2, 0));
}
#[test]
fn test_catalog_access() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let catalog = doc.catalog().unwrap();
let dict = catalog.as_dict().unwrap();
assert!(dict.contains_key("Type"));
}
#[test]
fn test_trailer_access() {
let pdf = build_minimal_pdf(None);
let doc = PdfDocument::from_bytes(pdf).unwrap();
let trailer = doc.trailer();
let dict = trailer.as_dict().unwrap();
assert!(dict.contains_key("Size"));
assert!(dict.contains_key("Root"));
}
#[test]
fn test_multi_page_count() {
let pdf = build_multi_page_pdf(5);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
assert_eq!(doc.page_count().unwrap(), 5);
}
#[test]
fn test_multi_page_extract_text() {
let pdf = build_multi_page_pdf(3);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text0 = doc.extract_text(0).unwrap();
assert!(text0.contains("Page 1"));
let text1 = doc.extract_text(1).unwrap();
assert!(text1.contains("Page 2"));
let text2 = doc.extract_text(2).unwrap();
assert!(text2.contains("Page 3"));
}
#[test]
fn test_extract_all_text() {
let pdf = build_multi_page_pdf(3);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let all_text = doc.extract_all_text().unwrap();
assert!(all_text.contains("Page 1"));
assert!(all_text.contains("Page 2"));
assert!(all_text.contains("Page 3"));
}
#[test]
fn test_extract_text_invalid_page() {
let pdf = build_minimal_pdf(Some("Hello"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let result = doc.extract_text(99);
assert!(result.is_err());
}
#[test]
fn test_extract_spans_basic() {
let pdf = build_minimal_pdf(Some("Test spans"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let spans = doc.extract_spans(0).unwrap();
let has_text = spans.iter().any(|s| !s.text.is_empty());
assert!(has_text, "Should have spans with text");
}
#[test]
fn test_extract_spans_empty_page() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let spans = doc.extract_spans(0).unwrap();
assert!(spans.is_empty() || spans.iter().all(|s| s.text.trim().is_empty()));
}
#[test]
fn test_extract_spans_invalid_page() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let result = doc.extract_spans(100);
assert!(result.is_err());
}
#[test]
fn test_to_markdown_basic() {
let pdf = build_minimal_pdf(Some("Markdown test"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let md = doc
.to_markdown(0, &pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = md;
}
#[test]
fn test_to_html_basic() {
let pdf = build_minimal_pdf(Some("HTML test"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let html = doc
.to_html(0, &pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = html;
}
#[test]
fn test_to_plain_text_basic() {
let pdf = build_minimal_pdf(Some("Plain text test"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc
.to_plain_text(0, &pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = text;
}
#[test]
fn test_to_markdown_all() {
let pdf = build_multi_page_pdf(2);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let md = doc
.to_markdown_all(&pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = md;
}
#[test]
fn test_to_plain_text_all() {
let pdf = build_multi_page_pdf(2);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc
.to_plain_text_all(&pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = text;
}
#[test]
fn test_to_html_all() {
let pdf = build_multi_page_pdf(2);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let html = doc
.to_html_all(&pdf_oxide::converters::ConversionOptions::default())
.unwrap();
let _ = html;
}
#[test]
fn test_extract_images_no_images() {
let pdf = build_minimal_pdf(Some("No images here"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let images = doc.extract_images(0).unwrap();
assert!(images.is_empty());
}
#[test]
fn test_extract_images_empty_page() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let images = doc.extract_images(0).unwrap();
assert!(images.is_empty());
}
#[test]
fn test_extract_images_invalid_page() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let result = doc.extract_images(42);
assert!(result.is_err());
}
#[test]
fn test_get_annotations_no_annotations() {
let pdf = build_minimal_pdf(Some("No annotations"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let annotations = doc.get_annotations(0).unwrap();
assert!(annotations.is_empty());
}
#[test]
fn test_debug_display() {
let pdf = build_minimal_pdf(None);
let doc = PdfDocument::from_bytes(pdf).unwrap();
let debug_str = format!("{:?}", doc);
assert!(debug_str.contains("PdfDocument"));
assert!(debug_str.contains("version"));
}
#[test]
fn test_check_circular_references() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let cycles = doc.check_for_circular_references();
let _ = cycles;
}
#[test]
fn test_may_contain_text_public() {
assert!(PdfDocument::may_contain_text_public(b"BT /F1 12 Tf (Hello) Tj ET"));
assert!(PdfDocument::may_contain_text_public(b"/X0 Do"));
assert!(!PdfDocument::may_contain_text_public(b"q 1 0 0 1 0 0 cm Q"));
}
#[test]
fn test_content_stream_with_tj_operator() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"BT /F1 12 Tf 72 720 Td (Hello) Tj 0 -14 Td (World) Tj ET";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Hello"));
assert!(text.contains("World"));
}
#[test]
fn test_content_stream_with_tj_array() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"BT /F1 12 Tf 72 720 Td [(Hello) -100 (World)] TJ ET";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Hello"));
}
#[test]
fn test_content_stream_with_quote_operator() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"BT /F1 12 Tf 14 TL 72 720 Td (Line1) Tj (Line2) ' ET";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Line1"));
}
#[test]
fn test_content_stream_with_text_state_operators() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"BT /F1 12 Tf 1 Tc 2 Tw 100 Tz 14 TL 0 Tr 3 Ts 72 720 Td (Styled) Tj ET";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Styled"));
}
#[test]
fn test_content_stream_with_graphics_state() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"q 1 0 0 1 72 720 cm BT /F1 12 Tf 0 0 Td (Transformed) Tj ET Q";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Transformed"));
}
#[test]
fn test_content_stream_with_color_operators() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n",
);
let off4 = pdf.len();
let content =
b"BT /F1 12 Tf 1 0 0 rg 0 0 0 RG 0.5 g 0.3 G 0 1 0 1 k 1 0 1 0 K 72 720 Td (Colored) Tj ET";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
let off5 = pdf.len();
pdf.extend_from_slice(
b"5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n",
);
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4, off5]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let text = doc.extract_text(0).unwrap();
assert!(text.contains("Colored"));
}
#[test]
fn test_fixture_simple_pdf() {
let mut doc = PdfDocument::open("tests/fixtures/simple.pdf").unwrap();
let _ = doc.page_count().unwrap();
let _ = doc.version();
}
#[test]
fn test_fixture_outline_pdf() {
let mut doc = PdfDocument::open("tests/fixtures/outline.pdf").unwrap();
let count = doc.page_count().unwrap();
assert!(count >= 1);
for i in 0..count {
let _ = doc.extract_text(i);
}
}
#[test]
fn test_fixture_simple_pdf_format_conversions() {
let mut doc = PdfDocument::open("tests/fixtures/simple.pdf").unwrap();
let opts = pdf_oxide::converters::ConversionOptions::default();
let _ = doc.to_markdown(0, &opts);
let _ = doc.to_html(0, &opts);
let _ = doc.to_plain_text(0, &opts);
}
#[test]
fn test_extract_chars_basic() {
let pdf = build_minimal_pdf(Some("Char test"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let chars = doc.extract_chars(0).unwrap();
let _ = chars;
}
#[test]
fn test_get_page_content_data() {
let pdf = build_minimal_pdf(Some("Content data"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let data = doc.get_page_content_data(0).unwrap();
assert!(!data.is_empty(), "Content stream should not be empty");
let content_str = String::from_utf8_lossy(&data);
assert!(content_str.contains("BT") || content_str.contains("Tj"));
}
#[test]
fn test_extract_paths_no_paths() {
let pdf = build_minimal_pdf(Some("No paths"));
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let paths = doc.extract_paths(0).unwrap();
assert!(paths.is_empty());
}
#[test]
fn test_extract_paths_with_rect() {
let mut pdf = b"%PDF-1.7\n".to_vec();
let off1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let off2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
let off3 = pdf.len();
pdf.extend_from_slice(
b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >>\nendobj\n",
);
let off4 = pdf.len();
let content = b"q 1 0 0 1 0 0 cm 0 0 0 rg 100 100 200 150 re f Q";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >>\nstream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"\nendstream\nendobj\n");
finalize_pdf(&mut pdf, &[0, off1, off2, off3, off4]);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let paths = doc.extract_paths(0).unwrap();
let _ = paths;
}
#[test]
fn test_authenticate_unencrypted_pdf() {
let pdf = build_minimal_pdf(None);
let doc = PdfDocument::from_bytes(pdf).unwrap();
let result = doc.authenticate(b"");
let _ = result;
}
#[test]
fn test_structure_tree_untagged() {
let pdf = build_minimal_pdf(None);
let mut doc = PdfDocument::from_bytes(pdf).unwrap();
let tree = doc.structure_tree().unwrap();
assert!(tree.is_none(), "Minimal PDF should not have a structure tree");
}