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