* craft-print: ask macOS for Auto Select when a job names no tray
CUPS gives a job that names no tray the PPD's default tray. On a queue
whose default tray is empty (a Xerox AltaLink defaulting to Tray 1), the
printer holds every job ("Tray 1 is empty") while CUPS reports it
completed, so nothing prints. Preview prints because the macOS Print
dialog's Paper Feed: Auto Select sends AP_D_InputSlot with an empty value,
which drops the PPD's default tray (storytold/pdfcraft#3752).
- lp_args sends `-o AP_D_InputSlot=` on macOS when the job's options name
no InputSlot. A tray the job names wins; checked with cupsfilter on a
synthetic PPD: with both, CUPS emits the InputSlot's code.
- apply_queue_defaults (now shared with apps that read options
themselves, such as PdfCraft) gives the driver's tray option an
AUTO_SELECT choice, labelled "Auto Select", first and as the default,
so a dialog shows what lp_args sends. It is left out when the user's
lpoptions defaults name another tray, since lp adds that tray to every
job and it wins.
Linux jobs are unchanged: AP_D_InputSlot is Apple's.
* craft-print: match queue defaults to the PPD without regard to case
CUPS treats PPD option and choice names as case-insensitive. A queue
default spelled otherwise than the PPD (inputslot=tray2 for InputSlot
Tray2) was ignored, so on macOS apply_queue_defaults showed Auto Select
although lp would send Tray2. Keys and choices now match case-
insensitively and keep the PPD's spelling. lpoptions -l prints the PPD's
spelling, so this matters for callers passing defaults from elsewhere.
Review: storytold/craft-libs#232 (CodeRabbit).
* README: keep both the macOS tray and the Flatpak notes (the merge left the conflict markers in)
---------
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
printer_queues() (#618) lists the queues without asking any driver, so
every printer's two_sided is unknown there. A Print dialog listing from it
still needs to know whether the printer the user picks takes two-sided
settings, without asking every other driver as printers() does: Windows
takes about 48 s to give up on a printer it can't reach, such as a WSD
printer on another network (storytold/pdfcraft#2192).
two_sided(printer) finds that queue and reads its driver's capabilities
with the same check printers() makes, so the answers match. It is None
when there is no such queue, when the capabilities can't be read, and off
Windows, as printers() reports there.
The paused-printer test now checks that two_sided(name) gives printers()'s
answer for the test queue from exactly one driver query, and a new test
checks that a name no queue has is unknown. The query counting moves into
a small helper, which keeps windows_jobs_reach_the_spooler under the
cognitive-complexity limit.
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* craft-print: a job can be cancelled until it starts reaching the spooler
On Windows a job asks the printer's driver for its capabilities before
anything is sent. A networked printer in power save can hold that query
for a long time, and an app had no way to give up on the job without
risking it printing later (storytold/wordcraft#5710; CodeRabbit on
storytold/wordcraft#5732).
A Cancel token, passed to the new submit_cancellable, moves a job from
pending to cancelled or to sending. The job checks it at the last point
before anything reaches the spooler: on Windows after the driver has
answered, before the job file is written; on macOS and Linux before lp
starts. A cancelled job returns an error and is never sent; a job already
being sent completes. submit and submit_with_progress are unchanged, and
Error keeps its one variant so callers that match on it still compile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-print: a job turns to sending with the provider held; the job file waits for it
Review: marked as sending before it took the PrintTicket provider, a job
could then wait behind another job's driver call that a sleeping printer
held, and an app that waits only for jobs being sent would wait on it.
The job now turns to sending only once it holds the provider, right
before printing, and writes its file only after that, so a cancelled job
leaves no copy of the document behind.
A test-only hold in the driver call lets the Windows spooler test cancel
a job while its driver doesn't answer: the cancel wins and, once the
driver answers, the job stops instead of reaching the paused queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-print: the DEVMODE conversion comes before a job turns to sending
CodeRabbit on #622: winprint converts the ticket to a DEVMODE, a driver
call a sleeping printer can stall, before it creates the native job, so
a job stalled there had already turned to sending and couldn't be
cancelled. The conversion now runs first, with the provider held, while
the job can still be cancelled; the provider stays held until the first
copy's own conversion, so no other job comes between them.
Tests: the Windows spooler test also cancels a job held in that
conversion; the CUPS test gives a spooler that can't start, so any
attempt to start it would change the error. README: cancellation in the
progress section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-print: clarify cancellation during winprint conversion repeat
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On Windows, printers() reads each driver's PrintCapabilities to report
two-sided support before it returns. A networked printer in power save
can hold that query for a long time, so a Print dialog showed no printer
at all, not even the ones that were awake (storytold/wordcraft#5710,
storytold/wordcraft#5645, storytold/wordcraft#5413).
printer_queues() returns the same queues and default from EnumPrinters
and GetDefaultPrinter with two_sided unknown, and makes no driver call.
Submission still checks the job's options with the driver. On other
platforms it is printers(). printers() is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-print: log why the printer list is empty when lpstat can't run; document Flatpak needs
Flatpak apps saw "No printers found" with working CUPS printers: the Freedesktop
runtime has libcups but not lpstat/lp, and the sandbox had neither the CUPS socket
nor Avahi (driverless printers come from lpstat -e). Log the spawn failure and say
in the README what a Flatpak must bundle and grant.
* craft-print: log a failed lpstat -p -d; README separates what each Flatpak piece is for
Review (CodeRabbit): a non-zero lpstat exit was dropped silently, and the README
said any missing piece empties the printer list when only lpstat and the socket do.
* craft-print README: driver options need the PPD, remote servers need network, socket paths are remapped
Review (CodeRabbit): printer_options reads /etc/cups/ppd before lpoptions, so it is
the PPD, not lpoptions, that a Flatpak lacks; a client.conf server is reached only
with --share=network.
* craft-print README: scope the CUPS_SERVER override to one app; describe the stricter socket wrapper
Review (CodeRabbit): flatpak override without an app id applies to every app.
Windows' PTMergeAndValidatePrintTicket names a vendor namespace with a generated
prefix (ns0000) as soon as the merged ticket uses a name from it. driver_ticket
merges the driver's own paper option from its capabilities, which carries vendor
properties, so a Brother HL-L2390DW's ticket came back with ns0000 where the
driver's own default ticket says brpsk. XML-wise the two are the same; the
driver's print pipeline reads the prefix as text. The job was accepted and then
ID2D1PrintControl::AddPage (or Close, in older builds) failed with 0x80040003,
reported by winprint as "Render error" / "Failed to close print control: No such
interface supported".
The same ticket sent raw with brpsk prints; with only the prefix changed to
ns0000 it fails the same way, so the prefix is the whole difference.
driver_ticket now re-spells the validated ticket with the prefixes of the driver's
own default ticket (an empty ticket, validated), through ticket::respell: pure
XML, tested on every platform, and a no-op unless a vendor namespace is spelled
differently. It rewrites only the root declarations and the vendor names in name
/ constrained attributes and QName values, handles two namespaces trading
prefixes, and leaves the ticket as it is when a rename could be ambiguous or leave
a prefix undeclared.
Verified on a Brother HL-L2390DW: the job spools, and pdfcraft head's doc_print
that failed at AddPage prints.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
The driver's PrintCapabilities tell a driver that dropped a requested value
from one that never had the feature. Grayscale on a driver with no colour
option and uncollated copies on one that can't collate are accepted, like
one-sided on a queue without duplex. Collated copies on a driver that can't
collate are refused with their own message. The capabilities are read only
when the strict check fails.
The Windows job ticket always named a colour: Monochrome when the job
asked for grayscale, Color otherwise. Merged over the user's printing
preferences, that Color replaced a black and white the user had picked
in the driver's own Properties dialog, so the job came out in colour
(storytold/pdfcraft#2422).
The ticket now names a colour only when grayscale is asked for. Without
it the driver validates the ticket from the user's preferences, as the
CUPS backend and `verify_job_options` already assumed.
Checked on an HP OfficeJet 250 (PCL-3 driver) with its preferences set
to black and white: the validated ticket had psk:Color and the DEVMODE
dmColor 2 before, psk:Monochrome and dmColor 1 after. With colour
preferences it is psk:Color either way.
- `the_ticket_carries_the_job_options`: no PageOutputColor without
grayscale (fails on the old ticket).
- `the_preferences_colour_reaches_a_job_without_grayscale` (ignored,
needs CRAFT_PRINT_TEST_PRINTER; sends no job): the job's validated
colour equals the preferences' colour. Fails on the old ticket with the
queue set to black and white; passes on HP OfficeJet 250, Microsoft
Print to PDF and OneNote.
- The Windows command preview says `color=(driver default)`, like
`duplex=(queue default)`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
craft-print's Windows backend ran an embedded PowerShell script for the printer list and every
job. Under App Control for Business or AppLocker script enforcement that script runs in
Constrained Language Mode and every job fails (storytold/pdfcraft#1107). The backend now runs
in this process and starts no program:
- Printers come from EnumPrinters and the default from GetDefaultPrinter; Printer::two_sided
from each driver's PrintCapabilities. The list is sorted, each name once.
- A job is drawn as vectors by the in-box Windows.Data.Pdf with Direct2D into the XPS print
API (winprint). Copies, collation, duplex (left out for duplex: None), colour, orientation
and the driver's own paper option go in a PrintTicket the driver validates; a ticket the
driver changed (paper, orientation, scaling) is refused before anything is sent. The driver
paper named like the standard size is preferred (pdfcraft#599).
- A job has one ticket, so one paper shape. A small lenient reader (sheets.rs) reads the
print-ready PDF's sheets and, when needed, appends an incremental update: /Rotate for sheets
of the other orientation, and for Scaling::ShrinkToFit a reframe onto the driver's matching
or default paper (shrunk inside its widest hardware margin, never enlarged, centred).
Exact size refuses mixed paper sizes and paper the driver lacks.
- command_preview describes the in-process job on Windows ("xps-print", key=value options).
- windows_print.ps1 and the PowerShell helpers are gone. open_printer_preferences (rundll32)
and the CUPS path are unchanged, as is the public API.
The end-to-end test puts a paused Generic / Text Only printer on LPT1: (no port to remove
afterwards; a paused FILE: queue never takes the job) and checks a mixed-orientation exact job
and a shrink-to-fit job each land as one job with all sheets. The reader is tested on every
platform with classic and compressed cross-references and thousands of truncated and corrupted
files. Windows-only dependencies: winprint =0.2.1 (default features off), winsafe 0.0.29,
roxmltree 0.20, miniz_oxide 0.8.
Ported from PdfCraft's native printing branch (pdfcraft#1132), which carries #599's approach.
Co-authored-by: durtyrobbin <76844303+durtyrobbin@users.noreply.github.com>
Co-authored-by: Dimitar Klaturov <7221384+bulgariamitko@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>