Skip to main content

docling_pdf/raster/
filters.rs

1//! The standard (non-image) stream filters an image XObject may wrap its
2//! samples in — `FlateDecode`, `LZWDecode`, `RunLengthDecode`,
3//! `ASCII85Decode`, `ASCIIHexDecode` — with the PNG and TIFF predictors of
4//! `/DecodeParms`. The image codecs (`DCTDecode` → [`super::jpeg`],
5//! `CCITTFaxDecode` → [`super::fax`], `JBIG2Decode` → the renderer's
6//! `jbig2` (#811), `JPXDecode` → its `jpx`) are reported to the caller as the
7//! remaining filter rather than applied here.
8//!
9//! Written out rather than borrowed from lopdf's `Stream::decompressed_content`
10//! because that one sizes the PNG predictor's rows for 8-bit samples only
11//! (`max(8, BitsPerComponent)`), which is wrong for the 1-bit scans this
12//! module exists for, and has no `RunLengthDecode`/`ASCIIHexDecode`/TIFF
13//! predictor.
14
15use std::io::Read;
16
17use lopdf::{Dictionary, Document, Object};
18
19/// A filter left for an image codec, with its `/DecodeParms`.
20#[derive(Debug, Clone)]
21pub struct ImageCodec {
22    pub name: String,
23    pub parms: Option<Dictionary>,
24}
25
26/// Why a stream could not be decoded here.
27#[derive(Debug)]
28pub enum Unsupported {
29    Filter(String),
30    Predictor(i64),
31    Corrupt(&'static str),
32}
33
34fn deref<'a>(doc: &'a Document, obj: &'a Object) -> &'a Object {
35    match obj {
36        Object::Reference(id) => doc.get_object(*id).unwrap_or(obj),
37        o => o,
38    }
39}
40
41/// The `/Filter` names and their aligned `/DecodeParms` dictionaries.
42pub fn filters(doc: &Document, dict: &Dictionary) -> Vec<(String, Option<Dictionary>)> {
43    let names: Vec<String> = match dict
44        .get(b"Filter")
45        .ok()
46        .or_else(|| {
47            dict.get(b"F")
48                .ok()
49                .filter(|o| !matches!(o, Object::String(..)))
50        })
51        .map(|o| deref(doc, o))
52    {
53        Some(Object::Name(n)) => vec![String::from_utf8_lossy(n).into_owned()],
54        Some(Object::Array(a)) => a
55            .iter()
56            .filter_map(|o| match deref(doc, o) {
57                Object::Name(n) => Some(String::from_utf8_lossy(n).into_owned()),
58                _ => None,
59            })
60            .collect(),
61        _ => Vec::new(),
62    };
63    let parms: Vec<Option<Dictionary>> = match dict
64        .get(b"DecodeParms")
65        .ok()
66        .or_else(|| dict.get(b"DP").ok())
67        .map(|o| deref(doc, o))
68    {
69        Some(Object::Dictionary(d)) => vec![Some(d.clone())],
70        Some(Object::Array(a)) => a
71            .iter()
72            .map(|o| match deref(doc, o) {
73                Object::Dictionary(d) => Some(d.clone()),
74                _ => None,
75            })
76            .collect(),
77        _ => Vec::new(),
78    };
79    names
80        .into_iter()
81        .enumerate()
82        .map(|(i, n)| (n, parms.get(i).cloned().flatten()))
83        .collect()
84}
85
86/// Apply every standard filter of `chain` in order to `data`; stop at an
87/// image codec and hand it back. `/DecodeParms` predictors are undone after
88/// Flate/LZW as the spec has it.
89pub fn apply(
90    doc: &Document,
91    data: &[u8],
92    chain: &[(String, Option<Dictionary>)],
93) -> Result<(Vec<u8>, Option<ImageCodec>), Unsupported> {
94    let mut cur: Vec<u8> = data.to_vec();
95    for (name, parms) in chain {
96        cur = match name.as_str() {
97            "FlateDecode" | "Fl" => predictor(doc, inflate(&cur), parms.as_ref())?,
98            "LZWDecode" | "LZW" => {
99                let early = parms
100                    .as_ref()
101                    .and_then(|p| p.get(b"EarlyChange").ok())
102                    .and_then(|o| deref(doc, o).as_i64().ok())
103                    .map(|v| v != 0)
104                    .unwrap_or(true);
105                predictor(doc, lzw(&cur, early), parms.as_ref())?
106            }
107            "RunLengthDecode" | "RL" => run_length(&cur),
108            "ASCII85Decode" | "A85" => ascii85(&cur),
109            "ASCIIHexDecode" | "AHx" => ascii_hex(&cur),
110            "DCTDecode" | "DCT" | "JPXDecode" | "JBIG2Decode" | "CCITTFaxDecode" | "CCF" => {
111                return Ok((
112                    cur,
113                    Some(ImageCodec {
114                        name: match name.as_str() {
115                            "DCT" => "DCTDecode".into(),
116                            "CCF" => "CCITTFaxDecode".into(),
117                            n => n.to_string(),
118                        },
119                        parms: parms.clone(),
120                    }),
121                ));
122            }
123            other => return Err(Unsupported::Filter(other.to_string())),
124        };
125    }
126    Ok((cur, None))
127}
128
129/// zlib inflate, keeping what a damaged stream yielded before the error — as
130/// pdfium's `FlateModule` (and lopdf) do.
131fn inflate(data: &[u8]) -> Vec<u8> {
132    let mut out = Vec::new();
133    let mut dec = flate2::read::ZlibDecoder::new(data);
134    let mut buf = [0u8; 1 << 16];
135    loop {
136        match dec.read(&mut buf) {
137            Ok(0) => break,
138            Ok(n) => out.extend_from_slice(&buf[..n]),
139            Err(_) => break,
140        }
141    }
142    out
143}
144
145/// PDF LZW: MSB-first codes starting at 9 bits, `EarlyChange` 1 by default.
146fn lzw(data: &[u8], early_change: bool) -> Vec<u8> {
147    use weezl::{decode::Decoder, BitOrder};
148    let mut dec = if early_change {
149        Decoder::with_tiff_size_switch(BitOrder::Msb, 8)
150    } else {
151        Decoder::new(BitOrder::Msb, 8)
152    };
153    let mut out = Vec::new();
154    let mut input = data;
155    let mut buf = vec![0u8; 1 << 16];
156    loop {
157        let r = dec.decode_bytes(input, &mut buf);
158        out.extend_from_slice(&buf[..r.consumed_out]);
159        input = &input[r.consumed_in..];
160        match r.status {
161            Ok(weezl::LzwStatus::Done) | Err(_) => break,
162            Ok(_) if r.consumed_in == 0 && r.consumed_out == 0 => break,
163            Ok(_) => {}
164        }
165    }
166    out
167}
168
169fn run_length(data: &[u8]) -> Vec<u8> {
170    let mut out = Vec::new();
171    let mut i = 0;
172    while i < data.len() {
173        let l = data[i] as usize;
174        i += 1;
175        match l {
176            128 => break,
177            0..=127 => {
178                let end = (i + l + 1).min(data.len());
179                out.extend_from_slice(&data[i..end]);
180                i = end;
181            }
182            _ => {
183                if let Some(&b) = data.get(i) {
184                    out.extend(std::iter::repeat_n(b, 257 - l));
185                }
186                i += 1;
187            }
188        }
189    }
190    out
191}
192
193fn ascii_hex(data: &[u8]) -> Vec<u8> {
194    let mut out = Vec::new();
195    let mut hi: Option<u8> = None;
196    for &c in data {
197        if c == b'>' {
198            break;
199        }
200        let v = match c {
201            b'0'..=b'9' => c - b'0',
202            b'a'..=b'f' => c - b'a' + 10,
203            b'A'..=b'F' => c - b'A' + 10,
204            _ => continue,
205        };
206        match hi.take() {
207            None => hi = Some(v),
208            Some(h) => out.push(h << 4 | v),
209        }
210    }
211    if let Some(h) = hi {
212        out.push(h << 4);
213    }
214    out
215}
216
217fn ascii85(data: &[u8]) -> Vec<u8> {
218    let mut out = Vec::new();
219    let mut group = [0u8; 5];
220    let mut n = 0;
221    let mut i = 0;
222    if data.starts_with(b"<~") {
223        i = 2;
224    }
225    while i < data.len() {
226        let c = data[i];
227        i += 1;
228        match c {
229            b'~' => break,
230            b'z' if n == 0 => out.extend_from_slice(&[0, 0, 0, 0]),
231            b'!'..=b'u' => {
232                group[n] = c - b'!';
233                n += 1;
234                if n == 5 {
235                    let v = group.iter().fold(0u32, |acc, &d| {
236                        acc.wrapping_mul(85).wrapping_add(u32::from(d))
237                    });
238                    out.extend_from_slice(&v.to_be_bytes());
239                    n = 0;
240                }
241            }
242            _ => {}
243        }
244    }
245    if n > 0 {
246        for g in group.iter_mut().skip(n) {
247            *g = 84;
248        }
249        let v = group.iter().fold(0u32, |acc, &d| {
250            acc.wrapping_mul(85).wrapping_add(u32::from(d))
251        });
252        out.extend_from_slice(&v.to_be_bytes()[..n - 1]);
253    }
254    out
255}
256
257/// Undo a `/Predictor` (PNG 10–15 per-row filters, TIFF 2) from `/DecodeParms`.
258fn predictor(
259    doc: &Document,
260    data: Vec<u8>,
261    parms: Option<&Dictionary>,
262) -> Result<Vec<u8>, Unsupported> {
263    let Some(p) = parms else { return Ok(data) };
264    let int = |k: &[u8], d: i64| {
265        p.get(k)
266            .ok()
267            .and_then(|o| deref(doc, o).as_i64().ok())
268            .unwrap_or(d)
269    };
270    let pred = int(b"Predictor", 1);
271    if pred <= 1 {
272        return Ok(data);
273    }
274    let colors = int(b"Colors", 1).max(1) as usize;
275    let bpc = int(b"BitsPerComponent", 8).max(1) as usize;
276    let columns = int(b"Columns", 1).max(1) as usize;
277    let bpp = (colors * bpc).div_ceil(8).max(1);
278    let row_len = (colors * bpc * columns).div_ceil(8);
279    match pred {
280        2 => {
281            if bpc != 8 {
282                return Err(Unsupported::Predictor(pred));
283            }
284            let mut data = data;
285            for row in data.chunks_exact_mut(row_len) {
286                for i in bpp..row.len() {
287                    row[i] = row[i].wrapping_add(row[i - bpp]);
288                }
289            }
290            Ok(data)
291        }
292        10..=15 => {
293            let mut out = Vec::with_capacity(data.len());
294            let mut prev = vec![0u8; row_len];
295            for chunk in data.chunks(row_len + 1) {
296                let Some((&ft, row)) = chunk.split_first() else {
297                    break;
298                };
299                let mut cur = row.to_vec();
300                cur.resize(row_len, 0);
301                for i in 0..row_len {
302                    let a = if i >= bpp { cur[i - bpp] } else { 0 };
303                    let b = prev[i];
304                    let c = if i >= bpp { prev[i - bpp] } else { 0 };
305                    let x = cur[i];
306                    cur[i] = match ft {
307                        0 => x,
308                        1 => x.wrapping_add(a),
309                        2 => x.wrapping_add(b),
310                        3 => x.wrapping_add(((u16::from(a) + u16::from(b)) / 2) as u8),
311                        4 => {
312                            let (ia, ib, ic) = (i16::from(a), i16::from(b), i16::from(c));
313                            let pa = (ib - ic).abs();
314                            let pb = (ia - ic).abs();
315                            let pc = (ia + ib - 2 * ic).abs();
316                            let pr = if pa <= pb && pa <= pc {
317                                a
318                            } else if pb <= pc {
319                                b
320                            } else {
321                                c
322                            };
323                            x.wrapping_add(pr)
324                        }
325                        _ => return Err(Unsupported::Corrupt("PNG predictor row filter")),
326                    };
327                }
328                out.extend_from_slice(&cur);
329                prev = cur;
330            }
331            Ok(out)
332        }
333        other => Err(Unsupported::Predictor(other)),
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn run_length_and_ascii_filters() {
343        assert_eq!(
344            run_length(&[2, b'a', b'b', b'c', 254, b'z', 128]),
345            b"abczzz"
346        );
347        assert_eq!(ascii_hex(b"48 65 6C6c 6F>"), b"Hello");
348        assert_eq!(ascii85(b"<~87cURD]i,\"Ebo80~>"), b"Hello World!");
349        assert_eq!(ascii85(b"z~>"), [0, 0, 0, 0]);
350    }
351
352    #[test]
353    fn png_up_predictor_on_one_bit_rows() {
354        // Two 16-pixel 1-bit rows (2 bytes each), the second `Up`-filtered.
355        let doc = Document::new();
356        let mut parms = Dictionary::new();
357        parms.set("Predictor", 12);
358        parms.set("Colors", 1);
359        parms.set("BitsPerComponent", 1);
360        parms.set("Columns", 16);
361        let data = vec![0u8, 0b1010_1010, 0b0000_1111, 2, 0b0101_0101, 0b1111_0000];
362        let out = predictor(&doc, data, Some(&parms)).unwrap();
363        assert_eq!(
364            out,
365            vec![0b1010_1010, 0b0000_1111, 0b1111_1111, 0b1111_1111]
366        );
367    }
368
369    #[test]
370    fn flate_round_trip_with_trailing_garbage() {
371        use std::io::Write;
372        let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
373        enc.write_all(b"scan line bytes").unwrap();
374        let mut z = enc.finish().unwrap();
375        z.extend_from_slice(b"junk");
376        assert_eq!(inflate(&z), b"scan line bytes");
377    }
378}