mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-14 03:03:36 +00:00
* M10: preserve annotation layers in vector printing (#5005) * #5019 test: a Popup is the annotation that never prints; a Link prints since #5028 --------- Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
150 lines
11 KiB
Markdown
150 lines
11 KiB
Markdown
# pdfcraft-print
|
||
|
||
Layer L4: printing (execution plan M10.5).
|
||
|
||
```rust
|
||
let pages = select_pages(count, Some("1-3, ii, 9-"), &labels, Subset::All, false)?;
|
||
let settings = Settings { pages, paper: PAPERS[0].1, layout: Layout::multiple(4), ..Default::default() };
|
||
let sheets = layout(&display_sizes, &settings)?; // geometry only (previews)
|
||
let pdf = impose(&doc, &settings)?; // the print-ready PDF
|
||
spool::submit(&pdf, &spool::Job { printer: None, copies: 2, ..Default::default() })?;
|
||
```
|
||
|
||
- **Size**: fit (to the sheet minus an 18 pt margin), actual size, shrink oversized, custom %;
|
||
centred; auto orientation turns the sheet for landscape pages.
|
||
- **Multiple**: 2/4/6/9/16 (or any n) pages per sheet, horizontal/vertical (reversed) order,
|
||
page borders, auto-rotation of pages that don't match the cell. With auto orientation a
|
||
predefined count turns the sheet to suit its grid (2 per sheet prints side by side on
|
||
landscape). A custom grid (`Layout::grid`, `doc_print` `cols`/`rows`, 1-99 each) is laid
|
||
out across and down the sheet as given and doesn't turn it: auto prints portrait.
|
||
- **Cut and stack** (Multiple's page order): consecutive pages in each cell's pile,
|
||
with aligned cut marks in the gutters. For example, 10 pages at 4 per sheet give
|
||
`[1, 4, 7, 10]`, `[2, 5, 8, blank]`, `[3, 6, 9, blank]`. Print single-sided,
|
||
keep sheets in output order, cut at the marks, then stack the cell piles from
|
||
left to right, top to bottom. Blank cells can be discarded. Page ranges and
|
||
Reverse pages are applied before imposition. Duplex imposition is not supported.
|
||
Headless: `doc_print` with `layout: "multiple"`, `order: "cut-stack"` and
|
||
`per_sheet: 4` (or any supported grid size), plus `path` or `printer`.
|
||
- **Booklet**: saddle-stitch imposition padded to a multiple of 4; both sides, front or back
|
||
only; left or right binding; creep compensation, which moves each sheet's pages toward the
|
||
fold by its place from the outside times the creep (points per sheet, up to `MAX_CREEP`),
|
||
clipped at the fold. Headless: `doc_print` with `layout: "booklet"` and `creep`.
|
||
- **Poster**: tile scale, overlap shared by neighbouring tiles, cut marks.
|
||
- **Comments & forms**: document, + markups, + stamps, or form fields only; annotations print
|
||
only with their Print flag, drawn from their appearance streams (Algorithm 8.1).
|
||
Existing Link appearances are document content in the first three modes; Link appearances
|
||
are not synthesized when absent, and form fields only excludes them.
|
||
- **Properties… on CUPS** reads the queue's PPD and its current defaults (`lpoptions -l`) off
|
||
the GUI thread, and gives `lpoptions` ten seconds: a CUPS that has stopped answering leaves
|
||
the PPD's own defaults in force instead of holding the dialog (#1818; `spool::output_within`
|
||
kills and reaps the command at the deadline).
|
||
- **Remembered choices** (#2001): the dialog keeps two-sided (including leaving it to the
|
||
printer), grayscale, collate, page sizing (with its custom percent), Comments & forms and a
|
||
booklet's binding side across restarts, in the settings file under `print` with the names
|
||
`doc_print` takes (`names::duplex`, `names::content`, `names::size_mode` and their `parse_*`
|
||
counterparts are the one vocabulary). The paper is saved by its own setting (#2156).
|
||
- **Each job starts afresh**: copies, pages (the choice, the range, odd/even and reverse), the
|
||
layout with its own options (pages per sheet, order, borders, booklet subset, poster scale,
|
||
overlap and cut marks) and orientation reset to the defaults each time Print opens, in the
|
||
same session too; ISO 32000-2 Table 147 initialises them per document when the print dialog
|
||
opens. Picked thumbnails still open it on Selected pages. The printer picked last carries over
|
||
within a session (#3537) but is not saved.
|
||
|
||
Each source page becomes a Form XObject (its content wrapped in q/Q, plus the printable
|
||
annotations); sheets place them with a clip. The result is a fresh, unencrypted,
|
||
garbage-collected file (callers check the print permission).
|
||
Annotation appearances receive unused XObject names, preserving existing page resources even when they use the same naming prefix.
|
||
Vector printing also carries each annotation's optional-content membership onto its own appearance invocation.
|
||
Hidden groups, membership policies, visibility expressions and existing layer overrides therefore remain effective in the generated PDF, including when appearances are shared.
|
||
Malformed top-level membership or required resource containers produce a print error instead of an unguarded appearance.
|
||
Nested policy data is preserved without a new evaluator; readers remain responsible for optional-content semantics.
|
||
The separate on-screen and Print as image annotation-visibility gap is tracked in #3572 / #3624.
|
||
Appearance placement keeps the full PDF number precision, including small coefficients needed by large appearance bounding boxes or matrices.
|
||
Existing printable NoRotate appearances keep their orientation and upper-left rectangle anchor when the source page is rotated.
|
||
Text annotations use this upright placement even with the NoRotate flag clear (ISO 32000-1 section 12.5.6.4).
|
||
Crop origins, UserUnit, sheet scaling, and later imposition rotations still apply; NoZoom does not cancel printing scale.
|
||
Missing or unusable appearance streams still cannot be printed.
|
||
|
||
The GUI Print dialog remembers its last selected paper size across application sessions,
|
||
including when Print is cancelled. The preference stores a canonical name such as `A4`;
|
||
unrecognized saved values retain the current selection (US Letter on a fresh app).
|
||
This change does not alter persistence of other print preferences.
|
||
Headless `doc_print` defaults are unchanged.
|
||
|
||
`spool` delegates discovery, driver options, preferences and submission to
|
||
[`craft-print`](https://github.com/storytold/craft-libs/tree/main/crates/print).
|
||
PdfCraft retains page selection, imposition, annotations and print-permission checks.
|
||
CUPS jobs stream through stdin with `fit-to-page=false`, and with `orientation-requested=4` when
|
||
every sheet is landscape (`cups_orientation`), since CUPS would otherwise shrink a landscape
|
||
sheet into portrait paper. On macOS they also carry the job name and owner
|
||
(`com.apple.print.JobInfo.PMJobName`, `PMJobOwner`) as the system print dialog sends them, which
|
||
some vendors' filters need (#5743). Windows renders sheets through
|
||
`Windows.Data.Pdf` and spools them at their physical PDF size with per-page paper and
|
||
orientation, zero added margins and hardware-edge clipping. Long-edge and short-edge duplex
|
||
use the corresponding Windows binding edges. Random, exclusively created Windows job files
|
||
are removed on success and failure. Other targets offer the print-ready PDF instead.
|
||
|
||
On Windows, **Printer default** reads the selected driver's current `DEVMODEW` duplex
|
||
setting immediately before submission and carries that binding explicitly into the job
|
||
(#5130). This also applies to `doc_print` with `duplex: "printer"`; its omitted-argument
|
||
default remains `off`. Explicit Off/Long Edge/Short Edge choices, booklet binding and
|
||
single-sided cut-and-stack bypass this lookup. Drivers without `DM_DUPLEX` retain the
|
||
unspecified setting. Two-sided defaults also stay unspecified when the driver does not
|
||
confirm automatic two-sided support, preserving manual duplex preferences (#5772).
|
||
A failed lookup, settings read or invalid declared duplex value is
|
||
logged and falls back to leaving duplex unspecified, so the driver can still choose its default.
|
||
If the queue was resolved before the settings failure, the job remains pinned to that queue. CUPS still leaves the choice to the queue. The Windows backend
|
||
selects `JobDuplexAllDocumentsContiguously`, falling back to `DocumentDuplex` when
|
||
the driver exposes only document-level duplex, and validates the retained binding before submission
|
||
([craft-libs #411](https://github.com/storytold/craft-libs/pull/411)).
|
||
|
||
Before a job reaches the Windows spooler, every embedded TrueType or OpenType program is
|
||
renamed `PdfCraftPrint<n>` in its `name` table and in the PDF (`/BaseFont`, `/FontName`; a
|
||
subset keeps its tag). Windows otherwise draws a subset's renumbered glyphs with the installed
|
||
font of the same name, so ReportLab's Arial subsets printed as shifted letters (#3132). Glyphs,
|
||
encodings and `/ToUnicode` are unchanged, and CUPS and Save as PDF get the PDF as it was. A
|
||
program PdfCraft can't read and rewrite keeps its old name in the PDF too, so a viewer that
|
||
can't load it either still picks its substitute by that name.
|
||
|
||
The shared dependency requires Rust 1.95. Transport and native Windows regression tests
|
||
live in craft-libs; local tests retain composition, facade, engine, automation and UI coverage.
|
||
The desktop UI discovers printers and submits jobs on workers, so neither operation blocks
|
||
the dialog; the Windows backend starts without a console window. Physical printer output
|
||
still needs hardware validation.
|
||
|
||
In a browser there is no spooler: the app (`pdfcraft-ui-egui`'s `web_print`) draws the print-ready
|
||
PDF's sheets with PdfCraft's renderer and prints them through the browser's print dialog, which
|
||
sets copies, two-sided printing and colour. This crate stays free of web APIs.
|
||
|
||
Not yet: poster labels, PostScript output, colour conversion for
|
||
grayscale.
|
||
|
||
## Architectural sheets and scale
|
||
|
||
The Paper menu and `doc_print` share Letter, Legal, Tabloid, A3/A4/A5/A6 plus
|
||
ARCH A/B/C/D/E1/E, ANSI C/D/E and ISO A0/A1/A2. ARCH D is 24 × 36 inches;
|
||
ANSI D is 22 × 34 inches. Dimensions follow the [HP DesignJet paper-size table](https://support.hp.com/hr-en/document/bpp01676).
|
||
Automation accepts short names such as `"ARCH D"` (case-insensitive) or the full menu label.
|
||
For example, `doc_print` with `paper: "ARCH D"`, `layout: "actual"`,
|
||
`orientation: "auto"` and `path: "plan.pdf"` writes a print-ready PDF at 100%.
|
||
|
||
**Match page at 100%** uses the currently viewed document page to choose a standard
|
||
sheet (either orientation, within 0.5 pt for rounded metric dimensions), Actual size
|
||
and Auto orientation. It leaves the selected page range intact. It is disabled
|
||
for a custom page size with no matching preset. One preset applies to the whole job;
|
||
this is not per-page paper-source selection.
|
||
|
||
**PDF scale** describes the source-to-sheet transform in the previewed print-ready
|
||
PDF, not measured physical output. Fit reserves an 18 pt margin and can reduce even
|
||
a matching page. On Windows craft-print sends the sheets as vectors with a PrintTicket
|
||
for the sheet's exact paper and no scaling (#599's approach), validated by the driver:
|
||
a job is refused, not shrunk, when the driver lacks the paper or would change it. Verify
|
||
the driver's paper settings and physical calibration before relying on drawing scale.
|
||
|
||
Vector printing resolves direct and indirect annotation rectangle coordinates before placing existing appearances.
|
||
Rectangles must contain exactly four finite numbers; malformed rectangles are skipped.
|
||
For an appearance-state dictionary, direct or indirect `AS` names select the existing artwork.
|
||
Rectangle arrays, individual coordinates and `AS` names follow up to 64 indirect references with cycle detection.
|
||
Missing, invalid, unresolved, cyclic, overly deep, or unknown state names leave that annotation unprinted; a single normal appearance stream does not require `AS`.
|
||
The print content mode still controls whether a field or comment prints, and printing preserves the source objects.
|