Skip to main content

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}