Skip to main content

pdfrum_parser/
decode.rs

1//! Getting a stream's real bytes.
2//!
3//! The decoding itself lives in `pdfrum-filters`, including the fallback
4//! ladder that turns every failure into the undecoded bytes. This module is
5//! only the parser's side of that boundary: two places inside the reader —
6//! cross-reference streams and object streams — need decoded bytes before
7//! anything else can happen, and consumers above need them on demand.
8
9use pdfrum_common::{Diagnostics, Limits};
10use pdfrum_object::{Resolve, Stream};
11
12/// A stream's data with its filters applied.
13///
14/// Never fails: a stream whose filter chain is unusable yields its raw bytes
15/// and a diagnostic, which is what makes a damaged file's content still
16/// render. A chain ending in an image codec yields the codec's *input*; the
17/// image path takes it from there.
18///
19/// ```
20/// use pdfrum_common::{Diagnostics, Limits};
21/// use pdfrum_object::{ByteSpan, Dict, Name, NoResolve, Object, Stream, names};
22/// use pdfrum_parser::decoded_stream;
23///
24/// let dict = Dict::from_pairs([(
25///     names::FILTER.clone(),
26///     Object::Name(Name::from("ASCIIHexDecode")),
27/// )]);
28/// let stream = Stream::new(dict, ByteSpan::from(b"48656c6c6f>".to_vec()));
29/// let mut diags = Diagnostics::default();
30/// let bytes = decoded_stream(&stream, &NoResolve, &Limits::default(), &mut diags);
31/// assert_eq!(bytes, b"Hello");
32/// ```
33#[must_use]
34pub fn decoded_stream(
35    stream: &Stream,
36    r: &impl Resolve,
37    limits: &Limits,
38    diags: &mut Diagnostics,
39) -> Vec<u8> {
40    decoded_bytes(stream, r, limits, diags)
41}
42
43/// The same, over a `?Sized` resolver so the store can call it behind a
44/// reference.
45pub(crate) fn decoded_bytes<R: Resolve + ?Sized>(
46    stream: &Stream,
47    r: &R,
48    limits: &Limits,
49    diags: &mut Diagnostics,
50) -> Vec<u8> {
51    pdfrum_filters::decode_chain(stream, 0, &r, limits, diags).data
52}
53
54/// Decoded bytes for a stream the *reader itself* has to understand — a
55/// cross-reference stream or an object stream.
56///
57/// Unlike a content stream, these are useless unless the whole chain
58/// produced real bytes. A chain that stops at an image codec has handed back
59/// that codec's input rather than the fields the reader is looking for, so
60/// this answers `None` and the caller falls through to its next repair
61/// instead of reading pixels as offsets.
62pub(crate) fn structural_bytes<R: Resolve + ?Sized>(
63    stream: &Stream,
64    r: &R,
65    limits: &Limits,
66    diags: &mut Diagnostics,
67) -> Option<Vec<u8>> {
68    let decoded = pdfrum_filters::decode_chain(stream, 0, &r, limits, diags);
69    decoded.image.is_none().then_some(decoded.data)
70}
71
72#[cfg(test)]
73mod tests {
74    use super::decoded_stream;
75    use pdfrum_common::{DiagKind, Diagnostics, Limits};
76    use pdfrum_object::{ByteSpan, Dict, Name, NoResolve, Object, Stream, names};
77
78    #[test]
79    fn an_unfiltered_stream_is_its_own_data() {
80        let stream = Stream::new(Dict::new(), ByteSpan::from(b"raw".to_vec()));
81        let mut diags = Diagnostics::default();
82        assert_eq!(
83            decoded_stream(&stream, &NoResolve, &Limits::default(), &mut diags),
84            b"raw"
85        );
86    }
87
88    #[test]
89    fn an_unusable_filter_declaration_yields_the_raw_bytes() {
90        let dict = Dict::from_pairs([(names::FILTER.clone(), Object::Int(7))]);
91        let stream = Stream::new(dict, ByteSpan::from(b"raw".to_vec()));
92        let mut diags = Diagnostics::default();
93        assert_eq!(
94            decoded_stream(&stream, &NoResolve, &Limits::default(), &mut diags),
95            b"raw"
96        );
97        assert!(diags.contains(&DiagKind::UndecodableStream));
98    }
99
100    #[test]
101    fn a_hex_filter_decodes() {
102        let dict = Dict::from_pairs([(
103            names::FILTER.clone(),
104            Object::Name(Name::from("ASCIIHexDecode")),
105        )]);
106        let stream = Stream::new(dict, ByteSpan::from(b"414243>".to_vec()));
107        let mut diags = Diagnostics::default();
108        assert_eq!(
109            decoded_stream(&stream, &NoResolve, &Limits::default(), &mut diags),
110            b"ABC"
111        );
112    }
113}