use rasgapdf::*;
#[test]
fn test_bounding_box_rotation_transformations() {
let pw = 600.0;
let ph = 800.0;
let original = BoundingBox::new(10.0, 20.0, 100.0, 60.0);
assert_eq!(original.width, 90.0);
assert_eq!(original.height, 40.0);
let rot0 = original.rotate(0, pw, ph);
assert_eq!((rot0.x0, rot0.y0, rot0.x1, rot0.y1), (10.0, 20.0, 100.0, 60.0));
let rot90 = original.rotate(90, pw, ph);
assert_eq!((rot90.x0, rot90.y0, rot90.x1, rot90.y1), (740.0, 10.0, 780.0, 100.0));
assert_eq!(rot90.width, 40.0);
assert_eq!(rot90.height, 90.0);
let rot180 = original.rotate(180, pw, ph);
assert_eq!((rot180.x0, rot180.y0, rot180.x1, rot180.y1), (500.0, 740.0, 590.0, 780.0));
assert_eq!(rot180.width, 90.0);
assert_eq!(rot180.height, 40.0);
let rot270 = original.rotate(270, pw, ph);
assert_eq!((rot270.x0, rot270.y0, rot270.x1, rot270.y1), (20.0, 500.0, 60.0, 590.0));
assert_eq!(rot270.width, 40.0);
assert_eq!(rot270.height, 90.0);
}
#[test]
fn test_spatial_chunks_preserve_all_constituent_bboxes() {
let block1 = SpatialBlock {
block_id: 1,
block_type: "text".to_string(),
bbox: BoundingBox::new(50.0, 100.0, 550.0, 180.0),
lines: vec![
SpatialLine {
text: "Introduction to Spatial Retrieval".to_string(),
bbox: BoundingBox::new(50.0, 100.0, 350.0, 130.0),
spans: vec![],
},
SpatialLine {
text: "Flat text loses visual and coordinate bounding boxes.".to_string(),
bbox: BoundingBox::new(50.0, 140.0, 550.0, 180.0),
spans: vec![],
},
],
};
let block2 = SpatialBlock {
block_id: 2,
block_type: "text".to_string(),
bbox: BoundingBox::new(50.0, 200.0, 550.0, 260.0),
lines: vec![
SpatialLine {
text: "By keeping the bounding box array intact,".to_string(),
bbox: BoundingBox::new(50.0, 200.0, 400.0, 225.0),
spans: vec![],
},
SpatialLine {
text: "downstream viewers can highlight multiple rectangles.".to_string(),
bbox: BoundingBox::new(50.0, 230.0, 550.0, 260.0),
spans: vec![],
},
],
};
let page = SpatialPage {
page_number: 1,
width: 612.0,
height: 792.0,
rotation: 0,
text_blocks: vec![block1, block2],
tables: vec![],
raw_span_count: 4,
images: vec![],
drawings: vec![],
media_box: Some([0.0, 0.0, 612.0, 792.0]),
crop_box: Some([0.0, 0.0, 612.0, 792.0]),
};
let chunks = page.to_spatial_chunks(1500, 0);
assert_eq!(chunks.len(), 1, "Should aggregate contiguous blocks into 1 chunk");
assert_eq!(chunks[0].block_count, 2);
assert_eq!(chunks[0].bboxes.len(), 2, "Must preserve individual bounding boxes for each block");
assert_eq!(chunks[0].bboxes[0].y0, 100.0);
assert_eq!(chunks[0].bboxes[1].y0, 200.0);
assert_eq!(chunks[0].overall_bbox.y0, 100.0);
assert_eq!(chunks[0].overall_bbox.y1, 260.0);
assert!(chunks[0].text.contains("Introduction to Spatial Retrieval"));
assert!(chunks[0].text.contains("downstream viewers can highlight multiple rectangles"));
}
#[test]
fn test_crop_box_origin_normalization_and_precedence() {
let mut pdf = Vec::new();
pdf.extend_from_slice(b"%PDF-1.4\n");
let o1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let o2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [ 3 0 R ] /Count 1 >>\nendobj\n");
let o3 = pdf.len();
pdf.extend_from_slice(b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [ 0 0 612 792 ] /CropBox [ 36 54 576 756 ] /Contents 4 0 R >>\nendobj\n");
let o4 = pdf.len();
let content = b"BT\n/F1 12 Tf\n36 740 Td\n(TopMarginCroppedText) Tj\nET\n";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >> stream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"endstream\nendobj\n");
let xref_pos = pdf.len();
pdf.extend_from_slice(
format!(
"xref\n0 5\n0000000000 65535 f \n{:010} 00000 n \n{:010} 00000 n \n{:010} 00000 n \n{:010} 00000 n \ntrailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{}\n%%EOF",
o1, o2, o3, o4, xref_pos
)
.as_bytes(),
);
let mut doc = rasgapdf::engine::PurePdfDoc::from_bytes(&pdf).expect("Failed to parse PDF");
let page = doc.load_page(0).expect("Failed to load page");
assert_eq!(page.width, 540.0);
assert_eq!(page.height, 702.0);
assert_eq!(page.origin_x, 36.0);
assert_eq!(page.origin_y, 54.0);
assert_eq!(page.media_box, Some([0.0, 0.0, 612.0, 792.0]));
assert_eq!(page.crop_box, Some([36.0, 54.0, 576.0, 756.0]));
let mut opts = ParserOptions::default();
opts.detect_tables = false;
let sdoc = doc.parse_structured("cropped.pdf", &opts).expect("Structured parse failed");
assert_eq!(sdoc.pages[0].width, 540.0);
assert_eq!(sdoc.pages[0].height, 702.0);
assert_eq!(sdoc.pages[0].crop_box, Some([36.0, 54.0, 576.0, 756.0]));
assert_eq!(sdoc.pages[0].media_box, Some([0.0, 0.0, 612.0, 792.0]));
let span = &sdoc.pages[0].text_blocks[0].lines[0];
assert!((span.bbox.x0 - 0.0).abs() <= 1.0, "Expected x0 near 0, got {}", span.bbox.x0);
}
#[test]
fn test_rotated_page_with_applied_rotation() {
let mut pdf = Vec::new();
pdf.extend_from_slice(b"%PDF-1.4\n");
let o1 = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let o2 = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [ 3 0 R ] /Count 1 >>\nendobj\n");
let o3 = pdf.len();
pdf.extend_from_slice(b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [ 0 0 612 792 ] /Rotate 90 /Contents 4 0 R >>\nendobj\n");
let o4 = pdf.len();
let content = b"BT\n/F1 12 Tf\n100 700 Td\n(Rotated90Text) Tj\nET\n";
pdf.extend_from_slice(format!("4 0 obj\n<< /Length {} >> stream\n", content.len()).as_bytes());
pdf.extend_from_slice(content);
pdf.extend_from_slice(b"endstream\nendobj\n");
let xref_pos = pdf.len();
pdf.extend_from_slice(
format!(
"xref\n0 5\n0000000000 65535 f \n{:010} 00000 n \n{:010} 00000 n \n{:010} 00000 n \n{:010} 00000 n \ntrailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{}\n%%EOF",
o1, o2, o3, o4, xref_pos
)
.as_bytes(),
);
let mut doc = rasgapdf::engine::PurePdfDoc::from_bytes(&pdf).expect("Failed to parse PDF");
let mut opts = ParserOptions::default();
opts.detect_tables = false;
let raw_parsed = doc.parse_structured("rot.pdf", &opts).unwrap();
assert_eq!(raw_parsed.pages[0].rotation, 90);
assert_eq!(raw_parsed.pages[0].width, 612.0);
assert_eq!(raw_parsed.pages[0].height, 792.0);
opts.apply_rotation = true;
let mut doc2 = rasgapdf::engine::PurePdfDoc::from_bytes(&pdf).expect("Failed to parse PDF");
let rotated_doc = doc2.parse_structured("rot.pdf", &opts).unwrap();
assert_eq!(rotated_doc.pages[0].width, 792.0);
assert_eq!(rotated_doc.pages[0].height, 612.0);
assert_eq!(rotated_doc.pages[0].rotation, 0);
let rotated_block = &rotated_doc.pages[0].text_blocks[0];
assert!(rotated_block.bbox.x0 >= 0.0 && rotated_block.bbox.x1 <= 792.0);
assert!(rotated_block.bbox.y0 >= 0.0 && rotated_block.bbox.y1 <= 612.0);
}