use std::path::Path;
use anyhow::{Context, Result};
use rayon::prelude::*;
#[derive(Debug, Clone)]
pub(crate) struct PageText {
pub page: u32,
pub text: String,
}
impl PageText {
pub(crate) fn needs_vision(&self) -> bool {
self.text.trim().is_empty()
}
}
pub(crate) fn page_count(path: &Path) -> Result<u32> {
let document = lopdf::Document::load(path)
.with_context(|| format!("cannot parse {} as a PDF", path.display()))?;
Ok(document.get_pages().len() as u32)
}
pub(crate) fn extract_pages(
path: &Path,
pages: &[u32],
max_decompressed_bytes: usize,
) -> Result<Vec<PageText>> {
let document = lopdf::Document::load(path)
.with_context(|| format!("cannot parse {} as a PDF", path.display()))?;
let total = document.get_pages().len() as u32;
if let Some(outside) = pages.iter().find(|page| **page == 0 || **page > total) {
anyhow::bail!(
"page {outside} is outside {} (the document has {total} pages)",
path.display()
);
}
let extracted: Vec<Result<PageText>> = pages
.par_iter()
.map(|page| {
let chunks = document.extract_text_chunks_with_limit(&[*page], max_decompressed_bytes);
let mut text = String::new();
for chunk in chunks {
let chunk = chunk.with_context(|| {
format!("cannot decompress page {page} of {}", path.display())
})?;
text.push_str(&chunk);
}
Ok(PageText { page: *page, text })
})
.collect();
extracted.into_iter().collect()
}
pub(crate) fn page_range(first: u32, last: u32, total: u32) -> Vec<u32> {
let low = first.max(1).min(total.max(1));
let high = last.max(1).min(total.max(1)).max(low);
(low..=high).collect()
}