easypdf_reader/reader/
pdf_reader.rs1use std::ops::Range;
4use std::path::Path;
5
6use easypdf_core::error::{PdfError, Result};
7use easypdf_core::io::guards::guard_element_explosion;
8use easypdf_core::io::repair::{RepairOptions, attempt_repair, is_likely_corrupt};
9use easypdf_core::{PageRange, PdfInput, ResourceLimits};
10
11use crate::strategy::ReadStrategy;
12
13pub struct PdfReader {
27 pub(super) document: Option<lopdf::Document>,
29 pub(super) pages: Option<PageRange>,
30 pub(super) limits: ResourceLimits,
31 pub(super) strategy: ReadStrategy,
32 pub(super) raw_bytes: Vec<u8>,
34}
35
36impl PdfReader {
37 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
48 let path = path.as_ref();
49 let file_size = std::fs::metadata(path).map_or(0, |m| m.len());
50 let strategy = ReadStrategy::auto(file_size);
51 Self::open_with_strategy(path, strategy)
52 }
53
54 pub fn from_bytes(bytes: impl Into<Vec<u8>>) -> Result<Self> {
60 let bytes = bytes.into();
61 let file_size = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
62 let strategy = ReadStrategy::auto(file_size);
63 let input = PdfInput::from_bytes(bytes);
64 Self::open_with_limits_and_strategy(&input, ResourceLimits::default(), strategy)
65 }
66
67 pub fn open_with_limits(input: &PdfInput, limits: ResourceLimits) -> Result<Self> {
75 let bytes = input.read(limits)?;
76 let file_size = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
77 let strategy = ReadStrategy::auto(file_size);
78 Self::load_from_bytes(bytes, limits, strategy)
79 }
80
81 pub fn open_with_strategy(path: impl AsRef<Path>, strategy: ReadStrategy) -> Result<Self> {
87 let input = PdfInput::from_path(path.as_ref());
88 Self::open_with_limits_and_strategy(&input, ResourceLimits::default(), strategy)
89 }
90
91 pub fn open_with_repair(
100 path: impl AsRef<Path>,
101 repair: RepairOptions,
102 strategy: ReadStrategy,
103 ) -> Result<Self> {
104 let input = PdfInput::from_path(path.as_ref());
105
106 let bytes = if is_likely_corrupt(&input) {
107 attempt_repair(&input, &repair)?
108 } else {
109 input.read(ResourceLimits::default())?
110 };
111
112 Self::load_from_bytes(bytes, ResourceLimits::default(), strategy)
113 }
114
115 pub fn open_with_limits_and_strategy(
121 input: &PdfInput,
122 limits: ResourceLimits,
123 strategy: ReadStrategy,
124 ) -> Result<Self> {
125 let bytes = input.read(limits)?;
126 Self::load_from_bytes(bytes, limits, strategy)
127 }
128
129 fn load_from_bytes(
134 bytes: Vec<u8>,
135 limits: ResourceLimits,
136 strategy: ReadStrategy,
137 ) -> Result<Self> {
138 if strategy == ReadStrategy::Streaming {
139 return Ok(Self {
141 document: None,
142 pages: None,
143 limits,
144 strategy,
145 raw_bytes: bytes,
146 });
147 }
148
149 let document = lopdf::Document::load_mem(&bytes)
150 .map_err(|error| PdfError::Parse(error.to_string()))?;
151
152 let element_count = document.objects.len();
154 guard_element_explosion(element_count, &limits)?;
155
156 let page_count = document.get_pages().len();
157 if page_count > limits.max_pages() {
158 return Err(PdfError::ResourceLimitExceeded {
159 resource: "pages",
160 limit: usize_to_u64_saturating(limits.max_pages()),
161 actual: usize_to_u64_saturating(page_count),
162 });
163 }
164
165 Ok(Self {
166 document: Some(document),
167 pages: None,
168 limits,
169 strategy,
170 raw_bytes: bytes,
171 })
172 }
173
174 #[must_use]
176 pub const fn strategy(&self) -> ReadStrategy {
177 self.strategy
178 }
179
180 #[must_use]
182 pub fn pages(mut self, range: Range<usize>) -> Self {
183 let start = range.start;
184 self.pages = Some(match PageRange::new(range) {
185 Ok(pages) => pages,
186 Err(_) => PageRange::empty_at(start),
187 });
188 self
189 }
190
191 pub fn try_pages(mut self, range: Range<usize>) -> Result<Self> {
197 self.pages = Some(PageRange::new(range)?);
198 Ok(self)
199 }
200}
201
202pub(crate) fn usize_to_u64_saturating(value: usize) -> u64 {
204 u64::try_from(value).unwrap_or(u64::MAX)
205}