pdfrum_object/stream.rs
1//! Stream objects (ISO 32000-1 ยง7.3.8) and the zero-copy window their data
2//! lives in.
3
4use std::fmt;
5use std::ops::{Deref, Range};
6use std::sync::Arc;
7
8use bytes::Bytes;
9
10use crate::{Dict, Error};
11
12/// A window into a shared byte buffer.
13///
14/// The document's bytes are held once and every stream is a window into them,
15/// so opening a file costs one copy no matter how many streams it holds. Three
16/// buffers occur in practice: the file itself, a decrypted replacement for one
17/// stream's bytes, and a decoded object stream's payload.
18///
19/// Backed by [`bytes::Bytes`], whose owner pointer is separate from its data
20/// pointer. That is what lets [`ByteSpan::from`] adopt a `Vec<u8>`'s allocation
21/// instead of copying it โ `Arc<[u8]>` cannot, because its refcounts live
22/// inline with the payload. A window is a refcount bump, never an allocation,
23/// so [`subspan`](Self::subspan) is the cheap way to carve a file up.
24///
25/// Decoded (filtered) data is deliberately *not* cached here โ the page layer
26/// owns those caches, keyed by the reference that produced them.
27///
28/// ```
29/// use std::sync::Arc;
30/// use pdfrum_object::ByteSpan;
31///
32/// let file: Arc<[u8]> = Arc::from(&b"%PDF-1.7 stream-bytes"[..]);
33/// let span = ByteSpan::new(file, 9..21).unwrap();
34/// assert_eq!(&*span, b"stream-bytes");
35/// assert_eq!(span.len(), 12);
36/// ```
37#[derive(Clone)]
38pub struct ByteSpan {
39 buf: Bytes,
40}
41
42impl ByteSpan {
43 /// A window covering `range` of `file`.
44 ///
45 /// # Errors
46 ///
47 /// [`Error::SpanOutOfBounds`] when the range runs past the buffer or ends
48 /// before it starts. Ranges come from `/Length` values in untrusted
49 /// files, so this is checked rather than trusted.
50 pub fn new(file: Arc<[u8]>, range: Range<usize>) -> Result<Self, Error> {
51 // Checked before slicing, never delegated to `Bytes::slice`: that
52 // panics where this must return, and the ranges are untrusted.
53 if range.start > range.end || range.end > file.len() {
54 return Err(Error::SpanOutOfBounds {
55 start: range.start,
56 end: range.end,
57 len: file.len(),
58 });
59 }
60 Ok(Self {
61 buf: Bytes::from_owner(file).slice(range),
62 })
63 }
64
65 /// A window over a whole buffer.
66 #[must_use]
67 pub fn whole(file: Arc<[u8]>) -> Self {
68 Self {
69 buf: Bytes::from_owner(file),
70 }
71 }
72
73 /// An empty window.
74 #[must_use]
75 pub fn empty() -> Self {
76 Self { buf: Bytes::new() }
77 }
78
79 /// The bytes in the window.
80 #[must_use]
81 pub fn as_bytes(&self) -> &[u8] {
82 &self.buf
83 }
84
85 /// Number of bytes in the window.
86 #[must_use]
87 pub fn len(&self) -> usize {
88 self.buf.len()
89 }
90
91 /// Whether the window is empty.
92 #[must_use]
93 pub fn is_empty(&self) -> bool {
94 self.buf.is_empty()
95 }
96
97 /// A sub-window, with offsets relative to this window's start.
98 ///
99 /// # Errors
100 ///
101 /// [`Error::SpanOutOfBounds`] when `range` leaves this window.
102 pub fn subspan(&self, range: Range<usize>) -> Result<Self, Error> {
103 if range.start > range.end || range.end > self.len() {
104 return Err(Error::SpanOutOfBounds {
105 start: range.start,
106 end: range.end,
107 len: self.len(),
108 });
109 }
110 Ok(Self {
111 buf: self.buf.slice(range),
112 })
113 }
114}
115
116impl Deref for ByteSpan {
117 type Target = [u8];
118
119 fn deref(&self) -> &[u8] {
120 self.as_bytes()
121 }
122}
123
124impl AsRef<[u8]> for ByteSpan {
125 fn as_ref(&self) -> &[u8] {
126 self.as_bytes()
127 }
128}
129
130impl PartialEq for ByteSpan {
131 fn eq(&self, other: &Self) -> bool {
132 self.as_bytes() == other.as_bytes()
133 }
134}
135
136impl Eq for ByteSpan {}
137
138impl fmt::Debug for ByteSpan {
139 /// Prints the window's shape rather than its bytes: stream payloads run
140 /// to megabytes and a `Debug` dump of an object tree must stay readable.
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("ByteSpan")
143 .field("len", &self.len())
144 .finish_non_exhaustive()
145 }
146}
147
148impl From<Arc<[u8]>> for ByteSpan {
149 /// Shares the buffer; nothing is copied.
150 fn from(file: Arc<[u8]>) -> Self {
151 Self::whole(file)
152 }
153}
154
155impl From<Vec<u8>> for ByteSpan {
156 /// Adopts the vector's allocation; nothing is copied.
157 fn from(bytes: Vec<u8>) -> Self {
158 Self {
159 buf: Bytes::from(bytes),
160 }
161 }
162}
163
164/// A stream object: a dictionary describing bytes, plus the bytes.
165///
166/// The data is the *raw* payload as it sits in the file โ filters have not
167/// been applied and encryption has, if the document was encrypted. Its length
168/// is authoritative: a `/Length` in the dictionary that disagreed with the
169/// bytes found before `endstream` was already repaired by the reader, which
170/// leaves the dictionary untouched and records a diagnostic.
171///
172/// ```
173/// use pdfrum_object::{ByteSpan, Dict, Object, Stream, names};
174///
175/// let stream = Stream {
176/// dict: Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]),
177/// data: ByteSpan::from(b"abc".to_vec()),
178/// };
179/// assert_eq!(&*stream.data, b"abc");
180/// ```
181#[derive(Debug, Clone, PartialEq)]
182pub struct Stream {
183 /// The stream's dictionary: `/Length`, `/Filter`, `/DecodeParms`, and
184 /// whatever the stream's own type adds.
185 pub dict: Dict,
186 /// The raw stream data.
187 pub data: ByteSpan,
188}
189
190impl Stream {
191 /// A stream from a dictionary and its raw bytes.
192 #[must_use]
193 pub fn new(dict: Dict, data: ByteSpan) -> Self {
194 Self { dict, data }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use std::ops::Range;
201 use std::sync::Arc;
202
203 use super::ByteSpan;
204 use crate::Error;
205
206 #[test]
207 fn window_covers_only_its_range() {
208 let file: Arc<[u8]> = Arc::from(&b"0123456789"[..]);
209 let span = ByteSpan::new(Arc::clone(&file), 2..5).unwrap();
210 assert_eq!(&*span, b"234");
211 assert_eq!(span.len(), 3);
212 assert!(!span.is_empty());
213 }
214
215 #[test]
216 fn out_of_bounds_ranges_are_refused_not_clamped() {
217 let file: Arc<[u8]> = Arc::from(&b"0123"[..]);
218 assert_eq!(
219 ByteSpan::new(Arc::clone(&file), 0..5),
220 Err(Error::SpanOutOfBounds {
221 start: 0,
222 end: 5,
223 len: 4
224 })
225 );
226 let reversed = Range { start: 3, end: 1 };
227 assert!(ByteSpan::new(Arc::clone(&file), reversed).is_err());
228 assert!(ByteSpan::new(file, 4..4).is_ok());
229 }
230
231 #[test]
232 fn subspans_are_relative_and_bounded() {
233 let span = ByteSpan::from(b"0123456789".to_vec());
234 let inner = span.subspan(2..5).unwrap();
235 assert_eq!(&*inner, b"234");
236 assert_eq!(&*inner.subspan(1..2).unwrap(), b"3");
237 assert!(inner.subspan(0..4).is_err());
238 }
239
240 #[test]
241 fn spans_compare_by_content_not_by_backing_buffer() {
242 let a = ByteSpan::from(b"abc".to_vec());
243 let b = ByteSpan::new(Arc::from(&b"xxabcxx"[..]), 2..5).unwrap();
244 assert_eq!(a, b);
245 assert!(ByteSpan::empty().is_empty());
246 }
247}