mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
pdfcraft-filters
PDF stream filters: the decoders and encoders behind /Filter and /DecodeParms.
- Layer: L0, standalone. No dependency on any other PdfCraft crate.
cosdepends on it (architecture §3 rule 3). - Licence: MIT OR Apache-2.0. Clean-room: written from ISO 32000-2 §7.4, RFC 1950/1951 (through
flate2with the pure-Rustminiz_oxidebackend), TIFF 6.0 §14 (predictor 2) and RFC 2083 §6 (PNG filters). #![forbid(unsafe_code)]. Builds forwasm32-unknown-unknown.
API
pub enum Filter { Flate, Lzw, AsciiHex, Ascii85, RunLength, Dct, Jpx, Jbig2, CcittFax, Crypt, Unknown(String) }
Filter::from_name(b"FlateDecode" | b"Fl" | ...) -> Filter // full names and inline-image abbreviations
Filter::name(&self) -> &str; Filter::is_image_codec(&self) -> bool
pub struct Params { predictor, colors, bits_per_component, columns, early_change } // i64s; Default = 1, 1, 8, 1, 1
pub enum FilterError { Corrupt { filter, detail }, LimitExceeded(usize), Unsupported(String) }
decode(chain, data, max_output) -> Result<Vec<u8>, FilterError>
decode_counted(chain, data, max_output) -> Result<(Vec<u8>, usize /*decoded*/), (FilterError, usize /*decoded*/)>
decode_tolerant(chain, data, max_output) -> Result<(Vec<u8>, bool /*partial*/), FilterError>
decode_tolerant_counted(chain, data, max_output) -> Result<(Vec<u8>, bool /*partial*/, usize /*decoded*/), (FilterError, usize /*decoded*/)>
encode(filter, params, data) -> Result<Vec<u8>, FilterError>
encode_flate(data) -> Vec<u8> // zlib, level 6
chainis applied first to last, as in a/Filterarray.- Image codecs (DCT, JPX, JBIG2, CCITT),
Cryptand unknown filters anywhere in a chain giveUnsupported: callers keep such data encoded. So does a chain longer thanMAX_CHAIN(8) filters, before any stage runs. Decryption belongs tocrypt. max_outputbounds every intermediate buffer (including the pre-predictor buffer) and givesLimitExceeded(max_output). This also holds when Flate retries raw deflate after a misleading zlib header, in both strict and tolerant decoding.decode_countedanddecode_tolerant_countedare for a caller charging a budget (pdfcraft_cos::DecodeBudget).max_outputbounds all stages together (each stage gets what the earlier ones left), and they give the bytes every stage that ran decoded, on success and on failure. A stage's output counts as its decoder produced it, before a predictor or a later stage shrinks it, and a failed decode pays for what it decoded first.decode_tolerantturnsCorruptinto "what was decoded so far" pluspartial = true, then runs the rest of the chain on it (predictors are applied to partial output too).LimitExceededandUnsupportedare still errors.
Behaviour
| Filter | Decode | Encode |
|---|---|---|
| FlateDecode | zlib header checked, then inflated raw: a missing or wrong Adler-32 is tolerated; no valid header → raw deflate; trailing bytes ignored; preset dictionary → Corrupt; truncated → Corrupt (partial in tolerant mode) | zlib level 6 |
| LZWDecode | 9–12-bit MSB-first codes, clear (256), EOD (257), EarlyChange 0/1; missing EOD/initial clear tolerated; a full table stops growing |
greedy, leading clear, clear-table when the table fills, EOD |
| Predictors (Flate/LZW) | 2 = TIFF (1/2/4/8/16 bpc), 10–15 = PNG with per-row tags 0–4; partial final rows decoded; bad params or tags → Corrupt | 2, 10 None, 11 Sub, 12 Up, 13 Average, 14 Paeth, 15 per-row optimum (min sum of signed residuals) |
| ASCIIHexDecode | white space ignored, odd digit padded with 0, > EOD (optional), other characters → Corrupt |
upper case, newline every 64 digits, > |
| ASCII85Decode | <~ prefix, z, white space, ~> (or bare ~, or none) EOD, final partial group; z inside a group, a 1-char final group, overflow or bad characters → Corrupt |
z for zero groups, newline every 75 characters, ~> |
| RunLengthDecode | 0–127 literal, 129–255 repeat, 128 EOD (optional); truncated run → Corrupt | runs of ≥2 repeat, literals up to 128, 128 |
Tests
- Unit tests per filter with hand-computed vectors (the ISO 32000-2 LZW example, RFC 1951 stored blocks, PNG/TIFF rows).
- LZW is cross-checked against
weezl(dev-dependency) in both directions for both early-change settings. tests/proptests.rs: encode→decode round trips for every encodable filter × predictor × colors × bpc × columns, two-filter chains, output-limit checks, and fuzz-style no-panic tests (random bytes, random/nonsense params, bit-flipped valid streams).fuzz/: a detached cargo-fuzz crate (cargo +nightly fuzz run decode, run fromcrates/filters/).
Status
M1.1/M1.2 for the general-purpose filters are done. Not yet here: the image codecs (DCT via zune-jpeg, JPX via hayro-jpeg2000, JBIG2, CCITT G3/G4 decode and G4 encode, DCT encode) and Flate encode levels other than 6.