use anyhow::{Context, Result};
use regex::Regex;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
pub fn page_count(path: &Path) -> Result<usize> {
let output = Command::new("pdfinfo")
.arg(path)
.output()
.map_err(|err| {
if err.kind() == io::ErrorKind::NotFound {
anyhow::anyhow!(
"pdfinfo not found. Please install Poppler (macOS: brew install poppler)."
)
} else {
err.into()
}
})
.with_context(|| "Failed to run pdfinfo")?;
if !output.status.success() {
anyhow::bail!(
"pdfinfo failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let re = Regex::new(r"(?m)^Pages:\s+(\d+)\s*$")?;
let caps = re
.captures(&stdout)
.context("Unable to parse page count from pdfinfo output")?;
let pages: usize = caps
.get(1)
.context("Missing page count capture")?
.as_str()
.parse()?;
Ok(pages)
}
pub fn render_page_to_png(
pdf: &Path,
page: usize,
dpi: u32,
temp_dir: &TempDir,
) -> Result<PathBuf> {
let prefix = temp_dir.path().join(format!("page_{page}"));
let status = Command::new("pdftoppm")
.arg("-r")
.arg(dpi.to_string())
.arg("-f")
.arg(page.to_string())
.arg("-l")
.arg(page.to_string())
.arg("-png")
.arg("-singlefile")
.arg(pdf)
.arg(&prefix)
.status()
.map_err(|err| {
if err.kind() == io::ErrorKind::NotFound {
anyhow::anyhow!(
"pdftoppm not found. Please install Poppler (macOS: brew install poppler)."
)
} else {
err.into()
}
})
.with_context(|| "Failed to run pdftoppm")?;
if !status.success() {
anyhow::bail!("pdftoppm failed for page {}", page);
}
let image_path = prefix.with_extension("png");
if !image_path.exists() {
anyhow::bail!("pdftoppm did not produce {}", image_path.display());
}
Ok(image_path)
}
pub fn extract_page_text(pdf: &Path, page: usize) -> Result<Option<String>> {
let output = Command::new("pdftotext")
.arg("-f")
.arg(page.to_string())
.arg("-l")
.arg(page.to_string())
.arg("-layout")
.arg(pdf)
.arg("-")
.output()
.map_err(|err| {
if err.kind() == io::ErrorKind::NotFound {
anyhow::anyhow!(
"pdftotext not found. Please install Poppler (macOS: brew install poppler)."
)
} else {
err.into()
}
})
.with_context(|| "Failed to run pdftotext")?;
if !output.status.success() {
anyhow::bail!(
"pdftotext failed for page {}: {}",
page,
String::from_utf8_lossy(&output.stderr)
);
}
let text = String::from_utf8_lossy(&output.stdout).to_string();
if text.trim().is_empty() {
return Ok(None);
}
Ok(Some(text))
}