mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 18:07:07 -06:00
Main prints through craft-print, which runs `lp`; a Flatpak has no `lp`, so File › Print fails there. This keeps craft-print as the route wherever `lp` is on the path (and on Windows) and adds two more behind it: - `photocraft-ipp`, a pure-Rust IPP client (CUPS-Get-Printers/Classes/ Default, Print-Job over HTTP/1.1) that talks to CUPS over its socket or TCP; the Flatpak manifests grant --socket=cups for it. A `printServer` named in the request always takes this route. `file.print.printers` lists the queues. - The XDG Print portal as the app's spooler (`Session::print_spooler`), for a sandbox without the CUPS socket or a system without a printer: the system print dialog, seeded with copies, paper and orientation. PHOTOCRAFT_PRINT_PORTAL=1 prefers it, =0 removes it. A route that exists but fails (unknown printer, refused job) is an error; only a missing route moves on. Results gain `backend` (lp, windows, ipp, portal). docs/printing.md describes the routes.
68 lines
4.3 KiB
Markdown
68 lines
4.3 KiB
Markdown
# Printing
|
||
|
||
File › Print renders the flattened document onto one PDF page (paper size and orientation,
|
||
position and scale, crop and registration marks, labels, colour handling) and hands that page
|
||
to a printer. The rendering is `print_cmds.rs` in `photocraft-engine`, shared by the dialog, the
|
||
batch commands and automation (`file.print`, `file.printOneCopy`); this page is about the hand-off.
|
||
|
||
## Routes to the printer
|
||
|
||
| Route | When | What the user sees |
|
||
|---|---|---|
|
||
| **`craft-print`** (`lp` on CUPS, the Windows spooler) | Whenever `lp` is on the path (macOS, Linux outside a sandbox) or on Windows | `Sent to the printer (request id is Office-42)` |
|
||
| **CUPS over IPP** (`photocraft-ipp`) | No `lp` on the path (a Flatpak) and a print server answers: `CUPS_SERVER`, then `/run/cups/cups.sock`, `/var/run/cups/cups.sock`, `/private/var/run/cupsd`, then `localhost:631`; always for a `printServer` named in the request | `Sent to the printer (job 42 on Office)` |
|
||
| **Print portal** (`org.freedesktop.portal.Print`, desktop app on Linux) | No print server reachable or no printer set up, or always with `PHOTOCRAFT_PRINT_PORTAL=1` | The system print dialog, seeded with the copies and paper; the outcome goes to the log |
|
||
| **Print to PDF** | `output` given (the Output field of the dialog) | The file; nothing is spooled unless `send` is true |
|
||
|
||
A route that exists but fails (an unknown printer, a job the queue refuses) is an error; only a
|
||
missing route (no `lp`, no print server, no printer) moves on to the next one.
|
||
|
||
The IPP client is in-process, pure Rust (RFC 8010/8011: `CUPS-Get-Printers`, `CUPS-Get-Classes`,
|
||
`CUPS-Get-Default`, `Print-Job` with the PDF as the document), so no `lp` binary is needed. That
|
||
is what makes printing work in the Flatpak: the manifest grants `--socket=cups`, which mounts the
|
||
host's CUPS socket, and the sandbox has no `lp`. On a system without CUPS, or in a sandbox
|
||
without the socket, the app falls back to the print portal, whose dialog runs outside the sandbox.
|
||
|
||
## Which printer
|
||
|
||
The dialog's Printer field, or `"printer"` in the command, names a queue (case-insensitively; a
|
||
CUPS class counts, as with `lp -d`).
|
||
Left empty, the server's default printer is used; without a default, the only printer; with
|
||
several and no default, the error names them. With no printer at all, the portal takes over when
|
||
it is available, otherwise the error says to add one in the system settings or print to PDF.
|
||
|
||
`file.printers` lists what the print server knows: `{"printServer": "host[:port]|socket"}`
|
||
is optional and defaults to the system's CUPS; the result is
|
||
`{server, default, printers: [{name, info, state, acceptingJobs, default, class}]}`, or an empty list
|
||
with a `note` when no server answers. `"printServer"` on `file.print` sends one job to a named
|
||
server; `"dryRun": true` renders the page and reports the equivalent `lp` command without
|
||
spooling.
|
||
|
||
## Results
|
||
|
||
`file.print` returns `sent`, `backend` (`lp`, `windows`, `ipp`, `portal`), `spooler` (where the
|
||
job went) and `pdf` (the page's path, only when `output` named one).
|
||
|
||
## Environment
|
||
|
||
| Variable | Effect |
|
||
|---|---|
|
||
| `CUPS_SERVER` | The print server, as CUPS itself reads it: `host[:port]` or a socket path |
|
||
| `PHOTOCRAFT_PRINT_PORTAL=1` | Always use the system print dialog (the portal) instead of `lp` or CUPS directly |
|
||
| `PHOTOCRAFT_PRINT_PORTAL=0` | Never use the portal; a missing print server is then an error |
|
||
|
||
## Verification
|
||
|
||
- `crates/ipp/src/tests.rs`: the client against a fake CUPS over TCP and a Unix socket:
|
||
printer listing with state and default, the default printer and its absence, `Print-Job`
|
||
with the document and the job id, chunked and content-length replies, HTTP and IPP error
|
||
statuses, broken replies.
|
||
- `crates/engine/src/print_cmds/tests.rs`: printer resolution (requested, default, only one,
|
||
several without a default, unknown, none); a complete `file.print` against a fake CUPS
|
||
(printer URI, copies, job name, document format, the PDF itself) and `file.printers`;
|
||
`lp` taking the job when it is on the path; the IPP route when it is not (a child process
|
||
with an empty path); the app's spooler when preferred, as the fallback without a printer, and
|
||
skipped when a server is named; print to PDF untouched.
|
||
- `apps/photocraft/src/print_portal.rs`: the request path rule and the paper/copies seeding of
|
||
the dialog (PWG names for the standard papers, millimetres for custom sizes).
|