Initial commit: telfax - Rust Fax Server
- Modem driver (serialport) with AT command interface - Class 1 fax protocol (T.30 session, HDLC framing) - T.4/T.6 image codec (fax crate integration) - Document conversion (image to fax, TIFF-F output) - REST API (axum) for fax job management - SQLite/InMemory job queue - CLI interface (detect/serve/send/test) - Integration tests passing
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
use crate::error::{FaxError, Result};
|
||||
|
||||
pub struct T4Codec;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct T4Page {
|
||||
pub data: Vec<u8>,
|
||||
pub width_pels: u32,
|
||||
pub rows: u32,
|
||||
pub encoding: T4Encoding,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum T4Encoding {
|
||||
Group3MH,
|
||||
Group3MR,
|
||||
Group4MMR,
|
||||
}
|
||||
|
||||
impl T4Codec {
|
||||
/// Decode T.4/T.6 data into raw 1-bit pixel data (MSB-first, row-major).
|
||||
/// `transitions_to_pixels` converts run-length transitions to pixel data.
|
||||
fn transitions_to_pixels(
|
||||
transitions: &[u16],
|
||||
width_pels: u32,
|
||||
row_buf: &mut [u8],
|
||||
) {
|
||||
row_buf.fill(0);
|
||||
let mut is_black = false;
|
||||
let mut prev = 0u32;
|
||||
|
||||
for &t in transitions {
|
||||
let t = t as u32;
|
||||
if t > width_pels {
|
||||
break;
|
||||
}
|
||||
if is_black {
|
||||
for p in prev..t.min(width_pels) {
|
||||
let byte_idx = (p / 8) as usize;
|
||||
let bit_idx = 7 - (p % 8);
|
||||
if byte_idx < row_buf.len() {
|
||||
row_buf[byte_idx] |= 1 << bit_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = t;
|
||||
is_black = !is_black;
|
||||
}
|
||||
// If ends on black, fill to end
|
||||
if is_black {
|
||||
for p in prev..width_pels {
|
||||
let byte_idx = (p / 8) as usize;
|
||||
let bit_idx = 7 - (p % 8);
|
||||
if byte_idx < row_buf.len() {
|
||||
row_buf[byte_idx] |= 1 << bit_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decode(data: &[u8], width_pels: u32, rows: u32, encoding: T4Encoding) -> Result<Vec<u8>> {
|
||||
let bytes_per_row = ((width_pels + 7) / 8) as usize;
|
||||
let mut pixels = vec![0u8; bytes_per_row * rows as usize];
|
||||
let data: Vec<u8> = data.to_vec();
|
||||
|
||||
match encoding {
|
||||
T4Encoding::Group3MH | T4Encoding::Group3MR => {
|
||||
let mut row_idx = 0u32;
|
||||
let result = fax::decoder::decode_g3(data.into_iter(), |transitions: &[u16]| {
|
||||
if (row_idx as usize) < pixels.len() / bytes_per_row {
|
||||
let start = row_idx as usize * bytes_per_row;
|
||||
let end = (start + bytes_per_row).min(pixels.len());
|
||||
let row_buf = &mut pixels[start..end];
|
||||
Self::transitions_to_pixels(transitions, width_pels, row_buf);
|
||||
}
|
||||
row_idx += 1;
|
||||
});
|
||||
match result {
|
||||
Some(()) => Ok(pixels),
|
||||
None => Err(FaxError::t4("Group 3 decode returned None")),
|
||||
}
|
||||
}
|
||||
T4Encoding::Group4MMR => {
|
||||
let mut row_idx = 0u32;
|
||||
let result = fax::decoder::decode_g4(
|
||||
data.into_iter(),
|
||||
width_pels as u16,
|
||||
None,
|
||||
|transitions: &[u16]| {
|
||||
if (row_idx as usize) < pixels.len() / bytes_per_row {
|
||||
let start = row_idx as usize * bytes_per_row;
|
||||
let end = (start + bytes_per_row).min(pixels.len());
|
||||
let row_buf = &mut pixels[start..end];
|
||||
Self::transitions_to_pixels(transitions, width_pels, row_buf);
|
||||
}
|
||||
row_idx += 1;
|
||||
},
|
||||
);
|
||||
match result {
|
||||
Some(()) => Ok(pixels),
|
||||
None => Err(FaxError::t4("Group 4 decode returned None")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user