69 Commits
Author SHA1 Message Date
FredApps 60ede99985 print: xps_driver says whether a Windows printer's driver renders XPS itself (#804)
* print: xps_driver says whether a Windows printer's driver renders XPS itself

craft_print::xps_driver(printer) reads PRINTER_DRIVER_XPS through winprint's new
PrinterDevice::is_xps_driver (storytold/winprint.rs#21): Some(true) for v4 drivers and v3
drivers with an XPS rendering filter, Some(false) for GDI drivers, None when it can't be read and
on other systems. On a GDI driver, reached through Windows' XPS-to-GDI conversion, a page with
transparency prints with halos, as black boxes or not at all (storytold/pdfcraft#6155, #5838,
#5940), so PdfCraft will flatten it for those drivers only.

The Windows end-to-end test checks the CI queue's "Generic / Text Only" driver reads as GDI.

* Test: the GDI-driver check in a helper (keeps the end-to-end test under the complexity limit)

* winprint bc06a7c48b768eb27a828c7986f6d2868b245ee0: storytold/winprint.rs main with #21 (PrinterDevice::is_xps_driver)
2026-11-11 07:48:44 +01:00
Juan JuradoandFredApps 1ee01a4c58 craft-print: ask macOS for Auto Select when a job names no tray (#232)
* 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>
2026-11-10 19:15:15 +01:00
FredApps 1146cdc4f9 print: grey pages print on a Windows driver with no grey option; ask for the one it lists (#774)
storytold/pdfcraft#5806: Print in grayscale was refused ("the printer's driver did not retain
the requested grayscale; nothing was sent") on drivers that list no grey option the ticket can
name: Microsoft Print to PDF lists only Color, and an HP Photosmart driver has only its own
ns0000:JobHPPrintInGrayScale.

- Job::grey_pages: the caller has already made the pages grey. Grayscale is still asked of the
  driver, but a driver that has no grey option, or doesn't keep it, prints the job instead of
  refusing it. Off (the default), such a driver is refused as before, since the pages would
  come out in colour.
- The ticket asks for psk:Grayscale when the driver lists that and not psk:Monochrome
  (grey_option); Windows dropped the Monochrome it didn't list and colour came back.
- Offered::grey records whether PageOutputColor has a grey option at all.
2026-11-10 15:16:57 +01:00
FredApps 3edc157a2c print: the CUPS printer list waits at most 15 s for lpstat, 10 s for discovery (#773)
printers() ran `lpstat -l -p -d` and `lpstat -e` with no time limit. When the CUPS scheduler
stops answering (macOS cupsd stopped serving its local socket in storytold/pdfcraft#6761), both
block forever and the Print dialog looks for printers until the app is closed.

Both now run through output_within (the bounded wait pdfcraft's lpoptions read already uses):
the queues get 15 s, network discovery 10 s. A scheduler that doesn't answer gives an empty list
and a warning naming cupsd; discovery that hangs only loses the driverless printers.
2026-11-10 15:16:51 +01:00
FredApps 0e4943cb5e print: winprint 3c978dfc: pages at the job's resolution, QName ticket values, padded integers (#761)
winprint.rs#19 draws each page at the resolution the job's ticket asks for instead of ~96 dpi
(storytold/pdfcraft#4941, #2676). #12 writes QName property values as prefix:local, which
drivers such as the Microsoft IPP Class Driver otherwise reject when merging the ticket
(storytold/pdfcraft#3823). #3 accepts whitespace around xsd:integer values in
PrintCapabilities (Toshiba Universal Printer 2, storytold/pdfcraft#2430).

The public API craft-print uses is unchanged; only the rev and the lockfile move.
2026-11-10 01:55:59 +01:00
art804andFredApps b6b8a55b2c craft-print: two_sided(printer) reads one printer's two-sided support (#133)
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>
2026-11-10 00:03:35 +01:00
robertobulltechandrobertobulltech 92451193ba fix(print): honor DocumentDuplex-only Windows drivers (#411)
* fix(print): honor DocumentDuplex-only Windows drivers (#5130)

* fix(print): select duplex scope by two-sided options (#5130)

* docs(print): clarify duplex ticket selection and validation

* fix(print): select duplex scope for the requested option

* fix(print): require standard namespace for duplex options

* docs(print): document duplex regression

* docs(print): describe both offered duplex scopes

---------

Co-authored-by: robertobulltech <341520377+robertobulltech@users.noreply.github.com>
2026-11-09 23:48:52 +01:00
Zhihui Song 7ee35a9fbd chore(print): refresh internal winprint pin after bounded stream test repair 2026-11-08 19:07:09 +08:00
Zhihui Song e6489d9226 print: preserve the public GDI dependency identity while updating the PDF loader 2026-11-08 11:05:10 +08:00
Zhihui Song 45b7ededd9 Merge main after #605 and adopt the reviewed Windows stream test repairs 2026-11-08 10:29:10 +08:00
058b047760 print: winprint 64e7afd4, whose Direct2D factory is multi-threaded (#605)
Windows.Data.Pdf draws a page on several of its own worker threads, which
share the print control's device context. With winprint 5f80b9b's
single-threaded factory Direct2D takes no locks, so those threads race in
d2d1.dll and the app closes while printing, most often on pages with Type 3
fonts (storytold/pdfcraft#5006, storytold/pdfcraft#2684).
storytold/winprint.rs#11 made the factory multi-threaded; it was merged as
64e7afd4.

craft-winprint-gdi moves to the same revision, so one checkout of
winprint.rs serves both; crates/gdi is unchanged between 417ac3c and
64e7afd4.

Co-authored-by: mertan-wq <235487900+mertan-wq@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:42:38 -06:00
Zhihui Song 93d7c7b57a print: adopt on-demand file loading without whole-job buffering 2026-11-07 15:06:10 +08:00
Zhihui Song e398084b63 print: take stream loading alongside the multithreaded Direct2D fix 2026-11-07 14:44:13 +08:00
ashandClaude Opus 5.5 a3e0b5a944 craft-print: a job can be cancelled until it starts reaching the spooler (#622)
* 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>
2026-11-05 21:32:35 -05:00
arnav-hiwarkarandClaude Opus 5.5 81c2488a09 craft-print: document spooler_list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 05:07:25 +05:30
arnav-hiwarkarandClaude Opus 5.5 a20c900ce0 craft-print: the Windows test counts driver queries during printer_queues
The live spooler test only checked that printer_queues() reports
two_sided as unknown, which would still pass if it called printers() and
cleared the field (CodeRabbit on #618). A test-only per-thread counter in
two_sided(), the one PrintCapabilities query printer discovery makes,
now shows printers() querying the test queue's driver (the control) and
printer_queues() querying none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 05:04:48 +05:30
arnav-hiwarkarandClaude Opus 5.5 62b0fe5edb craft-print: printer_queues lists the queues without asking any driver
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>
2026-11-06 04:54:11 +05:30
mertan-wqandClaude Opus 5.5 6485116bca print: winprint 64e7afd4, whose Direct2D factory is multi-threaded
Windows.Data.Pdf draws a page on several of its own worker threads, which
share the print control's device context. With winprint 5f80b9b's
single-threaded factory Direct2D takes no locks, so those threads race in
d2d1.dll and the app closes while printing, most often on pages with Type 3
fonts (storytold/pdfcraft#5006, storytold/pdfcraft#2684).
storytold/winprint.rs#11 made the factory multi-threaded; it was merged as
64e7afd4.

craft-winprint-gdi moves to the same revision, so one checkout of
winprint.rs serves both; crates/gdi is unchanged between 417ac3c and
64e7afd4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:11:16 +03:00
Dylan M. Taylor cb82b90580 print: charge skipped whitespace and comments to the lexer's token budget (#547)
* print: charge skipped whitespace and comments to the lexer's token budget

Lexer::skip_ws scanned whitespace and comments without charging them,
so an annotation object behind a long comment was rescanned in full for
every reference to it, outside the per-object and job-wide budgets. The
skipped bytes now count like tokens, and the scan stops at the budget.

* address review: fail objstm load when header lexer exhausts, restore token charge on failed reference lookahead

* address review: charge objstm header lexer tokens to the job budget
2026-11-03 22:58:48 -05:00
.CooCoo 69c268014e craft-print: serialize calls to the Windows PrintTicket provider (#171)
The first time a process opens a PrintTicket provider for a queue whose
provider is PrintConfig.dll, winspool.drv creates a process-wide cache
without a lock. When two threads make those first calls together,
winspool can keep a pointer to a cache it has already freed, and a later
PTOpenProvider call crashes the process with an access violation in
ntdll. Listing printers from several threads at once reaches it through
two_sided.

Hold one process-wide lock around every call that reaches the provider:
PrintCapabilities::fetch in two_sided and driver_ticket, the
PrintTicketBuilder in driver_ticket, fetch_xml_for_ticket in
imageable_margin, and the start of print_with_progress, where winprint
converts the ticket to a DEVMODE. The job releases the lock at its first
progress report, before it sends progress to the submitting thread. A
lock poisoned by a panic is still taken.
2026-10-31 23:29:47 -04:00
Dylan M. Taylor 733a038aa5 craft-print: log why the printer list is empty when lpstat can't run; document Flatpak needs (#429)
* 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.
2026-10-29 18:48:26 -04:00
aiongg b4afa6d22a Merge pull request #307 from rbulboaca/fix/windows-print-vendor-ticket-prefix
print: give the job ticket the driver's own prefix for its vendor nam…
2026-10-28 00:47:44 -04:00
aiongg 5f00550cf7 print: respell a QName value written after whitespace
A pretty-printed ticket puts whitespace before the value, so the prefix
starts after it; without this the ticket fell back to Windows' spelling.
2026-10-28 04:28:55 +00:00
skylar taylor-barrick cb53221767 fix(print): stop relying on inflate error output; bump miniz_oxide to 0.9 (#348)
* fix(print): do not treat early inflate errors as partial output

* chore(deps): bump miniz_oxide to 0.9
2026-10-25 20:37:49 -04:00
skylar 8e42b9ff4e fix(print): keep miniz_oxide at 0.8 2026-10-25 16:52:57 -06:00
skylar ce020c5811 chore(deps): bump roxmltree, miniz_oxide, serde_json, fixed_decimal
craft-print: roxmltree 0.20 -> 0.21 (locked 0.21.2, fixes a stack
overflow on deeply nested XML) and miniz_oxide 0.8 -> 0.9.
Lockfile only: serde_json 1.0.154, fixed_decimal 0.7.3.
2026-10-25 16:35:43 -06:00
Claude 23bd96375a print: the live driver test compares the vendor prefixes instead of re-running respell
Review follow-up. The test asserted `respell(validated, driver_default) == None`.
respell also returns None when it refuses a ticket, and in_drivers_spelling then
keeps the ticket as it is, so a ticket still spelled `ns0000` passed.

The test now compares the vendor namespace declarations of the ticket driver_ticket
returns with those of the driver's own default ticket and fails when a namespace the
driver declares is under another prefix. The comparison is
`ticket::prefixes_unlike_the_drivers` (test-only), unit-tested here: it flags the ticket
as the merge returns it, passes the respelled one and the driver's own, and flags a
ticket respell refused, where `respell(..) == None` can't tell it from a correct one.

Checked against a Brother HL-L2390DW: the test passes, and fails with
`(brother namespace, "ns0000", "brpsk")` when in_drivers_spelling is made to return
the merged ticket.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
2026-10-25 06:34:58 +00:00
Claude 766f1c6f2d print: respell leaves a ticket alone when an element or attribute name would move
Review follow-up. When two vendor namespaces trade prefixes, both prefixes stay
declared, so a name written with one of them as an element or attribute name
(`<ns0000:Feature/>`, `ns0000:flag="1"`) still parsed and resolved after the
root declarations changed, but to the other namespace. respell only rewrites
`name`/`constrained` values and QName values, so such a name would not follow.

respell now returns None when any element or attribute is named in a namespace
whose prefix changes. Names in a namespace that keeps its prefix do not block
the respelling, and the real Brother tickets (vendor names only in `name`
attributes and QName values) are respelled as before.

Tests: regression cases for element and attribute names in a swap and in a
prefix chain, and a control for names in an untouched namespace. Each ticket
is asserted well-formed with every prefix resolving, so the case is refused
for the name, not for a parse failure. The refusal tests fail with the guard
disabled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
2026-10-25 02:58:18 +00:00
aiongg 865c9af7fd print: collated copies that can't be one job print as a job per copy (#308) 2026-10-24 22:25:28 -04:00
aiongg 2869b8cc10 print: collated copies on a Windows driver that can't collate repeat the pages in one job (#305) 2026-10-24 22:11:16 -04:00
Claude fb59c587df print: respell refuses a nested redeclaration; test the vendor-element case for real
Review of #307:

- respell rewrites the root's namespace declarations, so an element below the
  root that declares either prefix of a rename again (ns0000 or brpsk) would make
  a name resolve to another namespace once the prefixes change. It now returns
  None for such a ticket, and a test covers both prefixes.
- The vendor-element test replaced only an opening tag, left the closing tag
  behind and so never parsed: it passed because respell refused the broken XML,
  not because of what it was meant to check. Every refusal fixture is now
  asserted well-formed, and the element is replaced whole. Doing so showed the
  element guard was redundant: an element or attribute name in a renamed
  namespace leaves an undeclared prefix, which the final parse of the result
  already refuses. The guard is removed and the test now exercises that check.
- Doc comments on the new constants and tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
2026-10-25 02:06:11 +00:00
Claude 0348d2990f print: give the job ticket the driver's own prefix for its vendor namespace
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
2026-10-25 01:35:49 +00:00
aiongg 13bb99663e print: DocumentDuplex counts as a driver's duplex feature 2026-10-24 23:37:08 +00:00
aiongg 00f0656e36 print: refuse a Windows job option only when the driver offers it and dropped it
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.
2026-10-24 23:23:12 +00:00
aiongg 941316b7ce Merge storytold/craft-libs#141 (Windows ticket names a colour only for grayscale) 2026-10-24 23:17:33 +00:00
aiongg 286212626e Merge remote-tracking branch 'upstream/main' into print-verify-capabilities 2026-10-24 23:16:52 +00:00
Dylan M. Taylor 4f85efb2b9 security: bound print dialog name resolution, PPD labels, lpstat merge and inherited geometry
- craft-ui shown_names resolves label collisions with hash-map counts and a worklist, so a
  crafted cascade is linear instead of cubic (CodeRabbit CWE-407, ui/src/printing.rs:32)
- parse_ppd caps group, option and choice labels at 256 characters before each option copies
  its group (CodeRabbit CWE-400, print/src/lib.rs:232)
- printers_parsed tracks seen destinations in a HashSet (CodeRabbit CWE-407, lib.rs:417)
- sheets::Reader::rect checks an inline array's length before reading it and borrows direct
  objects instead of deep-cloning the inherited MediaBox/CropBox/Rotate for every page
  (CodeRabbit CWE-400, print/src/sheets.rs:774)
2026-10-22 21:49:46 -04:00
Bishal De 2bbef53f29 craft-print, craft-ui: show printers by name, not CUPS queue name (#192)
* print, ui: a printer is shown by its name, not its CUPS queue name

CUPS queue names can't hold dots or spaces, so macOS adds the printer at
192.0.2.15 as the queue `_192_0_2_15`, and the Print dialog showed that
underscored name. The name the user knows is the queue's description
(CUPS `printer-info`).

craft-print now lists queues with `lpstat -l -p -d` (same lines, plus
each queue's indented details) and keeps the first `Description:` as
`Printer::description`, capped at 256 characters. Windows names are
already human, so they leave it empty.

craft-ui's `Printer` gains `label`; the dialog shows it, or the name when
it's empty, and `shown_names` adds the queue name in brackets when two
printers would show the same text. Selection and jobs still use `name`.

Both structs gain a field, so apps that build them by hand add it
(`..Default::default()` works for craft-ui's) when they update the pin.

* print, ui: an empty first description stays first, and shown names never collide

parse_lpstat tracks whether a queue has had its Description line, so an
empty first one isn't replaced by a later one. shown_names checks the
final texts: printers whose bracketed text another label already reads
show their queue names instead.
2026-10-20 09:08:21 -06:00
magdale76andClaude Opus 5.5 bfbf82b360 fix(print): a black and white chosen in the printer's preferences applies
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>
2026-10-15 10:17:48 +02:00
Justin CapellaandClaude Sonnet 5.5 18c4d3146a Accept a one-sided request on a queue whose driver has no duplex feature
The Windows path refused any explicit duplex request whose validated PrintTicket lacked the duplex feature. The Print dialog asks for one-sided by default, so every queue without a duplex unit refused with default settings, although one-sided is all such a queue can do and nothing was dropped.

A one-sided request is now satisfied when the driver's PrintCapabilities offer no duplex feature at all. Two-sided requests without the feature, a driver that offers the feature but returns a ticket without the setting, and capabilities that cannot be read are still refused. Copies, collation, colour, paper, orientation and scaling are checked as before.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 20:11:20 -07:00
skylar taylor-barrick 3d37da389a Merge current core print progress while retaining submission guards 2026-10-13 05:03:53 +02:00
skylar taylor-barrick d873514e8a Document driver option refusal and equivalent grayscale capability 2026-10-13 04:50:19 +02:00
skylar taylor-barrick 3929c3d14c Refuse explicit print options dropped by Windows drivers 2026-10-13 04:48:42 +02:00
skylartaylor 6ba4d0b97a Refuse annotation visibility the native renderer cannot print correctly 2026-10-13 03:29:55 +02:00
skylartaylor ee70afc29a Pin driver copy-validity qualification fix 2026-10-13 02:49:34 +02:00
skylartaylor 8fb89562d1 Expose shared safe driver settings and RGB print transport 2026-10-13 02:15:46 +02:00
skylartaylor ccb1eae613 fix(print): refuse fitting that would discard annotations 2026-10-13 01:47:19 +02:00
skylartaylor 4af92ad6aa feat(print): bridge native page processing to submitter callbacks 2026-10-13 01:20:21 +02:00
skylartaylorandTKIPisalegacycipher ebc4d8cfbd Add caller-thread progress for native sheet processing
Co-authored-by: TKIPisalegacycipher <git@chir.us>
2026-10-13 01:20:21 +02:00
skylar taylor-barrick 697b473c0b Stop PDF work immediately after a parser budget is exhausted 2026-10-12 23:27:53 +02:00