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