1use crate::object::ObjectIdentifier;
2use crate::object::Stream;
3use crate::reader::ReaderContext;
4use crate::sync::HashMap;
5use crate::sync::{Arc, Mutex, MutexExt};
6use crate::util::SegmentList;
7use alloc::vec::Vec;
8use core::fmt::{Debug, Formatter};
9
10pub(crate) type ObjectStreamOffsets = Vec<(u32, usize)>;
16
17#[derive(Clone)]
19pub struct PdfData {
20 #[cfg(feature = "std")]
21 inner: Arc<dyn AsRef<[u8]> + Send + Sync>,
22 #[cfg(not(feature = "std"))]
23 inner: Arc<dyn AsRef<[u8]>>,
24}
25
26impl Debug for PdfData {
27 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
28 write!(f, "PdfData {{ ... }}")
29 }
30}
31
32impl AsRef<[u8]> for PdfData {
33 fn as_ref(&self) -> &[u8] {
34 (*self.inner).as_ref()
35 }
36}
37
38#[cfg(feature = "std")]
39impl<T: AsRef<[u8]> + Send + Sync + 'static> From<Arc<T>> for PdfData {
40 fn from(data: Arc<T>) -> Self {
41 Self { inner: data }
42 }
43}
44
45#[cfg(not(feature = "std"))]
46impl<T: AsRef<[u8]> + 'static> From<Arc<T>> for PdfData {
47 fn from(data: Arc<T>) -> Self {
48 Self { inner: data }
49 }
50}
51
52impl From<Vec<u8>> for PdfData {
53 fn from(data: Vec<u8>) -> Self {
54 Self {
55 inner: Arc::new(data),
56 }
57 }
58}
59
60pub(crate) struct Data {
72 data: PdfData,
73 decoded: SegmentList<Vec<u8>, 32>,
75 map: Mutex<HashMap<ObjectIdentifier, usize>>,
76 object_stream_offsets: Mutex<HashMap<ObjectIdentifier, Arc<ObjectStreamOffsets>>>,
83}
84
85impl Debug for Data {
86 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
87 write!(f, "Data {{ ... }}")
88 }
89}
90
91impl Data {
92 pub(crate) fn new(data: PdfData) -> Self {
94 Self {
95 data,
96 decoded: SegmentList::new(),
97 map: Mutex::new(HashMap::new()),
98 object_stream_offsets: Mutex::new(HashMap::new()),
99 }
100 }
101
102 pub(crate) fn get(&self) -> &PdfData {
104 &self.data
105 }
106
107 pub(crate) fn get_object_stream_offsets_or_init<F>(
123 &self,
124 id: ObjectIdentifier,
125 parse: F,
126 ) -> Option<Arc<ObjectStreamOffsets>>
127 where
128 F: FnOnce() -> Option<ObjectStreamOffsets>,
129 {
130 if let Some(hit) = self.object_stream_offsets.get().get(&id).cloned() {
131 return Some(hit);
132 }
133
134 let parsed = Arc::new(parse()?);
137
138 let mut locked = self.object_stream_offsets.get();
142 Some(locked.entry(id).or_insert(parsed).clone())
143 }
144
145 #[cfg(test)]
147 pub(crate) fn object_stream_offsets_cache_len(&self) -> usize {
148 self.object_stream_offsets.get().len()
149 }
150
151 pub(crate) fn get_with(&self, id: ObjectIdentifier, ctx: &ReaderContext<'_>) -> Option<&[u8]> {
153 if let Some(&idx) = self.map.get().get(&id) {
154 return self.decoded.get(idx).map(Vec::as_slice);
155 }
156
157 let stream = ctx.xref().get_with::<Stream<'_>>(id, ctx)?;
159 let decoded = stream.decoded().ok()?;
160
161 let mut locked = self.map.get();
162 if let Some(&idx) = locked.get(&id) {
163 return self.decoded.get(idx).map(Vec::as_slice);
164 }
165
166 let idx = locked.len();
167 let decoded = self.decoded.get_or_init(idx, || decoded);
168 locked.insert(id, idx);
169
170 Some(decoded.as_slice())
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
185 use core::sync::atomic::{AtomicUsize, Ordering};
186
187 fn make_data() -> Data {
188 Data::new(PdfData::from(alloc::vec![0u8; 16]))
189 }
190
191 fn id(n: i32) -> ObjectIdentifier {
192 ObjectIdentifier::new(n, 0)
193 }
194
195 #[test]
196 fn qf2b_cache_miss_parses_once_and_returns_same_arc() {
197 let d = make_data();
198 let calls = AtomicUsize::new(0);
199
200 let a = d
201 .get_object_stream_offsets_or_init(id(7), || {
202 calls.fetch_add(1, Ordering::SeqCst);
203 Some(alloc::vec![(1, 10), (2, 20), (3, 30)])
204 })
205 .expect("first parse must succeed");
206
207 let b = d
209 .get_object_stream_offsets_or_init(id(7), || {
210 calls.fetch_add(1, Ordering::SeqCst);
211 Some(alloc::vec![(99, 99)])
212 })
213 .expect("cache hit must succeed");
214
215 assert_eq!(calls.load(Ordering::SeqCst), 1, "parse called exactly once");
216 assert!(Arc::ptr_eq(&a, &b), "cache returns identical Arc");
217 assert_eq!(&*a, &alloc::vec![(1u32, 10usize), (2, 20), (3, 30)]);
218 assert_eq!(d.object_stream_offsets_cache_len(), 1);
219 }
220
221 #[test]
222 fn qf2b_cache_miss_with_none_does_not_poison() {
223 let d = make_data();
224 let first = d.get_object_stream_offsets_or_init(id(9), || None);
225 assert!(first.is_none(), "first parse may legitimately fail");
226 assert_eq!(
227 d.object_stream_offsets_cache_len(),
228 0,
229 "failed parses must not pollute the cache"
230 );
231
232 let retry = d
234 .get_object_stream_offsets_or_init(id(9), || Some(alloc::vec![(5, 50)]))
235 .expect("retry after None must succeed");
236 assert_eq!(&*retry, &alloc::vec![(5u32, 50usize)]);
237 assert_eq!(d.object_stream_offsets_cache_len(), 1);
238 }
239
240 #[test]
241 fn qf2b_distinct_ids_are_isolated() {
242 let d = make_data();
243
244 let a = d
245 .get_object_stream_offsets_or_init(id(1), || Some(alloc::vec![(1, 10)]))
246 .unwrap();
247 let b = d
248 .get_object_stream_offsets_or_init(id(2), || Some(alloc::vec![(2, 20)]))
249 .unwrap();
250
251 assert!(!Arc::ptr_eq(&a, &b));
252 assert_eq!(&*a, &alloc::vec![(1u32, 10usize)]);
253 assert_eq!(&*b, &alloc::vec![(2u32, 20usize)]);
254 assert_eq!(d.object_stream_offsets_cache_len(), 2);
255 }
256
257 #[test]
258 fn qf2b_distinct_data_instances_do_not_share_cache() {
259 let d1 = make_data();
262 let d2 = make_data();
263
264 let _ = d1
265 .get_object_stream_offsets_or_init(id(7), || Some(alloc::vec![(1, 10)]))
266 .unwrap();
267
268 let calls = AtomicUsize::new(0);
270 let _ = d2
271 .get_object_stream_offsets_or_init(id(7), || {
272 calls.fetch_add(1, Ordering::SeqCst);
273 Some(alloc::vec![(2, 20)])
274 })
275 .unwrap();
276
277 assert_eq!(
278 calls.load(Ordering::SeqCst),
279 1,
280 "d2 must invoke its own parse — no cross-document cache"
281 );
282
283 let again = d2
284 .get_object_stream_offsets_or_init(id(7), || {
285 calls.fetch_add(1, Ordering::SeqCst);
286 Some(alloc::vec![(3, 30)])
287 })
288 .unwrap();
289 assert_eq!(
290 calls.load(Ordering::SeqCst),
291 1,
292 "second lookup on d2 must hit the d2 cache"
293 );
294 assert_eq!(&*again, &alloc::vec![(2u32, 20usize)]);
295 }
296
297 #[test]
298 fn qf2b_cache_drops_with_data() {
299 let arc_after_drop = {
303 let d = make_data();
304 let inner = d
305 .get_object_stream_offsets_or_init(id(1), || Some(alloc::vec![(1, 10)]))
306 .unwrap();
307 assert!(Arc::strong_count(&inner) >= 2, "cache + caller refs");
308 inner
309 };
310 assert_eq!(Arc::strong_count(&arc_after_drop), 1);
312 }
313}