use std::path::Path;
use firecrawl_pdfium::{PagePoint, PageRect, Pdfium, PixelRect, RenderConfig};
const TRUTH: PageRect = PageRect {
left: 50.0,
bottom: 25.0,
right: 150.0,
top: 75.0,
};
fn main() {
if let Err(err) = run() {
eprintln!("error: {err}");
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/red_rect.pdf");
let pdfium = Pdfium::load()?;
let bytes =
std::fs::read(&fixture).map_err(|e| format!("cannot read {}: {e}", fixture.display()))?;
let doc = pdfium.load_document(bytes, None)?;
let page = doc.page(0)?;
println!("page: {} x {} pt", page.width(), page.height());
let rendered = page.render(&RenderConfig::new().scale(2.0))?;
println!(
"rendered at scale 2: {} x {} px\n",
rendered.width(),
rendered.height()
);
let (mut min_x, mut min_y, mut max_x, mut max_y) = (u32::MAX, u32::MAX, 0u32, 0u32);
for y in 0..rendered.height() {
for (x, px) in rendered.row(y).chunks_exact(4).enumerate() {
let is_red = px[2] > 200 && px[1] < 60 && px[0] < 60;
if is_red {
let x = x as u32;
min_x = min_x.min(x);
max_x = max_x.max(x);
min_y = min_y.min(y);
max_y = max_y.max(y);
}
}
}
if min_x > max_x {
return Err("no red pixels found in the render; fixture geometry has changed".into());
}
println!("pixel bounding box of the red region (inclusive pixel indices):");
println!(" x: {min_x}..={max_x}, y: {min_y}..={max_y}");
let pixel_rect = PixelRect::new(
f64::from(min_x),
f64::from(min_y),
f64::from(max_x - min_x + 1),
f64::from(max_y - min_y + 1),
);
let measured = rendered.transform().pixel_rect_to_page(pixel_rect);
println!("mapped back to page space (points, origin bottom-left, y-up):");
println!(
" ({:.2}, {:.2}) - ({:.2}, {:.2})",
measured.left, measured.bottom, measured.right, measured.top
);
println!(
"known ground truth from the fixture: ({:.0}, {:.0}) - ({:.0}, {:.0})\n",
TRUTH.left, TRUTH.bottom, TRUTH.right, TRUTH.top
);
for (name, got, want) in [
("left", measured.left, TRUTH.left),
("bottom", measured.bottom, TRUTH.bottom),
("right", measured.right, TRUTH.right),
("top", measured.top, TRUTH.top),
] {
if (got - want).abs() > 1.0 {
return Err(format!(
"{name} edge off by more than 1 pt: measured {got:.3}, expected {want:.3}"
)
.into());
}
}
println!("round trip agrees with the ground truth within 1 pt\n");
let t = rendered.transform();
let top_left = t.page_to_pixel(PagePoint::new(TRUTH.left, TRUTH.top));
let bottom_right = t.page_to_pixel(PagePoint::new(TRUTH.right, TRUTH.bottom));
println!("projecting the rectangle's page corners into this render:");
println!(
" page ({:.0}, {:.0}) -> pixel ({:.1}, {:.1})",
TRUTH.left, TRUTH.top, top_left.x, top_left.y
);
println!(
" page ({:.0}, {:.0}) -> pixel ({:.1}, {:.1})\n",
TRUTH.right, TRUTH.bottom, bottom_right.x, bottom_right.y
);
let plan = page.transform_for(&RenderConfig::new().dpi(300.0))?;
println!(
"hypothetical 300 DPI render (no pixels produced): {} x {} px",
plan.pixel_width(),
plan.pixel_height()
);
let projected = plan.page_rect_to_pixel(TRUTH);
println!(
" the rectangle would occupy x {:.1}..{:.1}, y {:.1}..{:.1}",
projected.x,
projected.x + projected.width,
projected.y,
projected.y + projected.height
);
Ok(())
}