mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 15:49:54 +00:00
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>
This commit is contained in:
1 parent
8f5a283161
commit
bfbf82b360
5 files changed
+38
-8
No files matched your search
@@ -83,8 +83,10 @@ Native Windows PDF submission checks the driver's validated copy count and expli
|
||||
simplex/duplex and grayscale requests before sending. Collation must be retained when both
|
||||
the page count and copy count exceed one. A driver that drops or changes these options is
|
||||
refused; select supported options or flatten grayscale into the print-ready page content.
|
||||
Without an explicit duplex request, the queue default applies. Normal color output retains
|
||||
the driver's color policy. Single-page or single-copy jobs have no collating order to check.
|
||||
Without an explicit duplex request, the queue default applies. Without a grayscale request the
|
||||
ticket names no colour, so the colour in the user's printing preferences (Properties…, or
|
||||
Windows Settings) applies, black and white included. Single-page or single-copy jobs have no
|
||||
collating order to check.
|
||||
|
||||
`cargo test -p craft-print` tests CUPS transport (including pipe-sized jobs and refusal),
|
||||
printer parsing, PPD options and argument mapping, and on every platform the Windows pieces that
|
||||
|
||||
@@ -558,7 +558,7 @@ fn windows_preview(job: &Job) -> Vec<String> {
|
||||
Scaling::ShrinkToFit => "shrink-to-fit",
|
||||
}
|
||||
),
|
||||
format!("color={}", if job.grayscale { "monochrome" } else { "color" }),
|
||||
format!("color={}", if job.grayscale { "monochrome" } else { "(driver default)" }),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ fn command_previews_do_not_require_pdf_files() {
|
||||
} else {
|
||||
assert_eq!(
|
||||
preview,
|
||||
["xps-print", "printer=Office (east)", "copies=2", "collate=true", "duplex=(queue default)", "scaling=exact", "color=color"]
|
||||
["xps-print", "printer=Office (east)", "copies=2", "collate=true", "duplex=(queue default)", "scaling=exact", "color=(driver default)"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,15 @@ pub(crate) fn ticket_xml(job: &Job, landscape: bool) -> String {
|
||||
}
|
||||
};
|
||||
let collate = if job.collate { "Collated" } else { "Uncollated" };
|
||||
let color = if job.grayscale { "Monochrome" } else { "Color" };
|
||||
// Only grayscale is asked for. Without it the ticket says nothing about colour, so a black and
|
||||
// white chosen in the driver's own preferences (Properties…) still applies (storytold/pdfcraft#2422).
|
||||
let color = if job.grayscale { "\n <psf:Feature name=\"psk:PageOutputColor\"><psf:Option name=\"psk:Monochrome\"/></psf:Feature>" } else { "" };
|
||||
let copies = job.copies.clamp(1, 999);
|
||||
format!(
|
||||
r#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<psf:PrintTicket version="1" xmlns:psf="{NS_PSF}" xmlns:psk="{NS_PSK}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<psf:ParameterInit name="psk:JobCopiesAllDocuments"><psf:Value xsi:type="xsd:integer">{copies}</psf:Value></psf:ParameterInit>
|
||||
<psf:Feature name="psk:DocumentCollate"><psf:Option name="psk:{collate}"/></psf:Feature>{duplex}
|
||||
<psf:Feature name="psk:PageOutputColor"><psf:Option name="psk:{color}"/></psf:Feature>
|
||||
<psf:Feature name="psk:DocumentCollate"><psf:Option name="psk:{collate}"/></psf:Feature>{duplex}{color}
|
||||
<psf:Feature name="psk:PageScaling"><psf:Option name="psk:None"/></psf:Feature>
|
||||
<psf:Feature name="psk:PageOrientation"><psf:Option name="psk:{orientation}"/></psf:Feature>
|
||||
</psf:PrintTicket>"#
|
||||
@@ -256,10 +257,11 @@ mod tests {
|
||||
assert!(!xml.contains("<&>"), "the title never enters the XML");
|
||||
assert!(roxmltree::Document::parse(&xml).is_ok());
|
||||
let xml = ticket_xml(&Job { copies: 5000, ..Job::default() }, false);
|
||||
for value in [">999<", "psk:Collated", "psk:Color", "psk:Portrait"] {
|
||||
for value in [">999<", "psk:Collated", "psk:Portrait"] {
|
||||
assert!(xml.contains(value), "missing {value}");
|
||||
}
|
||||
assert!(!xml.contains("JobDuplex"), "no duplex asked: the queue's default applies");
|
||||
assert!(!xml.contains("PageOutputColor"), "no grayscale asked: the driver's own colour setting applies");
|
||||
assert!(ticket_xml(&Job { duplex: Some(Duplex::Off), ..Job::default() }, false).contains("psk:OneSided"));
|
||||
assert!(ticket_xml(&Job { duplex: Some(Duplex::LongEdge), ..Job::default() }, false).contains("psk:TwoSidedLongEdge"));
|
||||
assert!(!ticket_xml(&Job { printer: Some("Office \"east\" <1>".into()), ..Job::default() }, false).contains("east"));
|
||||
|
||||
@@ -242,4 +242,30 @@ mod tests {
|
||||
eprintln!("{sent:?}");
|
||||
assert!(sent.is_ok());
|
||||
}
|
||||
|
||||
/// A colour chosen in the driver's own preferences (Properties…) reaches the job when the job
|
||||
/// doesn't ask for grayscale: the validated ticket has the colour of the user's preferences,
|
||||
/// whatever they are (storytold/pdfcraft#2422; it was always Color before). Set the queue to
|
||||
/// black and white in its preferences to see the case that failed. Sends no job.
|
||||
#[test]
|
||||
#[ignore = "needs CRAFT_PRINT_TEST_PRINTER naming an installed printer; sends no job"]
|
||||
fn the_preferences_colour_reaches_a_job_without_grayscale() {
|
||||
fn colour(ticket: &PrintTicket) -> Option<String> {
|
||||
let xml = std::str::from_utf8(ticket.get_xml()).ok()?;
|
||||
let document = roxmltree::Document::parse(xml).ok()?;
|
||||
let feature = document
|
||||
.root_element()
|
||||
.children()
|
||||
.find(|n| n.has_tag_name((ticket::NS_PSF, "Feature")) && n.attribute("name").is_some_and(|a| a.ends_with(":PageOutputColor")))?;
|
||||
feature.children().find(|n| n.has_tag_name((ticket::NS_PSF, "Option")))?.attribute("name").map(str::to_owned)
|
||||
}
|
||||
let name = std::env::var("CRAFT_PRINT_TEST_PRINTER").unwrap();
|
||||
let device = select_device(PrinterDevice::all().unwrap(), Some(&name), None).unwrap();
|
||||
let mut builder = PrintTicketBuilder::new(&device).unwrap();
|
||||
builder.merge(PrintTicket::from_xml(winprint::ticket::DEFAULT_PRINT_TICKET_XML.as_bytes().to_vec())).unwrap();
|
||||
let preferences = colour(&builder.build().unwrap());
|
||||
eprintln!("the preferences' colour: {preferences:?}");
|
||||
let (ticket, _) = driver_ticket(&device, &Job::default(), (595.28, 841.89), false).unwrap();
|
||||
assert_eq!(colour(&ticket), preferences, "a job without grayscale keeps the preferences' colour");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user