use std::path::PathBuf;
use lopdf::{Document, Error as LopdfError};
use thiserror::Error;
use super::file::FileLoaderError;
#[derive(Error, Debug)]
pub enum PdfLoaderError {
#[error("{0}")]
FileLoaderError(#[from] FileLoaderError),
#[error("UTF-8 conversion error: {0}")]
FromUtf8Error(#[from] std::string::FromUtf8Error),
#[error("IO error: {0}")]
PdfError(#[from] LopdfError),
}
loadable_trait!(Loadable, PdfLoaderError, Document, load, load_with_path);
impl Loadable for PathBuf {
fn load(self) -> Result<Document, PdfLoaderError> {
Document::load(self).map_err(PdfLoaderError::PdfError)
}
fn load_with_path(self) -> Result<(PathBuf, Document), PdfLoaderError> {
let contents = Document::load(&self);
Ok((self, contents?))
}
}
impl Loadable for Vec<u8> {
fn load(self) -> Result<Document, PdfLoaderError> {
Document::load_mem(&self).map_err(PdfLoaderError::PdfError)
}
fn load_with_path(self) -> Result<(PathBuf, Document), PdfLoaderError> {
let doc = Document::load_mem(&self).map_err(PdfLoaderError::PdfError)?;
Ok((PathBuf::from("<memory>"), doc))
}
}
pub struct PdfFileLoader<'a, T> {
iterator: Box<dyn Iterator<Item = T> + 'a>,
}
#[allow(private_bounds)] impl<'a, T: Loadable + 'a> PdfFileLoader<'a, T> {
pub fn load(self) -> PdfFileLoader<'a, Result<Document, PdfLoaderError>> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(Loadable::load)),
}
}
pub fn load_with_path(self) -> PdfFileLoader<'a, Result<(PathBuf, Document), PdfLoaderError>> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(Loadable::load_with_path)),
}
}
}
fn page_texts(doc: &Document) -> Vec<(usize, Result<String, PdfLoaderError>)> {
doc.page_iter()
.enumerate()
.map(|(page_no, _)| {
(
page_no,
doc.extract_text(&[page_no as u32 + 1])
.map_err(PdfLoaderError::PdfError),
)
})
.collect()
}
fn all_text(doc: &Document) -> Result<String, PdfLoaderError> {
page_texts(doc).into_iter().map(|(_, text)| text).collect()
}
#[allow(private_bounds)] impl<'a, T: Loadable + 'a> PdfFileLoader<'a, T> {
pub fn read(self) -> PdfFileLoader<'a, Result<String, PdfLoaderError>> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(|res| all_text(&res.load()?))),
}
}
pub fn read_with_path(self) -> PdfFileLoader<'a, Result<(PathBuf, String), PdfLoaderError>> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(|res| {
let (path, doc) = res.load_with_path()?;
let content = all_text(&doc)?;
Ok((path, content))
})),
}
}
}
impl<'a> PdfFileLoader<'a, Document> {
pub fn by_page(self) -> PdfFileLoader<'a, Result<String, PdfLoaderError>> {
PdfFileLoader {
iterator: Box::new(
self.iterator
.flat_map(|doc| page_texts(&doc).into_iter().map(|(_, text)| text)),
),
}
}
}
type ByPage = (PathBuf, Vec<(usize, Result<String, PdfLoaderError>)>);
impl<'a> PdfFileLoader<'a, (PathBuf, Document)> {
pub fn by_page(self) -> PdfFileLoader<'a, ByPage> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(|(path, doc)| (path, page_texts(&doc)))),
}
}
}
impl<'a> PdfFileLoader<'a, ByPage> {
pub fn ignore_errors(self) -> PdfFileLoader<'a, (PathBuf, Vec<(usize, String)>)> {
PdfFileLoader {
iterator: Box::new(self.iterator.map(|(path, pages)| {
let pages = pages
.into_iter()
.filter_map(|(page_no, res)| res.ok().map(|content| (page_no, content)))
.collect::<Vec<_>>();
(path, pages)
})),
}
}
}
loader_scaffold!(PdfFileLoader, PdfLoaderError, dir: all_entries);
loader_from_bytes!(PdfFileLoader);
#[cfg(test)]
mod tests;