use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DocumentInfo {
pub pages: u32,
pub has_text_layer: bool,
}
pub fn inspect(path: &Path) -> anyhow::Result<DocumentInfo> {
let doc = lopdf::Document::load(path)
.map_err(|e| anyhow::anyhow!("reading {}: {e}", path.display()))?;
let pages = doc.get_pages().len() as u32;
let has_text_layer = pdf_extract::extract_text(path)
.map(|text| text.chars().any(|c| c.is_alphanumeric()))
.unwrap_or(false);
Ok(DocumentInfo {
pages,
has_text_layer,
})
}
pub fn page_text(path: &Path, page: u32) -> anyhow::Result<String> {
let doc = lopdf::Document::load(path)
.map_err(|e| anyhow::anyhow!("reading {}: {e}", path.display()))?;
let count = doc.get_pages().len() as u32;
if page >= count {
anyhow::bail!("page {page} is out of range; the document has {count}");
}
let text = pdf_extract::extract_text(path)
.map_err(|e| anyhow::anyhow!("extracting text from {}: {e}", path.display()))?;
let pages: Vec<&str> = text.split('\u{c}').collect();
match pages.get(page as usize) {
Some(found) => Ok(found.to_string()),
None if page == 0 => Ok(text),
None => anyhow::bail!(
"page {page} exists but no text could be extracted for it; \
the page may be scanned — check `has_text_layer` and render it instead"
),
}
}
#[cfg(feature = "pdf-render")]
pub fn render_page(path: &Path, page: u32, width: u16) -> anyhow::Result<Vec<u8>> {
crate::render_worker::render_page(path, page, width)
}
#[cfg(feature = "pdf-render")]
pub(crate) fn render_page_in_process(
path: &Path,
page: u32,
width: u16,
) -> anyhow::Result<Vec<u8>> {
use pdfium_render::prelude::*;
let bindings = match std::env::var("PDFIUM_DYNAMIC_LIB_PATH") {
Ok(dir) if !dir.is_empty() => {
Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path(&dir))
.or_else(|_| Pdfium::bind_to_system_library())
}
_ => Pdfium::bind_to_system_library(),
}
.map_err(|e| {
anyhow::anyhow!(
"could not load the pdfium shared library ({e}). It is a runtime \
dependency of the `pdf-render` feature, not a build-time one: \
install pdfium-binaries and set PDFIUM_DYNAMIC_LIB_PATH to its lib \
directory, or use the document's text layer instead."
)
})?;
let pdfium = Pdfium::new(bindings);
let document = pdfium
.load_pdf_from_file(path, None)
.map_err(|e| anyhow::anyhow!("opening {} for rendering: {e}", path.display()))?;
let pages = document.pages();
let count = pages.len();
if page >= count as u32 {
anyhow::bail!("page {page} is out of range; the document has {count}");
}
let target = pages
.get(page as i32)
.map_err(|e| anyhow::anyhow!("reading page {page}: {e}"))?;
let rendered = target
.render_with_config(&PdfRenderConfig::new().set_target_width(i32::from(width)))
.map_err(|e| anyhow::anyhow!("rendering page {page}: {e}"))?;
let mut png = std::io::Cursor::new(Vec::new());
rendered
.as_image()
.map_err(|e| anyhow::anyhow!("converting page {page} to an image: {e}"))?
.write_to(&mut png, image::ImageFormat::Png)
.map_err(|e| anyhow::anyhow!("encoding page {page} as PNG: {e}"))?;
Ok(png.into_inner())
}
#[cfg(not(feature = "pdf-render"))]
pub fn render_page(_path: &Path, _page: u32, _width: u16) -> anyhow::Result<Vec<u8>> {
anyhow::bail!(
"this build cannot render PDF pages to images: rebuild with the \
`pdf-render` feature, or use the document's text layer instead"
)
}