edgeparse_core/pdf/
loader.rs1use std::path::Path;
4
5use lopdf::Document;
6
7use crate::EdgePdfError;
8
9pub struct RawPdfDocument {
11 pub document: Document,
13 pub num_pages: u32,
15 pub metadata: PdfMetadata,
17}
18
19impl std::fmt::Debug for RawPdfDocument {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("RawPdfDocument")
22 .field("num_pages", &self.num_pages)
23 .field("metadata", &self.metadata)
24 .finish_non_exhaustive()
25 }
26}
27
28#[derive(Debug, Clone, Default)]
30pub struct PdfMetadata {
31 pub author: Option<String>,
33 pub title: Option<String>,
35 pub creation_date: Option<String>,
37 pub modification_date: Option<String>,
39 pub producer: Option<String>,
41 pub creator: Option<String>,
43}
44
45pub fn load_pdf(path: &Path, _password: Option<&str>) -> Result<RawPdfDocument, EdgePdfError> {
54 if !path.exists() {
55 return Err(EdgePdfError::LoadError(format!(
56 "File not found: {}",
57 path.display()
58 )));
59 }
60
61 let document = Document::load(path).map_err(|e| {
62 EdgePdfError::LoadError(format!("Failed to load PDF {}: {}", path.display(), e))
63 })?;
64
65 let pages = document.get_pages();
66 let num_pages = pages.len() as u32;
67
68 let metadata = extract_metadata(&document);
69
70 Ok(RawPdfDocument {
71 document,
72 num_pages,
73 metadata,
74 })
75}
76
77fn extract_metadata(doc: &Document) -> PdfMetadata {
79 let mut metadata = PdfMetadata::default();
80
81 if let Ok(info_ref) = doc.trailer.get(b"Info") {
82 if let Ok(info_ref) = info_ref.as_reference() {
83 if let Ok(info) = doc.get_object(info_ref) {
84 if let Ok(dict) = info.as_dict() {
85 metadata.author = extract_string_field(dict, b"Author");
86 metadata.title = extract_string_field(dict, b"Title");
87 metadata.creation_date = extract_string_field(dict, b"CreationDate");
88 metadata.modification_date = extract_string_field(dict, b"ModDate");
89 metadata.producer = extract_string_field(dict, b"Producer");
90 metadata.creator = extract_string_field(dict, b"Creator");
91 }
92 }
93 }
94 }
95
96 metadata
97}
98
99pub fn load_pdf_from_bytes(
111 data: &[u8],
112 _password: Option<&str>,
113) -> Result<RawPdfDocument, EdgePdfError> {
114 if data.is_empty() {
115 return Err(EdgePdfError::LoadError("Empty PDF data".to_string()));
116 }
117
118 let document = Document::load_mem(data)
119 .map_err(|e| EdgePdfError::LoadError(format!("Failed to parse PDF from bytes: {e}")))?;
120
121 let pages = document.get_pages();
122 let num_pages = pages.len() as u32;
123 let metadata = extract_metadata(&document);
124
125 Ok(RawPdfDocument {
126 document,
127 num_pages,
128 metadata,
129 })
130}
131
132fn extract_string_field(dict: &lopdf::Dictionary, key: &[u8]) -> Option<String> {
134 dict.get(key).ok().and_then(|obj| match obj {
135 lopdf::Object::String(bytes, _) => String::from_utf8(bytes.clone()).ok(),
136 _ => None,
137 })
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_load_nonexistent_file() {
146 let result = load_pdf(Path::new("/nonexistent/file.pdf"), None);
147 assert!(result.is_err());
148 match result.unwrap_err() {
149 EdgePdfError::LoadError(msg) => assert!(msg.contains("File not found")),
150 other => panic!("Unexpected error: {:?}", other),
151 }
152 }
153}