use crate::error::Result; /// T.4 Modified Huffman (MH) Encoder for Group 3 fax /// Each scan line is encoded independently using run-length Huffman coding. pub struct MhEncoder; /// White run terminating codes (0-63) const WHITE_TERMINATING: [&str; 64] = [ "00110101", "000111", "0111", "1000", "1011", "1100", "1110", "1111", "10011", "10100", "00111", "01000", "001000", "000011", "1101", "11101", "101011", "101100", "010011", "010100", "010101", "1001001", "101000", "0100100", "0100101", "0010011", "010110", "001100", "001101", "010111", "011000", "011001", "011010", "011011", "0100111", "0011100", "0011101", "0011110", "0011111", "0100000", "0100001", "0100010", "0100011", "0100100", "0100101", "0100110", "0100111", "00110100", "00110101", "00110110", "00110111", "00111000", "00111001", "00111010", "00111011", "00111100", "00111101", "00111110", "00111111", "000000111", "000001000", "000001001", "000001010", "000001011", ]; /// White run makeup codes (64, 128, 192, ..., 2560) const WHITE_MAKEUP: [&str; 40] = [ "11011", "10010", "010111", "0110111", "00110110", "00110111", "01100100", "01100101", "01101000", "01101001", "01101010", "01101011", "01101100", "01101101", "01101110", "01101111", "010011000", "010011001", "010011010", "010011011", "001100000", "001100001", "001100010", "001100011", "001100100", "001100101", "001100110", "001100111", "001101000", "001101001", "001101010", "001101011", "0000000100", "0000000101", "0000000110", "0000000111", "0000001000", "0000001001", "0000001010", "0000001011", ]; /// Black run terminating codes (0-63) const BLACK_TERMINATING: [&str; 64] = [ "0000110111", "0000010", "0000011", "0000100", "0000101", "0000110", "0000111", "0001000", "0001001", "0001010", "0001011", "0001100", "0001101", "0001110", "0001111", "0010000", "0010001", "0010010", "0010011", "0010100", "0010101", "0010110", "0010111", "0011000", "0011001", "0011010", "0011011", "0011100", "0011101", "0011110", "0011111", "000000010", "000000011", "0000001010", "0000001011", "0000001100", "0000001101", "0000001110", "0000001111", "0000010000", "0000010001", "0000010010", "0000010011", "0000010100", "0000010101", "0000010110", "0000010111", "0000011000", "0000011001", "0000011010", "0000011011", "0000011100", "0000011101", "0000011110", "0000011111", "0000100000", "0000100001", "0000100010", "0000100011", "0000100100", "0000100101", "0000100110", "0000100111", "0000101000", ]; /// Black run makeup codes (64, 128, 192, ..., 2560) const BLACK_MAKEUP: [&str; 40] = [ "0000001111", "0000001000", "0000001001", "0000001010", "0000001011", "0000001100", "0000001101", "0000001110", "0000001111", "0000010000", "0000010001", "0000010010", "0000010011", "0000010100", "0000010101", "0000010110", "0000010111", "0000011000", "0000011001", "0000011010", "0000011011", "0000011100", "0000011101", "0000011110", "0000011111", "0000100000", "0000100001", "0000100010", "0000100011", "0000100100", "0000100101", "0000100110", "0000100111", "0000101000", "0000101001", "0000101010", "0000101011", "0000101100", "0000101101", "0000101110", ]; /// EOL (End of Line) code - always 11 zero bits followed by 1 const EOL: &str = "000000000001"; /// Makeup code values const MAKEUP_VALUES: [u32; 40] = [ 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, ]; /// Encode a single scan line of 1-bit pixels into MH format fn encode_line(pixels: &[u8], width: usize) -> String { let mut bits = String::new(); let mut is_white = true; // Lines start with white let mut run_length = 0usize; // Process each pixel for pel in 0..width { let byte_idx = pel / 8; let bit_idx = 7 - (pel % 8); let pixel_is_black = if byte_idx < pixels.len() { (pixels[byte_idx] >> bit_idx) & 1 == 1 } else { false }; if pixel_is_black == is_white { // Run ended, encode it bits.push_str(&encode_run(run_length, is_white)); run_length = 0; is_white = !is_white; } run_length += 1; } // Encode final run if run_length > 0 { bits.push_str(&encode_run(run_length, is_white)); } // Add EOL bits.push_str(EOL); bits } /// Encode a run of pixels (white or black) using T.4 Huffman codes fn encode_run(length: usize, is_white: bool) -> String { let mut bits = String::new(); let mut remaining = length as u32; // Encode long runs (> 63) with makeup codes + terminating code while remaining > 63 { // Find appropriate makeup code let makeup_idx = find_makeup_index(remaining); if is_white { bits.push_str(WHITE_MAKEUP[makeup_idx]); } else { bits.push_str(BLACK_MAKEUP[makeup_idx]); } remaining -= MAKEUP_VALUES[makeup_idx]; } // Encode remaining with terminating code let term_idx = remaining as usize; if is_white { bits.push_str(WHITE_TERMINATING[term_idx]); } else { bits.push_str(BLACK_TERMINATING[term_idx]); } bits } /// Find makeup code index for runs > 63 fn find_makeup_index(length: u32) -> usize { for (i, val) in MAKEUP_VALUES.iter().enumerate() { if length >= *val { if i == MAKEUP_VALUES.len() - 1 || length < MAKEUP_VALUES[i + 1] { return i; } } } 0 } /// Convert bit string to byte array with byte alignment fn bits_to_bytes(bits: &str) -> Vec { let mut bytes = Vec::new(); let mut current_byte = 0u8; let mut bit_count = 0u8; for c in bits.chars() { if c == '1' { current_byte |= 1 << (7 - bit_count); } bit_count += 1; if bit_count == 8 { bytes.push(current_byte); current_byte = 0; bit_count = 0; } } // Pad remaining bits with zeros (fill bits) if bit_count > 0 { bytes.push(current_byte); } bytes } impl MhEncoder { /// Encode 1-bit pixel data into T.4 MH format /// Input: raw 1-bit pixels (MSB first, row-major), width in pixels, number of rows /// Output: MH-encoded byte stream ready for modem transmission pub fn encode(pixels: &[u8], width: u32, rows: u32) -> Result> { let width = width as usize; let rows = rows as usize; let bytes_per_row = (width + 7) / 8; let mut all_bits = String::new(); for row in 0..rows { let start = row * bytes_per_row; let end = (start + bytes_per_row).min(pixels.len()); let row_pixels = &pixels[start..end]; let line_bits = encode_line(row_pixels, width); all_bits.push_str(&line_bits); } // Add RTC (6 consecutive EOLs) to signal end of document for _ in 0..6 { all_bits.push_str(EOL); } Ok(bits_to_bytes(&all_bits)) } /// Encode a single page from FaxDocument pub fn encode_page(page: &crate::document::convert::Page) -> Result> { Self::encode(&page.pixels, page.width_pels, page.rows) } }