mod error;
mod json;
mod native;
mod options;
pub use error::{Error, ErrorKind, Result};
pub use options::{
Accessibility, Conformance, ImageFormat, ImageOptions, Margin, MarkdownOptions, MergeOptions,
PageCountOptions, PageSize, Pages, PdfAOptions, PdfOptions, TextOptions,
};
use std::path::{Path, PathBuf};
pub fn library_version() -> Result<String> {
native::library_version()
}
pub fn library_path() -> Result<PathBuf> {
native::library_path()
}
pub fn html_to_pdf(html: &str, options: &PdfOptions) -> Result<Vec<u8>> {
native::invoke("html_to_pdf", html.as_bytes(), &options.to_json()?)
}
pub fn html_file_to_pdf(path: impl AsRef<Path>, options: &PdfOptions) -> Result<Vec<u8>> {
let (text, options) = read_document(path.as_ref(), options)?;
html_to_pdf(&text, &options)
}
pub fn markdown_to_pdf(markdown: &str, options: &PdfOptions) -> Result<Vec<u8>> {
native::invoke("markdown_to_pdf", markdown.as_bytes(), &options.to_json()?)
}
pub fn markdown_file_to_pdf(path: impl AsRef<Path>, options: &PdfOptions) -> Result<Vec<u8>> {
let (text, options) = read_document(path.as_ref(), options)?;
markdown_to_pdf(&text, &options)
}
pub fn pdf_to_images(pdf: &[u8], options: &ImageOptions) -> Result<Vec<Vec<u8>>> {
native::unframe(&native::invoke("pdf_to_images", pdf, &options.to_json()?)?)
}
pub fn pdf_page_count(pdf: &[u8], options: &PageCountOptions) -> Result<usize> {
let raw = native::invoke("pdf_page_count", pdf, &options.to_json()?)?;
std::str::from_utf8(&raw)
.ok()
.and_then(|s| s.trim().parse().ok())
.ok_or_else(|| {
Error::new(
ErrorKind::Internal,
"the native library returned no page count",
)
})
}
pub fn pdf_to_text_pages(pdf: &[u8], options: &TextOptions) -> Result<Vec<String>> {
native::unframe(&native::invoke("pdf_to_text", pdf, &options.to_json()?)?)?
.into_iter()
.map(|page| {
String::from_utf8(page)
.map_err(|_| Error::new(ErrorKind::Internal, "text is not UTF-8"))
})
.collect()
}
pub fn pdf_to_text(pdf: &[u8], options: &TextOptions) -> Result<String> {
Ok(pdf_to_text_pages(pdf, options)?.join("\u{c}"))
}
pub fn pdf_to_markdown(pdf: &[u8], options: &MarkdownOptions) -> Result<String> {
String::from_utf8(native::invoke("pdf_to_markdown", pdf, &options.to_json()?)?)
.map_err(|_| Error::new(ErrorKind::Internal, "Markdown is not UTF-8"))
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PdfAResult {
pub pdf: Vec<u8>,
pub changes: Vec<String>,
pub problems: Vec<String>,
}
impl PdfAResult {
pub fn is_conformant(&self) -> bool {
self.problems.is_empty()
}
}
pub fn pdf_to_pdfa(pdf: &[u8], options: &PdfAOptions) -> Result<PdfAResult> {
let mut parts =
native::unframe(&native::invoke("pdf_to_pdfa", pdf, &options.to_json()?)?)?.into_iter();
let (Some(pdf), Some(report), None) = (parts.next(), parts.next(), parts.next()) else {
return Err(Error::new(
ErrorKind::Internal,
"the native library returned a malformed PDF/A result",
));
};
let report = String::from_utf8(report)
.map_err(|_| Error::new(ErrorKind::Internal, "the report is not UTF-8"))?;
Ok(PdfAResult {
pdf,
changes: json::string_list(&report, "changes")?,
problems: json::string_list(&report, "problems")?,
})
}
pub fn merge_pdfs<P: AsRef<[u8]>>(pdfs: &[P], options: &MergeOptions) -> Result<Vec<u8>> {
native::invoke("merge_pdfs", &native::frame(pdfs)?, &options.to_json()?)
}
fn read_document(path: &Path, options: &PdfOptions) -> Result<(String, PdfOptions)> {
let text = std::fs::read_to_string(path)
.map_err(|e| Error::new(ErrorKind::Io, format!("{}: {e}", path.display())))?;
let mut options = options.clone();
if !options.has_base_dir() {
let absolute = std::path::absolute(path)
.map_err(|e| Error::new(ErrorKind::Io, format!("{}: {e}", path.display())))?;
if let Some(dir) = absolute.parent() {
options = options.base_dir(dir);
}
}
Ok((text, options))
}