1use crate::error::Result;
7
8pub trait PdfModel {
13 fn render(&self) -> Result<Vec<RenderedElement>>;
21
22 fn metadata(&self) -> PdfModelMetadata;
24
25 fn field_descriptors(&self) -> Vec<PdfFieldDescriptor> {
30 Vec::new()
31 }
32}
33
34#[derive(Debug, Clone)]
39pub struct PdfFieldDescriptor {
40 pub field_name: String,
42 pub rust_field_name: String,
44 pub order: u32,
46 pub format: Option<String>,
48 pub default_value: Option<String>,
50 pub required: bool,
52 pub nested: bool,
54}
55
56impl PdfFieldDescriptor {
57 #[must_use]
59 pub fn new(field_name: impl Into<String>, rust_field_name: impl Into<String>) -> Self {
60 Self {
61 field_name: field_name.into(),
62 rust_field_name: rust_field_name.into(),
63 order: u32::MAX,
64 format: None,
65 default_value: None,
66 required: false,
67 nested: false,
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct PdfModelMetadata {
75 pub page_size: crate::enums::PageSize,
77 pub orientation: crate::enums::Orientation,
79 pub margins: f64,
81}
82
83impl Default for PdfModelMetadata {
84 fn default() -> Self {
85 Self {
86 page_size: crate::enums::PageSize::A4,
87 orientation: crate::enums::Orientation::default(),
88 margins: 72.0,
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
98pub enum RenderedElement {
99 Text {
101 x: f64,
103 y: f64,
105 text: crate::content::PdfText,
107 },
108 Table {
110 x: f64,
112 y: f64,
114 table: crate::content::PdfTable,
116 },
117 Image {
119 x: f64,
121 y: f64,
123 image: crate::content::PdfImage,
125 },
126}
127
128pub trait PdfReadListener: Send {
139 fn on_page_start(&mut self, page_number: usize) -> Result<()> {
145 let _ = page_number;
146 Ok(())
147 }
148
149 fn on_text(&mut self, page_number: usize, text: &str) -> Result<()>;
155
156 fn on_page_end(&mut self, page_number: usize) -> Result<()> {
162 let _ = page_number;
163 Ok(())
164 }
165
166 fn on_document_end(&mut self) -> Result<()> {
172 Ok(())
173 }
174}
175
176pub trait PdfWriteHandler: Send {
183 fn before_document(&mut self) -> Result<()> {
189 Ok(())
190 }
191
192 fn before_page(&mut self, page_number: usize) -> Result<()> {
198 let _ = page_number;
199 Ok(())
200 }
201
202 fn after_page(&mut self, page_number: usize) -> Result<()> {
208 let _ = page_number;
209 Ok(())
210 }
211
212 fn after_document(&mut self) -> Result<()> {
218 Ok(())
219 }
220}
221
222pub trait PdfConverter<T>: Send {
228 fn to_pdf_string(&self, value: &T) -> Result<String>;
234
235 #[allow(clippy::wrong_self_convention)]
241 fn from_pdf_string(&self, s: &str) -> Result<T>;
242}
243
244impl<T> PdfConverter<T> for Box<dyn PdfConverter<T>> {
247 fn to_pdf_string(&self, value: &T) -> Result<String> {
248 (**self).to_pdf_string(value)
249 }
250
251 fn from_pdf_string(&self, s: &str) -> Result<T> {
252 (**self).from_pdf_string(s)
253 }
254}
255
256pub trait PdfEngine: Send + Sync {
263 fn name(&self) -> &str;
265
266 fn capabilities(&self) -> EngineCapabilities;
268}
269
270#[derive(Debug, Clone, Copy, Default)]
272#[allow(clippy::struct_excessive_bools)]
273pub struct EngineCapabilities {
274 pub create: bool,
276 pub read: bool,
278 pub manipulate: bool,
280 pub fill_forms: bool,
282 pub encrypt: bool,
284 pub sign: bool,
286 pub pdfa: bool,
288}
289
290impl EngineCapabilities {
291 #[must_use]
293 pub const fn lopdf() -> Self {
294 Self {
295 create: false,
296 read: true,
297 manipulate: true,
298 fill_forms: true,
299 encrypt: false,
300 sign: false,
301 pdfa: false,
302 }
303 }
304
305 #[must_use]
307 pub const fn printpdf() -> Self {
308 Self {
309 create: true,
310 read: false,
311 manipulate: false,
312 fill_forms: false,
313 encrypt: false,
314 sign: false,
315 pdfa: false,
316 }
317 }
318
319 #[must_use]
324 pub const fn to_detailed(&self) -> DetailedEngineCapabilities {
325 DetailedEngineCapabilities {
326 text_extraction: if self.read {
327 CapabilityLevel::Structural
328 } else {
329 CapabilityLevel::None
330 },
331 metadata: if self.read {
332 CapabilityLevel::Structural
333 } else {
334 CapabilityLevel::None
335 },
336 image_extraction: if self.read {
337 CapabilityLevel::Heuristic
338 } else {
339 CapabilityLevel::None
340 },
341 table_detection: if self.read {
342 CapabilityLevel::Heuristic
343 } else {
344 CapabilityLevel::None
345 },
346 rendering: if self.create {
347 CapabilityLevel::Structural
348 } else {
349 CapabilityLevel::None
350 },
351 }
352 }
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
361#[non_exhaustive]
362pub enum CapabilityLevel {
363 #[default]
365 None,
366 Heuristic,
368 Structural,
370 Cloud,
372}
373
374impl CapabilityLevel {
375 #[must_use]
377 pub const fn is_supported(&self) -> bool {
378 !matches!(self, Self::None)
379 }
380}
381
382#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
388pub struct DetailedEngineCapabilities {
389 pub text_extraction: CapabilityLevel,
391 pub metadata: CapabilityLevel,
393 pub image_extraction: CapabilityLevel,
395 pub table_detection: CapabilityLevel,
397 pub rendering: CapabilityLevel,
399}
400
401#[cfg(test)]
402#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn capability_level_ordering() {
408 assert!(CapabilityLevel::None < CapabilityLevel::Heuristic);
409 assert!(CapabilityLevel::Heuristic < CapabilityLevel::Structural);
410 assert!(CapabilityLevel::Structural < CapabilityLevel::Cloud);
411 }
412
413 #[test]
414 fn capability_level_default_is_none() {
415 assert_eq!(CapabilityLevel::default(), CapabilityLevel::None);
416 }
417
418 #[test]
419 fn capability_level_is_supported() {
420 assert!(!CapabilityLevel::None.is_supported());
421 assert!(CapabilityLevel::Heuristic.is_supported());
422 assert!(CapabilityLevel::Structural.is_supported());
423 assert!(CapabilityLevel::Cloud.is_supported());
424 }
425
426 #[test]
427 fn capability_level_debug() {
428 assert_eq!(format!("{:?}", CapabilityLevel::Cloud), "Cloud");
429 }
430
431 #[test]
432 fn detailed_capabilities_default() {
433 let dc = DetailedEngineCapabilities::default();
434 assert_eq!(dc.text_extraction, CapabilityLevel::None);
435 assert_eq!(dc.metadata, CapabilityLevel::None);
436 assert_eq!(dc.image_extraction, CapabilityLevel::None);
437 assert_eq!(dc.table_detection, CapabilityLevel::None);
438 assert_eq!(dc.rendering, CapabilityLevel::None);
439 }
440
441 #[test]
442 fn engine_capabilities_to_detailed_lopdf() {
443 let caps = EngineCapabilities::lopdf();
444 let detailed = caps.to_detailed();
445 assert_eq!(detailed.text_extraction, CapabilityLevel::Structural);
446 assert_eq!(detailed.metadata, CapabilityLevel::Structural);
447 assert_eq!(detailed.image_extraction, CapabilityLevel::Heuristic);
448 assert_eq!(detailed.table_detection, CapabilityLevel::Heuristic);
449 assert_eq!(detailed.rendering, CapabilityLevel::None);
450 }
451
452 #[test]
453 fn engine_capabilities_to_detailed_printpdf() {
454 let caps = EngineCapabilities::printpdf();
455 let detailed = caps.to_detailed();
456 assert_eq!(detailed.text_extraction, CapabilityLevel::None);
457 assert_eq!(detailed.metadata, CapabilityLevel::None);
458 assert_eq!(detailed.image_extraction, CapabilityLevel::None);
459 assert_eq!(detailed.table_detection, CapabilityLevel::None);
460 assert_eq!(detailed.rendering, CapabilityLevel::Structural);
461 }
462
463 #[test]
464 fn detailed_capabilities_equality() {
465 let a = DetailedEngineCapabilities {
466 text_extraction: CapabilityLevel::Structural,
467 ..Default::default()
468 };
469 let b = DetailedEngineCapabilities {
470 text_extraction: CapabilityLevel::Structural,
471 ..Default::default()
472 };
473 assert_eq!(a, b);
474 }
475
476 #[test]
477 fn detailed_capabilities_inequality() {
478 let a = DetailedEngineCapabilities {
479 text_extraction: CapabilityLevel::Structural,
480 ..Default::default()
481 };
482 let b = DetailedEngineCapabilities {
483 text_extraction: CapabilityLevel::Heuristic,
484 ..Default::default()
485 };
486 assert_ne!(a, b);
487 }
488
489 #[test]
490 fn detailed_capabilities_debug() {
491 let dc = DetailedEngineCapabilities::default();
492 let dbg = format!("{:?}", dc);
493 assert!(dbg.contains("DetailedEngineCapabilities"));
494 }
495
496 #[test]
497 fn detailed_capabilities_clone() {
498 let dc = DetailedEngineCapabilities {
499 text_extraction: CapabilityLevel::Cloud,
500 metadata: CapabilityLevel::Structural,
501 image_extraction: CapabilityLevel::Heuristic,
502 table_detection: CapabilityLevel::None,
503 rendering: CapabilityLevel::Structural,
504 };
505 let cloned = dc;
506 assert_eq!(dc, cloned);
507 }
508
509 #[test]
510 fn engine_capabilities_default() {
511 let caps = EngineCapabilities::default();
512 assert!(!caps.create);
513 assert!(!caps.read);
514 assert!(!caps.manipulate);
515 assert!(!caps.fill_forms);
516 assert!(!caps.encrypt);
517 assert!(!caps.sign);
518 assert!(!caps.pdfa);
519 }
520
521 #[test]
522 fn engine_capabilities_debug() {
523 let caps = EngineCapabilities::lopdf();
524 let dbg = format!("{:?}", caps);
525 assert!(dbg.contains("EngineCapabilities"));
526 }
527
528 #[test]
529 fn engine_capabilities_clone() {
530 let caps = EngineCapabilities::printpdf();
531 let cloned = caps;
532 assert_eq!(caps.create, cloned.create);
533 }
534
535 #[test]
536 fn pdf_field_descriptor_new() {
537 let desc = PdfFieldDescriptor::new("pdf_name", "rust_name");
538 assert_eq!(desc.field_name, "pdf_name");
539 assert_eq!(desc.rust_field_name, "rust_name");
540 assert_eq!(desc.order, u32::MAX);
541 assert!(desc.format.is_none());
542 assert!(desc.default_value.is_none());
543 assert!(!desc.required);
544 assert!(!desc.nested);
545 }
546
547 #[test]
548 fn pdf_field_descriptor_debug() {
549 let desc = PdfFieldDescriptor::new("f", "r");
550 let dbg = format!("{:?}", desc);
551 assert!(dbg.contains("PdfFieldDescriptor"));
552 }
553
554 #[test]
555 fn pdf_field_descriptor_clone() {
556 let desc = PdfFieldDescriptor::new("f", "r");
557 let cloned = desc.clone();
558 assert_eq!(desc.field_name, cloned.field_name);
559 }
560
561 #[test]
562 fn pdf_model_metadata_default() {
563 let meta = PdfModelMetadata::default();
564 assert_eq!(meta.page_size, crate::enums::PageSize::A4);
565 assert_eq!(meta.margins, 72.0);
566 }
567
568 #[test]
569 fn pdf_model_metadata_debug() {
570 let meta = PdfModelMetadata::default();
571 let dbg = format!("{:?}", meta);
572 assert!(dbg.contains("PdfModelMetadata"));
573 }
574
575 #[test]
576 fn pdf_model_metadata_clone() {
577 let meta = PdfModelMetadata::default();
578 let cloned = meta.clone();
579 assert_eq!(meta.margins, cloned.margins);
580 }
581
582 #[test]
583 fn capability_level_clone_copy() {
584 let level = CapabilityLevel::Structural;
585 let copied = level;
586 assert_eq!(level, copied);
587 }
588
589 #[test]
590 fn capability_level_hash() {
591 use std::collections::HashSet;
592 let mut set = HashSet::new();
593 set.insert(CapabilityLevel::None);
594 set.insert(CapabilityLevel::None);
595 set.insert(CapabilityLevel::Heuristic);
596 assert_eq!(set.len(), 2);
597 }
598
599 #[test]
600 fn engine_capabilities_to_detailed_custom() {
601 let caps = EngineCapabilities {
602 create: true,
603 read: true,
604 manipulate: false,
605 fill_forms: false,
606 encrypt: false,
607 sign: false,
608 pdfa: false,
609 };
610 let detailed = caps.to_detailed();
611 assert_eq!(detailed.text_extraction, CapabilityLevel::Structural);
612 assert_eq!(detailed.metadata, CapabilityLevel::Structural);
613 assert_eq!(detailed.image_extraction, CapabilityLevel::Heuristic);
614 assert_eq!(detailed.table_detection, CapabilityLevel::Heuristic);
615 assert_eq!(detailed.rendering, CapabilityLevel::Structural);
616 }
617
618 #[test]
619 fn engine_capabilities_to_detailed_none() {
620 let caps = EngineCapabilities::default();
621 let detailed = caps.to_detailed();
622 assert_eq!(detailed.text_extraction, CapabilityLevel::None);
623 assert_eq!(detailed.metadata, CapabilityLevel::None);
624 assert_eq!(detailed.image_extraction, CapabilityLevel::None);
625 assert_eq!(detailed.table_detection, CapabilityLevel::None);
626 assert_eq!(detailed.rendering, CapabilityLevel::None);
627 }
628}