1use crate::PdfData;
4use crate::object::Object;
5use crate::page::Pages;
6use crate::page::cached::CachedPages;
7use crate::reader::Reader;
8use crate::xref::{XRef, XRefError, fallback, root_xref};
9use std::sync::Arc;
10
11pub use crate::crypto::DecryptionError;
12use crate::metadata::Metadata;
13
14pub struct Pdf {
16 xref: Arc<XRef>,
17 header_version: PdfVersion,
18 pages: CachedPages,
19 data: PdfData,
20}
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
24pub enum LoadPdfError {
25 Decryption(DecryptionError),
27 Invalid,
29}
30
31#[allow(clippy::len_without_is_empty)]
32impl Pdf {
33 pub fn new(data: PdfData) -> Result<Self, LoadPdfError> {
37 Self::new_with_password(data, "")
38 }
39
40 pub fn new_with_password(data: PdfData, password: &str) -> Result<Self, LoadPdfError> {
44 let password = password.as_bytes();
45 let version = find_version(data.as_ref().as_ref()).unwrap_or(PdfVersion::Pdf10);
46 let xref = match root_xref(data.clone(), password) {
47 Ok(x) => x,
48 Err(e) => match e {
49 XRefError::Unknown => {
50 fallback(data.clone(), password).ok_or(LoadPdfError::Invalid)?
51 }
52 XRefError::Encryption(e) => return Err(LoadPdfError::Decryption(e)),
53 },
54 };
55 let xref = Arc::new(xref);
56
57 let pages = CachedPages::new(xref.clone()).ok_or(LoadPdfError::Invalid)?;
58
59 Ok(Self {
60 xref,
61 header_version: version,
62 pages,
63 data,
64 })
65 }
66
67 pub fn len(&self) -> usize {
69 self.xref.len()
70 }
71
72 pub fn objects(&self) -> impl IntoIterator<Item = Object<'_>> {
74 self.xref.objects()
75 }
76
77 pub fn version(&self) -> PdfVersion {
79 self.xref
80 .trailer_data()
81 .version
82 .unwrap_or(self.header_version)
83 }
84
85 pub fn data(&self) -> &PdfData {
87 &self.data
88 }
89
90 pub fn pages(&self) -> &Pages<'_> {
92 self.pages.get()
93 }
94
95 pub fn xref(&self) -> &XRef {
97 &self.xref
98 }
99
100 pub fn metadata(&self) -> &Metadata {
102 self.xref.metadata()
103 }
104}
105
106fn find_version(data: &[u8]) -> Option<PdfVersion> {
107 let data = &data[..data.len().min(2000)];
108 let mut r = Reader::new(data);
109
110 while r.forward_tag(b"%PDF-").is_none() {
111 r.read_byte()?;
112 }
113
114 PdfVersion::from_bytes(r.tail()?)
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
119pub enum PdfVersion {
120 Pdf10,
122 Pdf11,
124 Pdf12,
126 Pdf13,
128 Pdf14,
130 Pdf15,
132 Pdf16,
134 Pdf17,
136 Pdf20,
138}
139
140impl PdfVersion {
141 pub(crate) fn from_bytes(bytes: &[u8]) -> Option<Self> {
142 match bytes.get(..3)? {
143 b"1.0" => Some(Self::Pdf10),
144 b"1.1" => Some(Self::Pdf11),
145 b"1.2" => Some(Self::Pdf12),
146 b"1.3" => Some(Self::Pdf13),
147 b"1.4" => Some(Self::Pdf14),
148 b"1.5" => Some(Self::Pdf15),
149 b"1.6" => Some(Self::Pdf16),
150 b"1.7" => Some(Self::Pdf17),
151 b"2.0" => Some(Self::Pdf20),
152 _ => None,
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use crate::pdf::{Pdf, PdfVersion};
160 use std::sync::Arc;
161
162 #[test]
163 fn issue_49() {
164 let data = Arc::new([]);
165 let _ = Pdf::new(data);
166 }
167
168 #[test]
169 fn pdf_version_header() {
170 let data = std::fs::read("../hayro-tests/downloads/pdfjs/alphatrans.pdf").unwrap();
171 let pdf = Pdf::new(Arc::new(data)).unwrap();
172
173 assert_eq!(pdf.version(), PdfVersion::Pdf17);
174 }
175
176 #[test]
177 fn pdf_version_catalog() {
178 let data = std::fs::read("../hayro-tests/downloads/pdfbox/2163.pdf").unwrap();
179 let pdf = Pdf::new(Arc::new(data)).unwrap();
180
181 assert_eq!(pdf.version(), PdfVersion::Pdf14);
182 }
183}