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 /// A window over the whole of a buffer something else owns — a font
74 /// blob a renderer already holds, a memory map — shared, not copied.
75 ///
76 /// For a buffer that is already an `Arc<[u8]>`, [`ByteSpan::whole`] says
77 /// the same thing; this is for owners of any other shape, so a caller
78 /// with a 30 MB font collection in hand does not copy it to name it.
79 ///
80 /// ```
81 /// use pdfrum_object::ByteSpan;
82 ///
83 /// let span = ByteSpan::from_owner(vec![1u8, 2, 3]);
84 /// assert_eq!(span.as_bytes(), &[1, 2, 3]);
85 /// ```
86 #[must_use]
87 pub fn from_owner(owner: impl AsRef<[u8]> + Send + 'static) -> Self {
88 Self {
89 buf: Bytes::from_owner(owner),
90 }
91 }
92
93 /// An empty window.
94 #[must_use]
95 pub fn empty() -> Self {
96 Self { buf: Bytes::new() }
97 }
98
99 /// The bytes in the window.
100 #[must_use]
101 pub fn as_bytes(&self) -> &[u8] {
102 &self.buf
103 }
104
105 /// Number of bytes in the window.
106 #[must_use]
107 pub fn len(&self) -> usize {
108 self.buf.len()
109 }
110
111 /// Whether the window is empty.
112 #[must_use]
113 pub fn is_empty(&self) -> bool {
114 self.buf.is_empty()
115 }
116
117 /// A sub-window, with offsets relative to this window's start.
118 ///
119 /// # Errors
120 ///
121 /// [`Error::SpanOutOfBounds`] when `range` leaves this window.
122 pub fn subspan(&self, range: Range<usize>) -> Result<Self, Error> {
123 if range.start > range.end || range.end > self.len() {
124 return Err(Error::SpanOutOfBounds {
125 start: range.start,
126 end: range.end,
127 len: self.len(),
128 });
129 }
130 Ok(Self {
131 buf: self.buf.slice(range),
132 })
133 }
134}
135
136impl Deref for ByteSpan {
137 type Target = [u8];
138
139 fn deref(&self) -> &[u8] {
140 self.as_bytes()
141 }
142}
143
144impl AsRef<[u8]> for ByteSpan {
145 fn as_ref(&self) -> &[u8] {
146 self.as_bytes()
147 }
148}
149
150impl PartialEq for ByteSpan {
151 fn eq(&self, other: &Self) -> bool {
152 self.as_bytes() == other.as_bytes()
153 }
154}
155
156impl Eq for ByteSpan {}
157
158impl fmt::Debug for ByteSpan {
159 /// Prints the window's shape rather than its bytes: stream payloads run
160 /// to megabytes and a `Debug` dump of an object tree must stay readable.
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.debug_struct("ByteSpan")
163 .field("len", &self.len())
164 .finish_non_exhaustive()
165 }
166}
167
168impl From<Arc<[u8]>> for ByteSpan {
169 /// Shares the buffer; nothing is copied.
170 fn from(file: Arc<[u8]>) -> Self {
171 Self::whole(file)
172 }
173}
174
175impl From<Vec<u8>> for ByteSpan {
176 /// Adopts the vector's allocation; nothing is copied.
177 fn from(bytes: Vec<u8>) -> Self {
178 Self {
179 buf: Bytes::from(bytes),
180 }
181 }
182}
183
184/// A stream object: a dictionary describing bytes, plus the bytes.
185///
186/// The data is the *raw* payload as it sits in the file — filters have not
187/// been applied and encryption has, if the document was encrypted. Its length
188/// is authoritative: a `/Length` in the dictionary that disagreed with the
189/// bytes found before `endstream` was already repaired by the reader, which
190/// leaves the dictionary untouched and records a diagnostic.
191///
192/// ```
193/// use pdfrum_object::{ByteSpan, Dict, Object, Stream, names};
194///
195/// let stream = Stream {
196/// dict: Dict::from_pairs([(names::LENGTH.clone(), Object::Int(3))]),
197/// data: ByteSpan::from(b"abc".to_vec()),
198/// };
199/// assert_eq!(&*stream.data, b"abc");
200/// ```
201#[derive(Debug, Clone, PartialEq)]
202pub struct Stream {
203 /// The stream's dictionary: `/Length`, `/Filter`, `/DecodeParms`, and
204 /// whatever the stream's own type adds.
205 pub dict: Dict,
206 /// The raw stream data.
207 pub data: ByteSpan,
208}
209
210impl Stream {
211 /// A stream from a dictionary and its raw bytes.
212 #[must_use]
213 pub fn new(dict: Dict, data: ByteSpan) -> Self {
214 Self { dict, data }
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use std::ops::Range;
221 use std::sync::Arc;
222
223 use super::ByteSpan;
224 use crate::Error;
225
226 #[test]
227 fn window_covers_only_its_range() {
228 let file: Arc<[u8]> = Arc::from(&b"0123456789"[..]);
229 let span = ByteSpan::new(Arc::clone(&file), 2..5).unwrap();
230 assert_eq!(&*span, b"234");
231 assert_eq!(span.len(), 3);
232 assert!(!span.is_empty());
233 }
234
235 #[test]
236 fn out_of_bounds_ranges_are_refused_not_clamped() {
237 let file: Arc<[u8]> = Arc::from(&b"0123"[..]);
238 assert_eq!(
239 ByteSpan::new(Arc::clone(&file), 0..5),
240 Err(Error::SpanOutOfBounds {
241 start: 0,
242 end: 5,
243 len: 4
244 })
245 );
246 let reversed = Range { start: 3, end: 1 };
247 assert!(ByteSpan::new(Arc::clone(&file), reversed).is_err());
248 assert!(ByteSpan::new(file, 4..4).is_ok());
249 }
250
251 #[test]
252 fn subspans_are_relative_and_bounded() {
253 let span = ByteSpan::from(b"0123456789".to_vec());
254 let inner = span.subspan(2..5).unwrap();
255 assert_eq!(&*inner, b"234");
256 assert_eq!(&*inner.subspan(1..2).unwrap(), b"3");
257 assert!(inner.subspan(0..4).is_err());
258 }
259
260 #[test]
261 fn spans_compare_by_content_not_by_backing_buffer() {
262 let a = ByteSpan::from(b"abc".to_vec());
263 let b = ByteSpan::new(Arc::from(&b"xxabcxx"[..]), 2..5).unwrap();
264 assert_eq!(a, b);
265 assert!(ByteSpan::empty().is_empty());
266 }
267}