Files
photocraft/docs/printing.md
T
Diego Chagas 235e7578f3 Print through CUPS over IPP and the print portal, so File › Print works in the Flatpak (#2728) (#3017)
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.
2026-10-17 15:55:54 -06:00

68 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).