fix(print): send Windows plots to the chosen printer, name the default, report failures

On Windows a plot with no printer chosen went to the PDF application's
bare "print" verb — whatever that application considered its default —
and a viewer without a "printto" verb (Edge, store readers) silently
dropped the printer the dialog had chosen. The dialog also never named the
system default, and a failed printer enumeration left only "Default" and
"PDF" with the reason written to a console nobody sees.

- The default printer is looked up by name (GetDefaultPrinterW) both for
  the dialog's "System default printer (…)" entry and before every job.
- A pure route decision (plan_print_route) resolves "Default" to that
  name and uses the PDF application only when it registered a "printto"
  verb for .pdf (AssocQueryStringW); otherwise the job goes straight to
  the spooler through the existing GDI path. The report says which:
  "Sent to printer: NAME (direct print | via the PDF application)".
- list_printers reports the system's own error; the plot dialog shows it
  under the printer row and the command line warns once. A desktop without
  CUPS is an empty list, not an error.
- PRINTERS command: default printer, listed printers (or the error) and,
  on Windows, the print route — a report to paste into an issue.
- Properties… on the Default entry opens that printer's preferences sheet.
- GDI path: the driver's dmSize is kept (its private data lives behind
  it), the print raster is no longer memoised for the session, and a
  spooled temporary PDF is removed.

Tests: route planning (named / blank / no default / printto or not), the
PRINTERS command; the two Windows-only GDI tests pass the resolved name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-18 15:49:27 +03:00
1 parent 2b38f798c0
commit c36ae08fd0
29 files changed
+592 -112

No files matched your search

+13
View File
@@ -365,6 +365,11 @@ pub struct PlotDialogState {
/// sentinels.
#[serde(skip)]
pub printers: Vec<String>,
/// Why the system gave no printer list (a stopped spooler, a failing
/// print service), shown under the printer row so an empty list is
/// never mistaken for "no printers".
#[serde(skip)]
pub printers_error: Option<String>,
/// Name of the system default printer, shown next to the default entry.
#[serde(skip)]
pub default_printer: Option<String>,
@@ -452,6 +457,7 @@ impl Default for PlotDialogState {
fn default() -> Self {
Self {
printers: Vec::new(),
printers_error: None,
default_printer: None,
printer_media: None,
custom_papers: Vec::new(),
@@ -1090,6 +1096,13 @@ pub fn view_window(
// editor itself unfolds there when Properties… is pressed.
let printer_note: Element<'_, Message> = match (&s.printer_editor, &s.printer) {
(Some(draft), _) => printer_options_editor(draft, width),
(None, _) if s.printers_error.is_some() => {
let error = s.printers_error.clone().unwrap_or_default();
text(crate::tf!("Could not list printers: {error}"))
.size(10)
.style(muted_style)
.into()
}
(None, Some(printer)) if s.driver_options.get(printer).is_some_and(|o| !o.is_empty()) => {
let count = s.driver_options[printer].len();
text(crate::tf!("{count} driver option(s) set for this printer."))