Multiple types compatibility - #1
Merged
Merged
Conversation
Approved investigation and decision record for growing the PDF-only service into a multi-format print service: - SumatraPDF kept as the print engine; LibreOffice headless joins later as office-to-PDF converter only; PDF stays the one internal print format - format strategy table, print-quality caveats, old-PC load profile - phased roadmap p10-p15 plus a v2 print-options phase, MVP/v2/future split - hardware spike protocol T5-T7 (images, office, text) for the real PC SOURCE_OF_TRUTH Section 9 now points to this plan.
- app/detection.py: magic-byte detection (PDF/JPEG/PNG/WebP/OLE, ZIP containers sniffed for office parts), extension allowlist, macro-format policy list - app/processors/: Processor protocol + category registry; the PDF pass-through is the only registered processor, so upload behavior stays PDF-only - uploads: validate_upload replaces validate_pdf (macro rejection, content vs extension cross-check, availability gate, size last), files stored with their real extension, sweep covers all files, delete_job_files centralizes per-job cleanup (used by cancel too) - pipeline: conversion stage behind a lock (one conversion at a time for the <=4GB PC), converting/printing states now actually set - models/jobs: converting state + format field on PrintJob - api: /print validates generically and records the format - config/.env.example: PAPER_SIZE, ENABLE_OFFICE, LO_PATH, CONVERT_TIMEOUT_S placeholders for phases 2-3 - tests: 132 pass, coverage 96.8% (gate 90%)
- app/processors/images.py: images become print-ready PDF pages on a white A4 canvas - EXIF orientation honored, transparency flattened to white, fitted+centered with 0.5in margins, wide photos get a landscape page, effective DPI capped at 300, multi-frame files capped at 10 pages - registry: image category registered (office/text still pending) - windows.py: optional -print-settings "paper=<X>,fit" when PAPER_SIZE is set; default stays empty = the spike-T4-proven driver behavior - web page + API: accept list widened to .pdf/.jpg/.jpeg/.png/.webp - spike_t5_images.py: T5 hardware check uses the real processor (convert timings + print + paper checklist); optional --paper verifies the driver honors paper size before PAPER_SIZE is enabled - tests: 150 pass, coverage 96.4% (gate 90%)
- app/processors/office.py: soffice --headless --convert-to pdf with a FRESH throwaway profile per conversion (a crashed run can never poison the next; GUI clashes impossible), CONVERT_TIMEOUT_S bounds the run and the process TREE is killed on timeout, failures map to human messages - Processor protocol gains available(): upload gate now distinguishes "arrives in a later phase" (unregistered) from "unavailable on this server" (office kill switch / LibreOffice missing) with actionable text - registry: office category registered; web page + API accept DOC/XLS/PPT/ODF families; text stays pending (Phase 4) - spike_t6_office.py: generates a table-heavy DOCX, a print-area XLSX and a 16:9 PPTX (python-docx/openpyxl/python-pptx, spike-only), converts with the real processor, prints, paper checklist = acceptance gate - tests: 169 pass, coverage 96.3% (gate 90%)
- app/processors/text.py: TXT renders as monospace, word-wrapped and paginated; CSV renders as a bordered grid with the header repeated on every page, delimiter sniffing (, ; tab |), and row/column caps that print an explicit truncation notice - encoding is verified, not assumed: UTF-16-with-BOM -> UTF-8 -> Windows-1252, and a file that decodes as none of them fails with a clear error instead of printing mojibake - page geometry (PAGE_SIZES_PT/page_size_pt) moves to processors/base.py so images and text share one source of truth - registry: text registered - all four MVP categories (pdf, image, office, text) are now printable, gated only by LibreOffice for office - web page + API accept .txt/.csv; spike_t7_text.py = T7 paper check - tests: 193 pass, coverage 96.9% (gate 90%)
…er gates pending - SOURCE_OF_TRUTH: status header now says multi-format code-complete (193 tests, ~97% coverage) with T5/T6/T7 explicitly pending; Section 5 gains the pending-spike entries (T5 images + paper-size check, T6 office after LibreOffice install, T7 text/CSV) and the normalization decision; Section 9 gains the 'where this stage stopped' block (done commits, open steps, phases 5-7 not started); Section 13's suite table covers the new processors and gates; Open Items updated - MULTI_FORMAT_PLAN: new 'Where this stage stopped' section (Section 0) with the resume checklist; open-items checklist refreshed - README: status line reflects the same state
…pen gate Spikes run on the print-server PC (2026-08-29): 4 image conversions 0.19-0.35s each, transparency corners white, EXIF upright, paper=A4 via -print-settings honored by the Epson driver (PAPER_SIZE now safe to set); TXT wrap and 40x6 CSV grid judged good on paper. Branch pushed, CI runs on PR #1. Only T6 (office, needs LibreOffice) remains open.
LibreOffice 26.8.0 installed via the new CLI recipe; spike_t6_office converted a table-heavy DOCX (20.8s), a print-area XLSX (10.5s, only A1:D20 printed, landscape) and a 16:9 PPTX (10.9s) - paper judged good. With T4/T5/T6/T7 all PASS, every supported format is verified end-to-end on the Epson L3210. Windows 11 confirmed (no LO 7.6.x pin needed). Next: Phase 5 (queue management).
- jobs.py: in-memory dict -> SQLite (JOB_DB_PATH, default
logs/jobs.sqlite3) behind the same function surface; source file +
category stored per job; SOURCE_OF_TRUTH Section 12's upgrade path taken
- cancellation works in every pre-done state now (was received-only, a
near-useless window): the pipeline checks between stages and never
marks a cancelled job done; cancelling while printing purges our queued
spooler jobs by document name (win32print.SetJob, best-effort)
- POST /jobs/{id}/retry + a Retry button on the web page: failed jobs
re-print from their stored upload, no re-upload needed
- startup recovery: jobs left active by a crashed run become failed
(their uploads are swept, so retrying is impossible)
- conftest: fresh per-test SQLite store; fake win32print gains the
spooler surface (OpenPrinter/EnumJobs/SetJob/JOB_CONTROL_DELETE)
- tests: 216 pass, coverage 96.1% (gate 90%)
…alog - printer_ready(): pre-dispatch spooler check (offline / not available / out of paper / jam / door open / error, plus Windows' WORK_OFFLINE attribute that a powered-off USB printer gets) - a known-bad printer fails with that reason before SumatraPDF starts; best-effort by design: a query hiccup never blocks printing. Verified against the real Epson (GetPrinter(2) dict shape confirmed live, healthy = ready) - SUMATRA_EXIT_MESSAGES: documented exit codes 2/3/4/5/6 now produce human messages instead of raw stderr - log rotation was already in place since p8; startup recovery since p14 - Phase 6 scope complete - tests: 224 pass, coverage 96.3% (gate 90%)
- PrintOptions + validate_print_options: copies 1-99, pages strict
allowlist (2-6 / 1,3,5 / odd / even - never touches a command line
unvalidated), paper enum incl. long-bond 8.5x13 (custom mm token),
color/monochrome - violations get a 422 with a readable message
- build_print_settings() in the print engine: request paper overrides
the PAPER_SIZE config; defaults still build NO settings, so the
no-options command stays byte-identical to the T4-proven one
- options stored on the job (JSON column + ALTER migration for older
DBs), shown in /jobs/{id}, and reused by retry - a retry prints
exactly like the original submission
- web page: collapsible Print options dialog (copies, pages, paper,
black & white) in the same vanilla-JS style
- spike_t5 --paper long-bond ready; 8.5x13 paper check stays the one
open spike line
- tests: 261 pass, coverage 96.3% (gate 90%)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Type of change
Related issue
How was this tested?
Screenshots (if applicable)
Checklist