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:
@@ -1,5 +1,7 @@
|
|||||||
pub mod convert;
|
pub mod convert;
|
||||||
pub mod tiff;
|
pub mod tiff;
|
||||||
|
pub mod pdf;
|
||||||
|
|
||||||
pub use convert::{document_from_image, document_from_tiff, DocumentFormat, FaxDocument, Page};
|
pub use convert::{document_from_image, document_from_tiff, DocumentFormat, FaxDocument, Page};
|
||||||
pub use tiff::TiffFaxWriter;
|
pub use tiff::TiffFaxWriter;
|
||||||
|
pub use pdf::pdf_to_fax_document;
|
||||||
|
|||||||
98
src/document/pdf.rs
Normal file
98
src/document/pdf.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use crate::error::{FaxError, Result};
|
||||||
|
use crate::document::convert::{DocumentFormat, FaxDocument, Page};
|
||||||
|
use crate::config::FaxResolution;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Convert PDF to FaxDocument using Ghostscript.
|
||||||
|
pub fn pdf_to_fax_document(pdf_path: &Path, resolution: FaxResolution) -> Result<FaxDocument> {
|
||||||
|
let dpi_arg = match resolution {
|
||||||
|
FaxResolution::Standard => "204x98",
|
||||||
|
FaxResolution::Fine => "204x196",
|
||||||
|
FaxResolution::SuperFine => "204x392",
|
||||||
|
};
|
||||||
|
|
||||||
|
let tiff_path = pdf_path.with_extension("fax.tif");
|
||||||
|
tracing::info!("Converting PDF {} to TIFF {}", pdf_path.display(), tiff_path.display());
|
||||||
|
|
||||||
|
let output = Command::new("gs")
|
||||||
|
.arg("-dBATCH")
|
||||||
|
.arg("-dNOPAUSE")
|
||||||
|
.arg("-q")
|
||||||
|
.arg("-sDEVICE=tiffgray")
|
||||||
|
.arg(format!("-r{}", dpi_arg))
|
||||||
|
.arg(format!("-sOutputFile={}", tiff_path.display()))
|
||||||
|
.arg(pdf_path)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| FaxError::document(format!("Failed to run ghostscript: {}", e)))?;
|
||||||
|
|
||||||
|
tracing::info!("Ghostscript exit status: {:?}", output.status);
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
tracing::error!("Ghostscript stderr: {}", stderr);
|
||||||
|
tracing::error!("Ghostscript stdout: {}", stdout);
|
||||||
|
return Err(FaxError::document(format!(
|
||||||
|
"Ghostscript failed: exit code {:?}",
|
||||||
|
output.status.code()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tiff_data = std::fs::read(&tiff_path)?;
|
||||||
|
let doc = tiff_to_fax_document(&tiff_data)?;
|
||||||
|
|
||||||
|
std::fs::remove_file(&tiff_path).ok();
|
||||||
|
|
||||||
|
Ok(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read TIFF-F and convert to FaxDocument with 1-bit pixel data.
|
||||||
|
pub fn tiff_to_fax_document(tiff_data: &[u8]) -> Result<FaxDocument> {
|
||||||
|
use tiff::decoder::Decoder;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
let mut cursor = Cursor::new(tiff_data);
|
||||||
|
let mut decoder = Decoder::new(&mut cursor)
|
||||||
|
.map_err(|e| FaxError::document(format!("Failed to open TIFF: {}", e)))?;
|
||||||
|
|
||||||
|
let (width, height) = decoder.dimensions()
|
||||||
|
.map_err(|e| FaxError::document(format!("Failed to get TIFF dimensions: {}", e)))?;
|
||||||
|
|
||||||
|
let bytes_per_row = ((width + 7) / 8) as usize;
|
||||||
|
let mut pixels = vec![0u8; bytes_per_row * height as usize];
|
||||||
|
|
||||||
|
let mut result = decoder.read_image()
|
||||||
|
.map_err(|e| FaxError::document(format!("Failed to read TIFF image: {}", e)))?;
|
||||||
|
|
||||||
|
// Convert to 1-bit packed format
|
||||||
|
let buffer = result.as_buffer(0);
|
||||||
|
let img_data = buffer.as_bytes();
|
||||||
|
|
||||||
|
// Process each pixel - TIFF is grayscale (8-bit), convert to 1-bit
|
||||||
|
for (i, &byte) in img_data.iter().enumerate() {
|
||||||
|
let pixel_idx = i % width as usize;
|
||||||
|
let row_idx = i / width as usize;
|
||||||
|
let byte_idx = pixel_idx / 8;
|
||||||
|
let bit_idx = 7 - (pixel_idx % 8);
|
||||||
|
|
||||||
|
// WhiteIsZero: 0=white, 255=black
|
||||||
|
// We want: 1=black, 0=white (MSB first)
|
||||||
|
if byte < 128 {
|
||||||
|
let idx = row_idx * bytes_per_row + byte_idx;
|
||||||
|
if idx < pixels.len() {
|
||||||
|
pixels[idx] |= 1 << bit_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = Page {
|
||||||
|
pixels,
|
||||||
|
width_pels: width,
|
||||||
|
rows: height,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(FaxDocument {
|
||||||
|
pages: vec![page],
|
||||||
|
format: DocumentFormat::Tiff,
|
||||||
|
})
|
||||||
|
}
|
||||||
117
src/main.rs
117
src/main.rs
@@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use telfax::config::FaxConfig;
|
use telfax::config::FaxConfig;
|
||||||
|
use telfax::config::FaxResolution;
|
||||||
use telfax::error::Result;
|
use telfax::error::Result;
|
||||||
use telfax::modem::detect::detect_modem;
|
use telfax::modem::detect::detect_modem;
|
||||||
use telfax::modem::ModemDriver;
|
use telfax::modem::ModemDriver;
|
||||||
@@ -32,11 +33,14 @@ enum Commands {
|
|||||||
Send {
|
Send {
|
||||||
/// Recipient phone number
|
/// Recipient phone number
|
||||||
recipient: String,
|
recipient: String,
|
||||||
/// Path to document file (PNG/JPG/TIFF)
|
/// Path to document file (PDF or image)
|
||||||
file: String,
|
file: String,
|
||||||
/// Serial device
|
/// Serial device
|
||||||
#[arg(short, long, default_value = "/dev/cu.usbmodem00000021")]
|
#[arg(short, long, default_value = "/dev/cu.usbmodem00000021")]
|
||||||
device: String,
|
device: String,
|
||||||
|
/// Resolution (standard/fine/superfine)
|
||||||
|
#[arg(short, long, default_value = "fine")]
|
||||||
|
resolution: String,
|
||||||
},
|
},
|
||||||
/// Interactively test the modem
|
/// Interactively test the modem
|
||||||
Test {
|
Test {
|
||||||
@@ -44,6 +48,17 @@ enum Commands {
|
|||||||
#[arg(default_value = "/dev/cu.usbmodem00000021")]
|
#[arg(default_value = "/dev/cu.usbmodem00000021")]
|
||||||
device: String,
|
device: String,
|
||||||
},
|
},
|
||||||
|
/// Convert PDF to TIFF-F fax format (dry test)
|
||||||
|
Convert {
|
||||||
|
/// Input PDF file path
|
||||||
|
input: String,
|
||||||
|
/// Output TIFF file path
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<String>,
|
||||||
|
/// Resolution (standard/fine/superfine)
|
||||||
|
#[arg(short, long, default_value = "fine")]
|
||||||
|
resolution: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -60,8 +75,9 @@ async fn main() -> Result<()> {
|
|||||||
match cli.command.unwrap_or(Commands::Serve { config: None }) {
|
match cli.command.unwrap_or(Commands::Serve { config: None }) {
|
||||||
Commands::Detect { device } => cmd_detect(&device),
|
Commands::Detect { device } => cmd_detect(&device),
|
||||||
Commands::Serve { config } => cmd_serve(config).await,
|
Commands::Serve { config } => cmd_serve(config).await,
|
||||||
Commands::Send { recipient, file, device } => cmd_send(&device, &recipient, &file),
|
Commands::Send { recipient, file, device, resolution } => cmd_send(&device, &recipient, &file, &resolution),
|
||||||
Commands::Test { device } => cmd_test(&device),
|
Commands::Test { device } => cmd_test(&device),
|
||||||
|
Commands::Convert { input, output, resolution } => cmd_convert(&input, output.as_deref(), &resolution),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,15 +119,36 @@ async fn cmd_serve(config_path: Option<String>) -> Result<()> {
|
|||||||
telfax::api::start_server(config, queue.clone()).await
|
telfax::api::start_server(config, queue.clone()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_send(device: &str, recipient: &str, file: &str) -> Result<()> {
|
fn cmd_send(device: &str, recipient: &str, file: &str, resolution: &str) -> Result<()> {
|
||||||
info!("Sending fax to {} from {} using device {}", recipient, file, device);
|
info!("Sending fax to {} from {} using device {}", recipient, file, device);
|
||||||
|
|
||||||
let mut driver = ModemDriver::open(device, 115200)?;
|
let mut driver = ModemDriver::open(device, 115200)?;
|
||||||
let data = std::fs::read(file)
|
|
||||||
.map_err(|e| telfax::error::FaxError::Other(format!("Failed to read file: {}", e)))?;
|
|
||||||
|
|
||||||
let doc = telfax::document::document_from_image(&data)?;
|
// Determine resolution
|
||||||
|
let fax_resolution = match resolution.to_lowercase().as_str() {
|
||||||
|
"standard" | "std" => FaxResolution::Standard,
|
||||||
|
"fine" => FaxResolution::Fine,
|
||||||
|
"superfine" | "super" => FaxResolution::SuperFine,
|
||||||
|
_ => FaxResolution::Fine,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load document - handle PDF or image
|
||||||
|
let path = std::path::Path::new(file);
|
||||||
|
let doc = if file.to_lowercase().ends_with(".pdf") {
|
||||||
|
info!("Converting PDF to fax format...");
|
||||||
|
telfax::document::pdf_to_fax_document(path, fax_resolution)?
|
||||||
|
} else {
|
||||||
|
let data = std::fs::read(file)
|
||||||
|
.map_err(|e| telfax::error::FaxError::Other(format!("Failed to read file: {}", e)))?;
|
||||||
|
telfax::document::document_from_image(&data)?
|
||||||
|
};
|
||||||
|
|
||||||
let page = &doc.pages[0];
|
let page = &doc.pages[0];
|
||||||
|
info!("Document: {}x{} pixels, {} bytes", page.width_pels, page.rows, page.pixels.len());
|
||||||
|
|
||||||
|
// TODO: Encode to T.4 MH before sending
|
||||||
|
// For now, we send raw pixel data (modem will need T.4 encoded data)
|
||||||
|
info!("WARNING: T.4 encoding not fully implemented - sending raw pixels");
|
||||||
|
|
||||||
let mut sender = telfax::fax::class1::Class1Send::new(&mut driver);
|
let mut sender = telfax::fax::class1::Class1Send::new(&mut driver);
|
||||||
sender.send_fax(recipient, &page.pixels, page.width_pels, page.rows)?;
|
sender.send_fax(recipient, &page.pixels, page.width_pels, page.rows)?;
|
||||||
@@ -159,3 +196,71 @@ fn cmd_test(device: &str) -> Result<()> {
|
|||||||
println!("\nDone.");
|
println!("\nDone.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cmd_convert(input: &str, output: Option<&str>, resolution: &str) -> Result<()> {
|
||||||
|
let fax_resolution = match resolution.to_lowercase().as_str() {
|
||||||
|
"standard" | "std" => FaxResolution::Standard,
|
||||||
|
"fine" => FaxResolution::Fine,
|
||||||
|
"superfine" | "super" => FaxResolution::SuperFine,
|
||||||
|
_ => FaxResolution::Fine,
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_path = std::path::Path::new(input);
|
||||||
|
let output_path = output.map(|s| std::path::Path::new(s));
|
||||||
|
|
||||||
|
println!("=== Converting {} to Fax Format ===", input);
|
||||||
|
println!("Resolution: {}", resolution);
|
||||||
|
|
||||||
|
if input.to_lowercase().ends_with(".pdf") {
|
||||||
|
let doc = telfax::document::pdf_to_fax_document(input_path, fax_resolution)?;
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
let bytes_per_row = ((page.width_pels + 7) / 8) as usize;
|
||||||
|
println!(" Bytes per row: {}", bytes_per_row);
|
||||||
|
println!(" Data rows: {}", page.pixels.len() / bytes_per_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write TIFF output if specified
|
||||||
|
if let Some(out_path) = output_path {
|
||||||
|
telfax::document::tiff::TiffFaxWriter::write_fax_tiff(out_path, &doc.pages)?;
|
||||||
|
println!(" Output written to: {}", out_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== Conversion Test Passed ===");
|
||||||
|
} else if input.to_lowercase().ends_with(".tif") || input.to_lowercase().ends_with(".tiff") {
|
||||||
|
let tiff_data = std::fs::read(input_path)?;
|
||||||
|
let doc = telfax::document::pdf::tiff_to_fax_document(&tiff_data)?;
|
||||||
|
|
||||||
|
println!("TIFF parsed 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== TIFF Parse Test Passed ===");
|
||||||
|
} else {
|
||||||
|
// Try as image
|
||||||
|
let data = std::fs::read(input_path)?;
|
||||||
|
let doc = telfax::document::document_from_image(&data)?;
|
||||||
|
|
||||||
|
println!("Image 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== Image Conversion Test Passed ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
BIN
test/output.tif
Normal file
BIN
test/output.tif
Normal file
Binary file not shown.
BIN
test/output_fax.tif
Normal file
BIN
test/output_fax.tif
Normal file
Binary file not shown.
76
test/test_conversion.py
Normal file
76
test/test_conversion.py
Normal 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
33
test/test_tiff_convert.rs
Normal 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
BIN
test/testfax.pdf
Normal file
Binary file not shown.
41
test/testfax.ps
Normal file
41
test/testfax.ps
Normal 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
BIN
test/testfax.tif
Normal file
Binary file not shown.
BIN
test/testfax_gray.tif
Normal file
BIN
test/testfax_gray.tif
Normal file
Binary file not shown.
Reference in New Issue
Block a user