use std::path::PathBuf;
use std::process::Command;
fn recon_bin() -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.push("target");
p.push("release");
p.push("recon");
p
}
fn pdftoppm_present() -> bool {
Command::new("pdftoppm")
.arg("-v")
.output()
.map(|o| o.status.success() || !o.stderr.is_empty())
.unwrap_or(false)
}
fn run_export(args: &[&str]) -> (bool, String, String) {
let out = Command::new(recon_bin()).args(args).output().expect("spawn recon");
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
const MAGIC_PNG: &[u8] = &[0x89, 0x50, 0x4E, 0x47];
const MAGIC_JPEG: &[u8] = &[0xFF, 0xD8, 0xFF];
fn assert_magic(path: &PathBuf, magic: &[u8]) {
let bytes = std::fs::read(path).expect("read output");
assert!(
bytes.starts_with(magic),
"magic mismatch for {}: got {:02x?}",
path.display(),
&bytes[..magic.len().min(bytes.len())]
);
}
fn assert_webp(path: &PathBuf) {
let bytes = std::fs::read(path).expect("read output");
assert_eq!(&bytes[0..4], b"RIFF", "RIFF header missing");
assert_eq!(&bytes[8..12], b"WEBP", "WEBP signature missing");
}
fn assert_png_has_content(path: &PathBuf) {
let bytes = std::fs::read(path).expect("read png");
let decoder = png::Decoder::new(bytes.as_slice());
let mut reader = decoder.read_info().expect("png header");
let mut buf = vec![0u8; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).expect("png frame");
let channels = match info.color_type {
png::ColorType::Rgb => 3,
png::ColorType::Rgba => 4,
png::ColorType::Grayscale => 1,
png::ColorType::GrayscaleAlpha => 2,
other => panic!("unexpected color type: {other:?}"),
};
let stride = (info.width as usize) * channels;
let mut min = u8::MAX;
let mut max = u8::MIN;
let step = 8usize;
let mut y = 0usize;
while y < info.height as usize {
let row = y * stride;
let mut x = 0usize;
while x < info.width as usize {
let v = buf[row + x * channels];
if v < min {
min = v;
}
if v > max {
max = v;
}
x += step;
}
y += step;
}
let range = max as i32 - min as i32;
assert!(
range > 32,
"rendered PNG looks like a flat color (sample range {min}..={max}); \
likely a renderer failure"
);
}
#[test]
fn export_png_default() {
if !pdftoppm_present() {
eprintln!("SKIP: pdftoppm not on PATH");
return;
}
let out = std::env::temp_dir().join("recon-it-page.png");
let _ = std::fs::remove_file(&out);
let out_str = out.to_str().unwrap();
let (ok, _stdout, stderr) = run_export(&[
"--export-pdf-page", "1", "docs/MANUAL.pdf",
"-o", out_str,
]);
assert!(ok, "non-zero exit; stderr: {stderr}");
assert_magic(&out, MAGIC_PNG);
assert_png_has_content(&out);
std::fs::remove_file(&out).ok();
}
#[test]
fn export_jpeg_quality() {
if !pdftoppm_present() {
eprintln!("SKIP: pdftoppm not on PATH");
return;
}
let out = std::env::temp_dir().join("recon-it-page.jpg");
let _ = std::fs::remove_file(&out);
let out_str = out.to_str().unwrap();
let (ok, _stdout, stderr) = run_export(&[
"--export-pdf-page", "2", "docs/MANUAL.pdf",
"-o", out_str,
"--pdf-quality", "60",
]);
assert!(ok, "non-zero exit; stderr: {stderr}");
assert_magic(&out, MAGIC_JPEG);
std::fs::remove_file(&out).ok();
}
#[test]
fn export_webp_transcode() {
if !pdftoppm_present() {
eprintln!("SKIP: pdftoppm not on PATH");
return;
}
let out = std::env::temp_dir().join("recon-it-page.webp");
let _ = std::fs::remove_file(&out);
let out_str = out.to_str().unwrap();
let (ok, _stdout, stderr) = run_export(&[
"--export-pdf-page", "1", "docs/MANUAL.pdf",
"-o", out_str,
]);
assert!(ok, "non-zero exit; stderr: {stderr}");
assert_webp(&out);
std::fs::remove_file(&out).ok();
}
#[test]
fn export_page_out_of_range() {
if !pdftoppm_present() {
eprintln!("SKIP: pdftoppm not on PATH");
return;
}
let out = std::env::temp_dir().join("recon-it-oor.png");
let _ = std::fs::remove_file(&out);
let out_str = out.to_str().unwrap();
let (ok, _stdout, stderr) = run_export(&[
"--export-pdf-page", "99999", "docs/MANUAL.pdf",
"-o", out_str,
]);
assert!(!ok, "expected non-zero exit for out-of-range page");
assert!(
stderr.contains("out of range"),
"expected 'out of range' in stderr, got: {stderr}"
);
assert!(!out.exists(), "file should not exist for failed render");
}