Skip to main content

pdfluent_ccitt/
lib.rs

1//! A decoder for CCITT fax-encoded images.
2//!
3//! This crate implements the CCITT Group 3 and Group 4 fax compression algorithms
4//! as defined in ITU-T Recommendations T.4 and T.6. These encodings are commonly
5//! used for bi-level (black and white) images in PDF documents and fax transmissions.
6//!
7//! The main entry point is the [`decode`] function, which takes encoded data, a
8//! [`DecoderContext`], and outputs the decoded pixels through a [`Decoder`] trait
9//! that can be implemented according to your needs.
10//!
11//! The crate is `no_std` compatible but requires an allocator to be available.
12//!
13//! # Safety
14//! Unsafe code is forbidden via a crate-level attribute.
15//!
16//! # License
17//! Licensed under either of
18//!
19//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
20//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
21//!
22//! at your option.
23//!
24//! [`decode`]: crate::decode
25//! [`Decoder`]: crate::Decoder
26
27#![no_std]
28#![forbid(unsafe_code)]
29#![forbid(missing_docs)]
30
31extern crate alloc;
32
33use crate::bit_reader::BitReader;
34
35use crate::decode::{EOFB, Mode};
36use alloc::vec;
37use alloc::vec::Vec;
38
39mod bit_reader;
40mod decode;
41mod state_machine;
42
43/// A specialized Result type for CCITT decoding operations.
44pub type Result<T> = core::result::Result<T, DecodeError>;
45
46/// An error that can occur during CCITT decoding.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum DecodeError {
49    /// Unexpected end of input while reading bits.
50    UnexpectedEof,
51    /// Invalid Huffman code sequence was encountered during decoding.
52    InvalidCode,
53    /// A scanline didn't have the expected number of pixels.
54    LineLengthMismatch,
55    /// Arithmetic overflow in run length or position calculation.
56    Overflow,
57}
58
59impl core::fmt::Display for DecodeError {
60    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
61        match self {
62            Self::UnexpectedEof => write!(f, "unexpected end of input"),
63            Self::InvalidCode => write!(f, "invalid CCITT code sequence"),
64            Self::LineLengthMismatch => write!(f, "scanline length mismatch"),
65            Self::Overflow => write!(f, "arithmetic overflow in position calculation"),
66        }
67    }
68}
69
70impl core::error::Error for DecodeError {}
71
72/// The encoding mode for CCITT fax decoding.
73#[derive(Copy, Clone, Debug, PartialEq, Eq)]
74pub enum EncodingMode {
75    /// Group 4 (MMR).
76    Group4,
77    /// Group 3 1D (MH).
78    Group3_1D,
79    /// Group 3 2D (MR).
80    Group3_2D {
81        /// The K parameter.
82        k: u32,
83    },
84}
85
86/// Settings to apply during decoding.
87#[derive(Copy, Clone, Debug)]
88pub struct DecodeSettings {
89    /// How many columns the image has (i.e. its width).
90    pub columns: u32,
91    /// How many rows the image has (i.e. its height).
92    ///
93    /// In case `end_of_block` has been set to true, decoding will run until
94    /// the given number of rows have been decoded, or the `end_of_block` marker
95    /// has been encountered, whichever occurs first.
96    pub rows: u32,
97    /// Whether the stream _MAY_ contain an end-of-block marker
98    /// (It doesn't have to. In that case this is set to `true` but there are
99    /// no end-of-block markers, hayro-ccitt will still use the value of `rows`
100    /// to determine when to stop decoding).
101    pub end_of_block: bool,
102    /// Whether the stream contains end-of-line markers.
103    pub end_of_line: bool,
104    /// Whether the data in the stream for each row is aligned to the byte
105    /// boundary.
106    pub rows_are_byte_aligned: bool,
107    /// The encoding mode used by the image.
108    pub encoding: EncodingMode,
109    /// Whether black and white should be inverted.
110    pub invert_black: bool,
111}
112
113/// Split a run of `count` pixels into the part that finishes the current byte,
114/// the whole bytes after it, and the remainder.
115///
116/// Upstream replaced `push_pixel` and `push_pixel_chunk` with a single
117/// `push_pixels`, which is the better shape: the implementor sees the whole run
118/// and decides how to write it. But every implementor that writes bytes rather
119/// than bits then needs the same three-way split, and an off-by-one in that
120/// arithmetic corrupts image data silently rather than failing.
121///
122/// `written` is how many bits the implementor has already put into its current
123/// byte, so `written % 8 == 0` means it is aligned and can start writing bytes
124/// immediately. Returns `(prefix_pixels, whole_bytes, tail_pixels)`, where
125/// `prefix_pixels + whole_bytes * 8 + tail_pixels == count`.
126///
127/// ```
128/// # use pdfluent_ccitt::split_run;
129/// assert_eq!(split_run(0, 24), (0, 3, 0));   // aligned, three whole bytes
130/// assert_eq!(split_run(3, 24), (5, 2, 3));   // three bits in: 5 + 16 + 3
131/// assert_eq!(split_run(0, 5), (0, 0, 5));    // too short for a byte
132/// ```
133pub fn split_run(written: u32, count: u32) -> (u32, u32, u32) {
134    let to_boundary = (8 - (written % 8)) % 8;
135    let prefix = count.min(to_boundary);
136    let rest = count - prefix;
137    (prefix, rest / 8, rest % 8)
138}
139
140/// A decoder for CCITT images.
141pub trait Decoder {
142    /// Push a run of pixels of the same color.
143    fn push_pixels(&mut self, white: bool, count: u32);
144    /// Called when a row has been completed.
145    fn next_line(&mut self);
146}
147
148/// Pixel color in a bi-level (black and white) image.
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub(crate) enum Color {
151    /// White pixel.
152    White,
153    /// Black pixel.
154    Black,
155}
156
157impl Color {
158    /// Returns the opposite color.
159    #[inline(always)]
160    fn opposite(self) -> Self {
161        match self {
162            Self::White => Self::Black,
163            Self::Black => Self::White,
164        }
165    }
166
167    /// Returns true if this color is white.
168    #[inline(always)]
169    fn is_white(self) -> bool {
170        matches!(self, Self::White)
171    }
172}
173
174/// Represents a color change at a specific index in a line.
175#[derive(Clone, Copy)]
176struct ColorChange {
177    idx: u32,
178    color: Color,
179}
180
181/// Decode the given image using the provided decoder context and decoder.
182///
183/// If decoding was successful, the number of bytes that have been read in total
184/// is returned.
185///
186/// If an error is returned, it means that the file is somehow malformed.
187/// However, even if that's the case, it is possible that a number
188/// of rows were decoded successfully and written into the decoder, so those
189/// can still be used, but the image might be truncated.
190pub fn decode(data: &[u8], decoder: &mut impl Decoder, ctx: &mut DecoderContext) -> Result<usize> {
191    ctx.reset();
192    let mut reader = BitReader::new(data);
193
194    match ctx.settings.encoding {
195        EncodingMode::Group4 => decode_group4(ctx, &mut reader, decoder)?,
196        EncodingMode::Group3_1D => decode_group3_1d(ctx, &mut reader, decoder)?,
197        EncodingMode::Group3_2D { .. } => decode_group3_2d(ctx, &mut reader, decoder)?,
198    }
199
200    reader.align();
201    Ok(reader.byte_pos())
202}
203
204/// Group 3 1D decoding (T.4 Section 4.1).
205fn decode_group3_1d(
206    ctx: &mut DecoderContext,
207    reader: &mut BitReader<'_>,
208    decoder: &mut impl Decoder,
209) -> Result<()> {
210    // It seems like PDF producers are a bit sloppy with the `end_of_line` flag,
211    // so we just always try to read one.
212    let _ = reader.read_eol_if_available();
213
214    loop {
215        decode_1d_line(ctx, reader, decoder)?;
216        ctx.next_line(reader, decoder)?;
217
218        if group3_check_eob(ctx, reader) {
219            break;
220        }
221    }
222
223    Ok(())
224}
225
226/// Group 3 2D decoding (T.4 Section 4.2).
227fn decode_group3_2d(
228    ctx: &mut DecoderContext,
229    reader: &mut BitReader<'_>,
230    decoder: &mut impl Decoder,
231) -> Result<()> {
232    // It seems like PDF producers are a bit sloppy with the `end_of_line` flag,
233    // so we just always try to read one.
234    let _ = reader.read_eol_if_available();
235
236    loop {
237        let tag_bit = reader.read_bit()?;
238
239        if tag_bit == 1 {
240            decode_1d_line(ctx, reader, decoder)?;
241        } else {
242            decode_2d_line(ctx, reader, decoder)?;
243        }
244
245        ctx.next_line(reader, decoder)?;
246
247        if group3_check_eob(ctx, reader) {
248            break;
249        }
250    }
251
252    Ok(())
253}
254
255/// Check for end-of-block, including RTC (T.4 Section 4.1.4).
256fn group3_check_eob(ctx: &mut DecoderContext, reader: &mut BitReader<'_>) -> bool {
257    let eol_count = reader.read_eol_if_available();
258
259    // T.4 Section 4.1.4: "The end of a document transmission is indicated by
260    // sending six consecutive EOLs."
261    // PDFBOX-2778 has 7 EOL, although it should only be 6. Let's be lenient
262    // and check with >=.
263    if ctx.settings.end_of_block && eol_count >= 6 {
264        return true;
265    }
266
267    if ctx.decoded_rows == ctx.settings.rows || reader.at_end() {
268        return true;
269    }
270
271    false
272}
273
274fn decode_group4(
275    ctx: &mut DecoderContext,
276    reader: &mut BitReader<'_>,
277    decoder: &mut impl Decoder,
278) -> Result<()> {
279    loop {
280        if ctx.settings.end_of_block && reader.peak_bits(24) == Ok(EOFB) {
281            reader.read_bits(24)?;
282            break;
283        }
284
285        if ctx.decoded_rows == ctx.settings.rows || reader.at_end() {
286            break;
287        }
288
289        decode_2d_line(ctx, reader, decoder)?;
290        ctx.next_line(reader, decoder)?;
291    }
292
293    Ok(())
294}
295
296/// Decode a single 1D-coded line (T.4 Section 4.1.1, T.6 Section 2.2.4).
297#[inline(always)]
298fn decode_1d_line(
299    ctx: &mut DecoderContext,
300    reader: &mut BitReader<'_>,
301    decoder: &mut impl Decoder,
302) -> Result<()> {
303    while !ctx.at_eol() {
304        let run_length = reader.decode_run(ctx.color)?;
305        ctx.push_pixels(decoder, run_length);
306        ctx.color = ctx.color.opposite();
307    }
308
309    Ok(())
310}
311
312/// Decode a single 2D-coded line (T.4 Section 4.2, T.6 Section 2.2).
313#[inline(always)]
314fn decode_2d_line(
315    ctx: &mut DecoderContext,
316    reader: &mut BitReader<'_>,
317    decoder: &mut impl Decoder,
318) -> Result<()> {
319    while !ctx.at_eol() {
320        let mode = reader.decode_mode()?;
321
322        match mode {
323            // Pass mode (T.4 Section 4.2.1.3.2a, T.6 Section 2.2.3.1).
324            Mode::Pass => {
325                ctx.push_pixels(decoder, ctx.b2() - ctx.a0().unwrap_or(0));
326                ctx.update_b();
327                // No color change happens in pass mode.
328            }
329            // Vertical mode (T.4 Section 4.2.1.3.2b, T.6 Section 2.2.3.2).
330            Mode::Vertical(i) => {
331                let b1 = ctx.b1();
332                let a1 = if i >= 0 {
333                    b1.checked_add(i as u32).ok_or(DecodeError::Overflow)?
334                } else {
335                    b1.checked_sub((-i) as u32).ok_or(DecodeError::Overflow)?
336                };
337
338                let a0 = ctx.a0().unwrap_or(0);
339
340                ctx.push_pixels(decoder, a1.checked_sub(a0).ok_or(DecodeError::Overflow)?);
341                ctx.color = ctx.color.opposite();
342
343                ctx.update_b();
344            }
345            // Horizontal mode (T.4 Section 4.2.1.3.2c, T.6 Section 2.2.3.3).
346            Mode::Horizontal => {
347                let a0a1 = reader.decode_run(ctx.color)?;
348                ctx.push_pixels(decoder, a0a1);
349                ctx.color = ctx.color.opposite();
350
351                let a1a2 = reader.decode_run(ctx.color)?;
352                ctx.push_pixels(decoder, a1a2);
353                ctx.color = ctx.color.opposite();
354
355                ctx.update_b();
356            }
357        }
358    }
359
360    Ok(())
361}
362
363/// A reusable context for decoding CCITT images.
364pub struct DecoderContext {
365    /// Color changes in the reference line (previous line).
366    ref_changes: Vec<ColorChange>,
367    /// The minimum index we need to start from when searching for b1.
368    ref_pos: u32,
369    /// The current index of b1.
370    b1_idx: u32,
371    /// Color changes in the coding line (current line being decoded).
372    coding_changes: Vec<ColorChange>,
373    /// Current position in the coding line (number of pixels decoded).
374    pixels_decoded: u32,
375    /// The width of a line in pixels (i.e. number of columns).
376    line_width: u32,
377    /// The color of the next run to be decoded.
378    color: Color,
379    /// How many rows have been decoded so far.
380    decoded_rows: u32,
381    /// The settings to apply during decoding.
382    settings: DecodeSettings,
383    /// Whether to invert black and white.
384    invert_black: bool,
385}
386
387impl DecoderContext {
388    /// Creates a new decoder context using the provided decode settings.
389    pub fn new(settings: DecodeSettings) -> Self {
390        Self {
391            ref_changes: vec![],
392            ref_pos: 0,
393            b1_idx: 0,
394            coding_changes: Vec::new(),
395            pixels_decoded: 0,
396            line_width: settings.columns,
397            // Each run starts with an imaginary white pixel on the left.
398            color: Color::White,
399            decoded_rows: 0,
400            settings,
401            invert_black: settings.invert_black,
402        }
403    }
404
405    fn reset(&mut self) {
406        self.ref_changes.clear();
407        self.ref_pos = 0;
408        self.b1_idx = 0;
409        self.coding_changes.clear();
410        self.pixels_decoded = 0;
411        self.line_width = self.settings.columns;
412        self.color = Color::White;
413        self.decoded_rows = 0;
414        self.invert_black = self.settings.invert_black;
415    }
416
417    /// `a0` refers to the first changing element on the current line.
418    #[inline(always)]
419    fn a0(&self) -> Option<u32> {
420        if self.pixels_decoded == 0 {
421            // If we haven't coded anything yet, a0 conceptually points at the
422            // index -1. This is a bit of an edge case, and we therefore require
423            // callers of this method to handle the case themselves.
424            None
425        } else {
426            // Otherwise, the index points to the next element to be decoded.
427            Some(self.pixels_decoded)
428        }
429    }
430
431    /// "The first changing element on the reference line to the right of a0 and
432    /// of opposite color to a0."
433    #[inline(always)]
434    fn b1(&self) -> u32 {
435        self.ref_changes
436            .get(self.b1_idx as usize)
437            .map_or(self.line_width, |c| c.idx)
438    }
439
440    /// "The next changing element to the right of b1, on the reference line."
441    #[inline(always)]
442    fn b2(&self) -> u32 {
443        self.ref_changes
444            .get(self.b1_idx as usize + 1)
445            .map_or(self.line_width, |c| c.idx)
446    }
447
448    /// Compute the new position of b1 (and implicitly b2).
449    #[inline(always)]
450    fn update_b(&mut self) {
451        // b1 refers to an element of the opposite color.
452        let target_color = self.color.opposite();
453        // b1 must be strictly greater than a0.
454        let min_idx = self.a0().map_or(0, |a| a + 1);
455
456        self.b1_idx = self.line_width;
457
458        for i in self.ref_pos..self.ref_changes.len() as u32 {
459            let change = &self.ref_changes[i as usize];
460
461            if change.idx < min_idx {
462                self.ref_pos = i + 1;
463                continue;
464            }
465
466            if change.color == target_color {
467                self.b1_idx = i;
468                break;
469            }
470        }
471    }
472
473    #[inline(always)]
474    fn push_pixels(&mut self, decoder: &mut impl Decoder, count: u32) {
475        // Make sure we don't have too many pixels (for invalid files).
476        let count = count.min(self.line_width - self.pixels_decoded);
477        let white = self.color.is_white() ^ self.invert_black;
478        decoder.push_pixels(white, count);
479
480        // Track the color change:
481        // - At start of line (no previous changes): only add if color differs from
482        //   imaginary white, i.e., only add if black.
483        // - Mid-line: only add if color differs from previous.
484        if count > 0 {
485            let is_change = self
486                .coding_changes
487                .last()
488                .map_or(!self.color.is_white(), |last| last.color != self.color);
489            if is_change {
490                self.coding_changes.push(ColorChange {
491                    idx: self.pixels_decoded,
492                    color: self.color,
493                });
494            }
495            self.pixels_decoded += count;
496        }
497    }
498
499    #[inline(always)]
500    fn at_eol(&self) -> bool {
501        self.a0().unwrap_or(0) == self.line_width
502    }
503
504    #[inline(always)]
505    fn next_line(&mut self, reader: &mut BitReader<'_>, decoder: &mut impl Decoder) -> Result<()> {
506        if self.pixels_decoded != self.settings.columns {
507            return Err(DecodeError::LineLengthMismatch);
508        }
509
510        core::mem::swap(&mut self.ref_changes, &mut self.coding_changes);
511        self.coding_changes.clear();
512        self.pixels_decoded = 0;
513        self.ref_pos = 0;
514        self.b1_idx = 0;
515        self.color = Color::White;
516        self.decoded_rows += 1;
517        decoder.next_line();
518
519        if self.settings.rows_are_byte_aligned {
520            reader.align();
521        }
522
523        self.update_b();
524
525        Ok(())
526    }
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532    use alloc::vec::Vec;
533
534    // Collects decoded pixels row by row. true = white, false = black.
535    struct PixelCollector {
536        rows: Vec<Vec<bool>>,
537        current: Vec<bool>,
538    }
539
540    impl PixelCollector {
541        fn new() -> Self {
542            Self {
543                rows: Vec::new(),
544                current: Vec::new(),
545            }
546        }
547    }
548
549    impl Decoder for PixelCollector {
550        fn push_pixels(&mut self, white: bool, count: u32) {
551            for _ in 0..count {
552                self.current.push(white);
553            }
554        }
555
556        fn next_line(&mut self) {
557            self.rows.push(core::mem::take(&mut self.current));
558        }
559    }
560
561    fn g3_1d(columns: u32, rows: u32) -> DecodeSettings {
562        DecodeSettings {
563            columns,
564            rows,
565            end_of_block: false,
566            end_of_line: false,
567            rows_are_byte_aligned: false,
568            encoding: EncodingMode::Group3_1D,
569            invert_black: false,
570        }
571    }
572
573    fn g4(columns: u32, rows: u32) -> DecodeSettings {
574        DecodeSettings {
575            columns,
576            rows,
577            end_of_block: false,
578            end_of_line: false,
579            rows_are_byte_aligned: false,
580            encoding: EncodingMode::Group4,
581            invert_black: false,
582        }
583    }
584
585    const W: bool = true;
586    const B: bool = false;
587
588    // Group 3 1D: 4-column all-white row.
589    // White(4) = 0b1011 → padded to byte 0b10110000 = 0xB0.
590    #[test]
591    fn g3_1d_all_white() {
592        let mut sink = PixelCollector::new();
593        decode(&[0xB0], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap();
594        assert_eq!(sink.rows, [[W, W, W, W]]);
595    }
596
597    // Group 3 1D: 4 cols, W(2)+B(2).
598    // White(2)=0b0111 (4 bits), Black(2)=0b11 (2 bits) → 0b01111100 = 0x7C.
599    #[test]
600    fn g3_1d_white_then_black() {
601        let mut sink = PixelCollector::new();
602        decode(&[0x7C], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap();
603        assert_eq!(sink.rows, [[W, W, B, B]]);
604    }
605
606    // Group 3 1D: 4-column all-black row.
607    // White(0)=0b00110101 (8 bits), Black(4)=0b0011 (4 bits) → [0x35, 0x30].
608    #[test]
609    fn g3_1d_all_black() {
610        let mut sink = PixelCollector::new();
611        decode(
612            &[0x35, 0x30],
613            &mut sink,
614            &mut DecoderContext::new(g3_1d(4, 1)),
615        )
616        .unwrap();
617        assert_eq!(sink.rows, [[B, B, B, B]]);
618    }
619
620    // Group 3 1D: two rows of 4 white pixels each.
621    // W(4) W(4) = 0b10111011 = 0xBB.
622    #[test]
623    fn g3_1d_two_rows() {
624        let mut sink = PixelCollector::new();
625        decode(&[0xBB], &mut sink, &mut DecoderContext::new(g3_1d(4, 2))).unwrap();
626        assert_eq!(sink.rows, [[W, W, W, W], [W, W, W, W]]);
627    }
628
629    // Group 4: 4-column all-white row.
630    // V(0)=0b1 (1 bit) → a1=b1=4 → 4 white pixels → 0b10000000 = 0x80.
631    #[test]
632    fn group4_all_white() {
633        let mut sink = PixelCollector::new();
634        decode(&[0x80], &mut sink, &mut DecoderContext::new(g4(4, 1))).unwrap();
635        assert_eq!(sink.rows, [[W, W, W, W]]);
636    }
637
638    // invert_black: a white-coded pixel should be returned as black when the flag is set.
639    // 1 col, White(1) = 0b000111 → 0b00011100 = 0x1C.
640    #[test]
641    fn invert_black_flag() {
642        let mut sink = PixelCollector::new();
643        let settings = DecodeSettings {
644            invert_black: true,
645            ..g3_1d(1, 1)
646        };
647        decode(&[0x1C], &mut sink, &mut DecoderContext::new(settings)).unwrap();
648        assert_eq!(sink.rows, [[B]]);
649    }
650
651    // Empty input must return UnexpectedEof immediately.
652    #[test]
653    fn empty_data_returns_unexpected_eof() {
654        let mut sink = PixelCollector::new();
655        let err = decode(&[], &mut sink, &mut DecoderContext::new(g3_1d(4, 1))).unwrap_err();
656        assert_eq!(err, DecodeError::UnexpectedEof);
657    }
658}
659
660#[cfg(test)]
661mod split_run_tests {
662    use super::split_run;
663
664    /// The three parts must always add back up to the run, at every alignment.
665    ///
666    /// Written as an exhaustive sweep rather than a handful of cases because
667    /// the failure mode is an off-by-one at one specific alignment, which is
668    /// exactly what a handful of cases misses.
669    #[test]
670    fn the_parts_always_reconstruct_the_run() {
671        for written in 0..32u32 {
672            for count in 0..200u32 {
673                let (prefix, bytes, tail) = split_run(written, count);
674                assert_eq!(
675                    prefix + bytes * 8 + tail,
676                    count,
677                    "written={written} count={count} split into {prefix}+{bytes}*8+{tail}"
678                );
679                assert!(tail < 8, "tail {tail} should have been a whole byte");
680                assert!(
681                    prefix == count || (written + prefix) % 8 == 0,
682                    "written={written} count={count}: prefix {prefix} left the writer unaligned"
683                );
684            }
685        }
686    }
687
688    /// An aligned writer must not be handed a prefix: that would push the fast
689    /// path off by a byte for every run.
690    #[test]
691    fn an_aligned_writer_gets_no_prefix() {
692        for written in [0u32, 8, 16, 800] {
693            assert_eq!(split_run(written, 24), (0, 3, 0), "written={written}");
694        }
695    }
696}