use crate::error::Result;
pub trait PdfModel {
fn render(&self) -> Result<Vec<RenderedElement>>;
fn metadata(&self) -> PdfModelMetadata;
fn field_descriptors(&self) -> Vec<PdfFieldDescriptor> {
Vec::new()
}
}
#[derive(Debug, Clone)]
pub struct PdfFieldDescriptor {
pub field_name: String,
pub rust_field_name: String,
pub order: u32,
pub format: Option<String>,
pub default_value: Option<String>,
pub required: bool,
pub nested: bool,
}
impl PdfFieldDescriptor {
#[must_use]
pub fn new(field_name: impl Into<String>, rust_field_name: impl Into<String>) -> Self {
Self {
field_name: field_name.into(),
rust_field_name: rust_field_name.into(),
order: u32::MAX,
format: None,
default_value: None,
required: false,
nested: false,
}
}
}
#[derive(Debug, Clone)]
pub struct PdfModelMetadata {
pub page_size: crate::enums::PageSize,
pub orientation: crate::enums::Orientation,
pub margins: f64,
}
impl Default for PdfModelMetadata {
fn default() -> Self {
Self {
page_size: crate::enums::PageSize::A4,
orientation: crate::enums::Orientation::default(),
margins: 72.0,
}
}
}
#[derive(Debug, Clone)]
pub enum RenderedElement {
Text {
x: f64,
y: f64,
text: crate::content::PdfText,
},
Table {
x: f64,
y: f64,
table: crate::content::PdfTable,
},
Image {
x: f64,
y: f64,
image: crate::content::PdfImage,
},
}
pub trait PdfReadListener: Send {
fn on_page_start(&mut self, page_number: usize) -> Result<()> {
let _ = page_number;
Ok(())
}
fn on_text(&mut self, page_number: usize, text: &str) -> Result<()>;
fn on_page_end(&mut self, page_number: usize) -> Result<()> {
let _ = page_number;
Ok(())
}
fn on_document_end(&mut self) -> Result<()> {
Ok(())
}
}
pub trait PdfWriteHandler: Send {
fn before_document(&mut self) -> Result<()> {
Ok(())
}
fn before_page(&mut self, page_number: usize) -> Result<()> {
let _ = page_number;
Ok(())
}
fn after_page(&mut self, page_number: usize) -> Result<()> {
let _ = page_number;
Ok(())
}
fn after_document(&mut self) -> Result<()> {
Ok(())
}
}
pub trait PdfConverter<T>: Send {
fn to_pdf_string(&self, value: &T) -> Result<String>;
#[allow(clippy::wrong_self_convention)]
fn from_pdf_string(&self, s: &str) -> Result<T>;
}
impl<T> PdfConverter<T> for Box<dyn PdfConverter<T>> {
fn to_pdf_string(&self, value: &T) -> Result<String> {
(**self).to_pdf_string(value)
}
fn from_pdf_string(&self, s: &str) -> Result<T> {
(**self).from_pdf_string(s)
}
}
pub trait PdfEngine: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> EngineCapabilities;
}
#[derive(Debug, Clone, Copy, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct EngineCapabilities {
pub create: bool,
pub read: bool,
pub manipulate: bool,
pub fill_forms: bool,
pub encrypt: bool,
pub sign: bool,
pub pdfa: bool,
}
impl EngineCapabilities {
#[must_use]
pub const fn lopdf() -> Self {
Self {
create: false,
read: true,
manipulate: true,
fill_forms: true,
encrypt: false,
sign: false,
pdfa: false,
}
}
#[must_use]
pub const fn printpdf() -> Self {
Self {
create: true,
read: false,
manipulate: false,
fill_forms: false,
encrypt: false,
sign: false,
pdfa: false,
}
}
#[must_use]
pub const fn to_detailed(&self) -> DetailedEngineCapabilities {
DetailedEngineCapabilities {
text_extraction: if self.read {
CapabilityLevel::Structural
} else {
CapabilityLevel::None
},
metadata: if self.read {
CapabilityLevel::Structural
} else {
CapabilityLevel::None
},
image_extraction: if self.read {
CapabilityLevel::Heuristic
} else {
CapabilityLevel::None
},
table_detection: if self.read {
CapabilityLevel::Heuristic
} else {
CapabilityLevel::None
},
rendering: if self.create {
CapabilityLevel::Structural
} else {
CapabilityLevel::None
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[non_exhaustive]
pub enum CapabilityLevel {
#[default]
None,
Heuristic,
Structural,
Cloud,
}
impl CapabilityLevel {
#[must_use]
pub const fn is_supported(&self) -> bool {
!matches!(self, Self::None)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DetailedEngineCapabilities {
pub text_extraction: CapabilityLevel,
pub metadata: CapabilityLevel,
pub image_extraction: CapabilityLevel,
pub table_detection: CapabilityLevel,
pub rendering: CapabilityLevel,
}
#[cfg(test)]
#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn capability_level_ordering() {
assert!(CapabilityLevel::None < CapabilityLevel::Heuristic);
assert!(CapabilityLevel::Heuristic < CapabilityLevel::Structural);
assert!(CapabilityLevel::Structural < CapabilityLevel::Cloud);
}
#[test]
fn capability_level_default_is_none() {
assert_eq!(CapabilityLevel::default(), CapabilityLevel::None);
}
#[test]
fn capability_level_is_supported() {
assert!(!CapabilityLevel::None.is_supported());
assert!(CapabilityLevel::Heuristic.is_supported());
assert!(CapabilityLevel::Structural.is_supported());
assert!(CapabilityLevel::Cloud.is_supported());
}
#[test]
fn capability_level_debug() {
assert_eq!(format!("{:?}", CapabilityLevel::Cloud), "Cloud");
}
#[test]
fn detailed_capabilities_default() {
let dc = DetailedEngineCapabilities::default();
assert_eq!(dc.text_extraction, CapabilityLevel::None);
assert_eq!(dc.metadata, CapabilityLevel::None);
assert_eq!(dc.image_extraction, CapabilityLevel::None);
assert_eq!(dc.table_detection, CapabilityLevel::None);
assert_eq!(dc.rendering, CapabilityLevel::None);
}
#[test]
fn engine_capabilities_to_detailed_lopdf() {
let caps = EngineCapabilities::lopdf();
let detailed = caps.to_detailed();
assert_eq!(detailed.text_extraction, CapabilityLevel::Structural);
assert_eq!(detailed.metadata, CapabilityLevel::Structural);
assert_eq!(detailed.image_extraction, CapabilityLevel::Heuristic);
assert_eq!(detailed.table_detection, CapabilityLevel::Heuristic);
assert_eq!(detailed.rendering, CapabilityLevel::None);
}
#[test]
fn engine_capabilities_to_detailed_printpdf() {
let caps = EngineCapabilities::printpdf();
let detailed = caps.to_detailed();
assert_eq!(detailed.text_extraction, CapabilityLevel::None);
assert_eq!(detailed.metadata, CapabilityLevel::None);
assert_eq!(detailed.image_extraction, CapabilityLevel::None);
assert_eq!(detailed.table_detection, CapabilityLevel::None);
assert_eq!(detailed.rendering, CapabilityLevel::Structural);
}
#[test]
fn detailed_capabilities_equality() {
let a = DetailedEngineCapabilities {
text_extraction: CapabilityLevel::Structural,
..Default::default()
};
let b = DetailedEngineCapabilities {
text_extraction: CapabilityLevel::Structural,
..Default::default()
};
assert_eq!(a, b);
}
#[test]
fn detailed_capabilities_inequality() {
let a = DetailedEngineCapabilities {
text_extraction: CapabilityLevel::Structural,
..Default::default()
};
let b = DetailedEngineCapabilities {
text_extraction: CapabilityLevel::Heuristic,
..Default::default()
};
assert_ne!(a, b);
}
#[test]
fn detailed_capabilities_debug() {
let dc = DetailedEngineCapabilities::default();
let dbg = format!("{:?}", dc);
assert!(dbg.contains("DetailedEngineCapabilities"));
}
#[test]
fn detailed_capabilities_clone() {
let dc = DetailedEngineCapabilities {
text_extraction: CapabilityLevel::Cloud,
metadata: CapabilityLevel::Structural,
image_extraction: CapabilityLevel::Heuristic,
table_detection: CapabilityLevel::None,
rendering: CapabilityLevel::Structural,
};
let cloned = dc;
assert_eq!(dc, cloned);
}
#[test]
fn engine_capabilities_default() {
let caps = EngineCapabilities::default();
assert!(!caps.create);
assert!(!caps.read);
assert!(!caps.manipulate);
assert!(!caps.fill_forms);
assert!(!caps.encrypt);
assert!(!caps.sign);
assert!(!caps.pdfa);
}
#[test]
fn engine_capabilities_debug() {
let caps = EngineCapabilities::lopdf();
let dbg = format!("{:?}", caps);
assert!(dbg.contains("EngineCapabilities"));
}
#[test]
fn engine_capabilities_clone() {
let caps = EngineCapabilities::printpdf();
let cloned = caps;
assert_eq!(caps.create, cloned.create);
}
#[test]
fn pdf_field_descriptor_new() {
let desc = PdfFieldDescriptor::new("pdf_name", "rust_name");
assert_eq!(desc.field_name, "pdf_name");
assert_eq!(desc.rust_field_name, "rust_name");
assert_eq!(desc.order, u32::MAX);
assert!(desc.format.is_none());
assert!(desc.default_value.is_none());
assert!(!desc.required);
assert!(!desc.nested);
}
#[test]
fn pdf_field_descriptor_debug() {
let desc = PdfFieldDescriptor::new("f", "r");
let dbg = format!("{:?}", desc);
assert!(dbg.contains("PdfFieldDescriptor"));
}
#[test]
fn pdf_field_descriptor_clone() {
let desc = PdfFieldDescriptor::new("f", "r");
let cloned = desc.clone();
assert_eq!(desc.field_name, cloned.field_name);
}
#[test]
fn pdf_model_metadata_default() {
let meta = PdfModelMetadata::default();
assert_eq!(meta.page_size, crate::enums::PageSize::A4);
assert_eq!(meta.margins, 72.0);
}
#[test]
fn pdf_model_metadata_debug() {
let meta = PdfModelMetadata::default();
let dbg = format!("{:?}", meta);
assert!(dbg.contains("PdfModelMetadata"));
}
#[test]
fn pdf_model_metadata_clone() {
let meta = PdfModelMetadata::default();
let cloned = meta.clone();
assert_eq!(meta.margins, cloned.margins);
}
#[test]
fn capability_level_clone_copy() {
let level = CapabilityLevel::Structural;
let copied = level;
assert_eq!(level, copied);
}
#[test]
fn capability_level_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(CapabilityLevel::None);
set.insert(CapabilityLevel::None);
set.insert(CapabilityLevel::Heuristic);
assert_eq!(set.len(), 2);
}
#[test]
fn engine_capabilities_to_detailed_custom() {
let caps = EngineCapabilities {
create: true,
read: true,
manipulate: false,
fill_forms: false,
encrypt: false,
sign: false,
pdfa: false,
};
let detailed = caps.to_detailed();
assert_eq!(detailed.text_extraction, CapabilityLevel::Structural);
assert_eq!(detailed.metadata, CapabilityLevel::Structural);
assert_eq!(detailed.image_extraction, CapabilityLevel::Heuristic);
assert_eq!(detailed.table_detection, CapabilityLevel::Heuristic);
assert_eq!(detailed.rendering, CapabilityLevel::Structural);
}
#[test]
fn engine_capabilities_to_detailed_none() {
let caps = EngineCapabilities::default();
let detailed = caps.to_detailed();
assert_eq!(detailed.text_extraction, CapabilityLevel::None);
assert_eq!(detailed.metadata, CapabilityLevel::None);
assert_eq!(detailed.image_extraction, CapabilityLevel::None);
assert_eq!(detailed.table_detection, CapabilityLevel::None);
assert_eq!(detailed.rendering, CapabilityLevel::None);
}
}