Add PDF to fax conversion support

- pdf.rs: Ghostscript-based PDF to TIFF-F conversion
- tiff_to_fax_document: Parse TIFF and convert to 1-bit fax format
- CLI: Add 'convert' subcommand for dry testing
- CLI: Add resolution option for send command
- Test files for PDF/TIFF conversion validation
This commit is contained in:
Warren
2026-07-01 17:17:49 +08:00
parent 13438289fe
commit bf1ca90a9c
11 changed files with 361 additions and 6 deletions

BIN
test/output.tif Normal file

Binary file not shown.

BIN
test/output_fax.tif Normal file

Binary file not shown.

76
test/test_conversion.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import subprocess
import os
# Test PDF to TIFF-F conversion
pdf_path = "/Users/accusys/telfax/test/testfax.pdf"
tiff_path = "/Users/accusys/telfax/test/testfax_converted.tif"
print("=== Testing PDF → TIFF-F Conversion ===")
# Run ghostscript
result = subprocess.run([
"gs", "-dBATCH", "-dNOPAUSE", "-q",
"-sDEVICE=tiffg3",
"-r204x196", # Fine resolution
"-sOutputFile", tiff_path,
pdf_path
], capture_output=True, text=True)
if result.returncode != 0:
print(f"Ghostscript failed: {result.stderr}")
exit(1)
# Check output
if os.path.exists(tiff_path):
size = os.path.getsize(tiff_path)
print(f"✓ TIFF-F created: {tiff_path}")
print(f" Size: {size} bytes")
# Get TIFF info
import struct
with open(tiff_path, 'rb') as f:
header = f.read(8)
if header[:2] == b'II':
print(" Byte order: Little-endian")
elif header[:2] == b'MM':
print(" Byte order: Big-endian")
# Read IFD offset
if header[:2] == b'II':
ifd_offset = struct.unpack('<I', header[4:8])[0]
else:
ifd_offset = struct.unpack('>I', header[4:8])[0]
f.seek(ifd_offset)
num_entries = struct.unpack('<H' if header[:2] == b'II' else '>H', f.read(2))[0]
print(f" IFD entries: {num_entries}")
# Read key tags
for i in range(num_entries):
tag = struct.unpack('<H' if header[:2] == b'II' else '>H', f.read(2))[0]
type_ = struct.unpack('<H' if header[:2] == b'II' else '>H', f.read(2))[0]
count = struct.unpack('<I' if header[:2] == b'II' else '>I', f.read(4))[0]
value_offset = f.read(4)
# Tags of interest: 256=width, 257=height, 259=compression, 262=photometric
if tag == 256: # Width
val = struct.unpack('<I' if header[:2] == b'II' else '>I', value_offset)[0]
print(f" Width: {val} pixels")
elif tag == 257: # Height
val = struct.unpack('<I' if header[:2] == b'II' else '>I', value_offset)[0]
print(f" Height: {val} lines")
elif tag == 259: # Compression
val = struct.unpack('<H' if header[:2] == b'II' else '>H', value_offset[:2])[0]
comp_names = {1: "None", 3: "Group 3 MH", 4: "Group 4 MMR"}
print(f" Compression: {comp_names.get(val, val)}")
elif tag == 262: # PhotometricInterpretation
val = struct.unpack('<H' if header[:2] == b'II' else '>H', value_offset[:2])[0]
photo_names = {0: "WhiteIsZero", 1: "BlackIsZero"}
print(f" Photometric: {photo_names.get(val, val)}")
else:
print("✗ TIFF-F not created")
exit(1)
print("\n=== Conversion Test Passed ===")

33
test/test_tiff_convert.rs Normal file
View File

@@ -0,0 +1,33 @@
use telfax::document::pdf::{pdf_to_fax_document, tiff_to_fax_document};
use telfax::config::FaxResolution;
use std::path::Path;
fn main() {
let tiff_path = "/Users/accusys/telfax/test/testfax.tif";
let tiff_data = std::fs::read(tiff_path).expect("Failed to read TIFF");
println!("=== Testing TIFF → FaxDocument conversion ===");
println!("Input TIFF: {} bytes", tiff_data.len());
let doc = tiff_to_fax_document(&tiff_data).expect("Conversion failed");
println!("Converted successfully:");
println!(" Pages: {}", doc.pages.len());
for (i, page) in doc.pages.iter().enumerate() {
println!(" Page {}: {}x{} pixels, {} bytes",
i + 1, page.width_pels, page.rows, page.pixels.len());
}
// Verify pixel data - check some sample values
let page = &doc.pages[0];
let bytes_per_row = ((page.width_pels + 7) / 8) as usize;
println!(" Bytes per row: {}", bytes_per_row);
println!(" Expected rows: {}", page.rows);
println!(" Actual data rows: {}", page.pixels.len() / bytes_per_row);
// Sample first few bytes of first row
println!(" First row bytes (hex): {:02x?}", &page.pixels[0..8.min(bytes_per_row)]);
println!("\n=== Test Passed ===");
}

BIN
test/testfax.pdf Normal file

Binary file not shown.

41
test/testfax.ps Normal file
View File

@@ -0,0 +1,41 @@
%!PS-Adobe-3.0
%%Pages: 1
%%PageOrder: Ascend
%%BoundingBox: 0 0 612 792
%%EndComments
%%BeginProlog
/Dict 2 dict def
Dict begin
/PageSize [612 792] def
end
%%EndProlog
%%Page: 1 1
10 750 moveto
/Fnt1 12/Times-Roman findfont definefont pop
/Fnt1 12 selectfont
(Telfax Test Document - Page 1) show
10 730 moveto
(This is a test fax transmission from telfax Rust server.) show
10 700 moveto
(Testing US Robotics 56K FAX USB Modem) show
10 680 moveto
(Date: 2026-07-01) show
10 600 moveto
/BoxTimes-Roman 14 selectfont
(TEST FAX CONTENT) show
10 550 moveto
(This document tests the Class 1 fax protocol implementation.) show
showpage
%%PageTrailer
%%Trailer
%%EOF

BIN
test/testfax.tif Normal file

Binary file not shown.

BIN
test/testfax_gray.tif Normal file

Binary file not shown.