Add cover page support with Chinese font rendering
Features:
- Cover page generation using Python/Pillow with Apple PingFang font
- CLI commands: cover-create, job-cover for cover page management
- API endpoints: PUT /api/fax/jobs/{id}/cover for cover modification
- Send fax with cover page: --to, --from, --subject, --note options
- Multi-page TIFF parsing for PDF conversion (fix: all pages now sent)
- Preview HTML generation with pan/zoom support
- OCR verification for cover page content
- Class 2 receive support for incoming faxes
Fixes:
- Modem initialization before Class 2 mode entry
- Dial timeout handling with retry logic
- Multi-page fax transmission between pages
- Chinese character rendering in cover pages (Apple native fonts)
This commit is contained in:
Executable
+177
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Class 1 Fax Receiver - for testing with USR sender"""
|
||||
import termios
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: class1_receive.py <device>")
|
||||
sys.exit(1)
|
||||
|
||||
device = sys.argv[1]
|
||||
fd = os.open(device, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
|
||||
|
||||
attrs = termios.tcgetattr(fd)
|
||||
attrs[4] = termios.B115200
|
||||
attrs[5] = termios.B115200
|
||||
attrs[2] |= termios.CLOCAL | termios.CREAD
|
||||
attrs[3] &= ~termios.ICANON
|
||||
attrs[3] &= ~termios.ECHO
|
||||
attrs[3] &= ~termios.ECHOE
|
||||
attrs[3] &= ~termios.ISIG
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
||||
termios.tcflush(fd, termios.TCIOFLUSH)
|
||||
|
||||
def drain():
|
||||
try:
|
||||
while True:
|
||||
os.read(fd, 4096)
|
||||
except BlockingIOError:
|
||||
pass
|
||||
|
||||
def send_cmd(cmd, wait=0.5):
|
||||
drain()
|
||||
os.write(fd, cmd.encode() + b"\r")
|
||||
time.sleep(wait)
|
||||
result = b""
|
||||
try:
|
||||
while True:
|
||||
data = os.read(fd, 4096)
|
||||
result += data
|
||||
except BlockingIOError:
|
||||
pass
|
||||
return result.decode(errors="replace")
|
||||
|
||||
def wait_for(pattern, timeout=30):
|
||||
start = time.time()
|
||||
result = b""
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
data = os.read(fd, 4096)
|
||||
result += data
|
||||
decoded = result.decode(errors="replace")
|
||||
for p in pattern if isinstance(pattern, list) else [pattern]:
|
||||
if p in decoded:
|
||||
return decoded
|
||||
except BlockingIOError:
|
||||
time.sleep(0.1)
|
||||
return result.decode(errors="replace")
|
||||
|
||||
print("[RECV] Initializing Class 1...")
|
||||
send_cmd("AT", 0.3)
|
||||
send_cmd("AT+FCLASS=1", 0.3)
|
||||
|
||||
print("[RECV] Waiting for rings...")
|
||||
|
||||
ring_count = 0
|
||||
while ring_count < 2:
|
||||
try:
|
||||
data = os.read(fd, 4096)
|
||||
text = data.decode(errors="replace")
|
||||
if "RING" in text:
|
||||
ring_count += text.count("RING")
|
||||
print(f"[RECV] Ring {ring_count}")
|
||||
except BlockingIOError:
|
||||
time.sleep(0.3)
|
||||
|
||||
print("[RECV] Answering...")
|
||||
answer_result = send_cmd("ATA", 3)
|
||||
print(f"[RECV] ATA result: {answer_result.strip()}")
|
||||
|
||||
if "CONNECT" not in answer_result:
|
||||
drain()
|
||||
os.write(fd, b"ATA\r")
|
||||
wait_result = wait_for("CONNECT", 10)
|
||||
print(f"[RECV] Wait result: {wait_result.strip()}")
|
||||
|
||||
print("[RECV] Connected! Sending CED tone...")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Send CED tone (2100Hz for 2.6-4 seconds)
|
||||
ced_result = send_cmd("AT+FTS=8", 4) # FTS=8 sends 2100Hz
|
||||
print(f"[RECV] CED result: {ced_result.strip()}")
|
||||
|
||||
# Wait 75ms then send DIS
|
||||
time.sleep(0.1)
|
||||
|
||||
print("[RECV] Sending DIS frame...")
|
||||
|
||||
# Start HDLC transmit
|
||||
os.write(fd, b"AT+FTH=3\r")
|
||||
fth_result = wait_for("CONNECT", 5)
|
||||
print(f"[RECV] FTH=3: {fth_result.strip()}")
|
||||
|
||||
if "CONNECT" in fth_result:
|
||||
# DIS frame: preamble+flags+DCS+data+RTC
|
||||
# Address: 0xFF (broadcast), Control: 0x80 (DIS from called)
|
||||
# DIS data: minimum capabilities
|
||||
dis_data = bytes([
|
||||
0xFF, 0x13, 0x80, # Address + Control (DIS from called)
|
||||
0x00, 0x50, 0x1F, # DIS parameters
|
||||
])
|
||||
# Add FCS (we'll let modem calculate)
|
||||
os.write(fd, dis_data)
|
||||
os.write(fd, b"\x00\x00\x00") # Padding
|
||||
time.sleep(0.5)
|
||||
|
||||
# Wait for OK
|
||||
dis_ok = wait_for("OK", 5)
|
||||
print(f"[RECV] DIS sent: {dis_ok.strip()}")
|
||||
|
||||
print("[RECV] Waiting for DCS...")
|
||||
drain()
|
||||
os.write(fd, b"AT+FRH=3\r")
|
||||
dcs_result = wait_for("OK", 10)
|
||||
print(f"[RECV] DCS received: {len(dcs_result)} bytes")
|
||||
|
||||
print("[RECV] Waiting for TCF...")
|
||||
drain()
|
||||
os.write(fd, b"AT+FRM=96\r") # Receive at 14400 bps
|
||||
tcf_result = wait_for("CONNECT", 5)
|
||||
print(f"[RECV] TCF: {tcf_result.strip()}")
|
||||
|
||||
# Send CFR
|
||||
print("[RECV] Sending CFR...")
|
||||
drain()
|
||||
os.write(fd, b"AT+FTH=3\r")
|
||||
wait_for("CONNECT", 5)
|
||||
cfr_data = bytes([0xFF, 0x13, 0x21]) # CFR frame
|
||||
os.write(fd, cfr_data)
|
||||
os.write(fd, b"\x00\x00\x00")
|
||||
time.sleep(0.5)
|
||||
wait_for("OK", 5)
|
||||
print("[RECV] CFR sent")
|
||||
|
||||
print("[RECV] Waiting for page data...")
|
||||
# Receive page
|
||||
drain()
|
||||
os.write(fd, b"AT+FRM=96\r")
|
||||
page_result = wait_for("OK", 60)
|
||||
print(f"[RECV] Page received!")
|
||||
|
||||
# Send MCF
|
||||
print("[RECV] Sending MCF...")
|
||||
drain()
|
||||
os.write(fd, b"AT+FTH=3\r")
|
||||
wait_for("CONNECT", 5)
|
||||
mcf_data = bytes([0xFF, 0x13, 0x31]) # MCF frame
|
||||
os.write(fd, mcf_data)
|
||||
os.write(fd, b"\x00\x00\x00")
|
||||
time.sleep(0.5)
|
||||
wait_for("OK", 5)
|
||||
print("[RECV] MCF sent")
|
||||
|
||||
# Wait for DCN or hangup
|
||||
print("[RECV] Waiting for DCN...")
|
||||
drain()
|
||||
os.write(fd, b"AT+FRH=3\r")
|
||||
dcn_result = wait_for("OK", 10)
|
||||
print(f"[RECV] Session ended: {dcn_result[:50]}")
|
||||
|
||||
os.close(fd)
|
||||
print("[RECV] Done!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user