Browse / github.com/storytold/pdfcraft / patch
feat(print): add eco print mode preset with grayscale and duplex (#4843)
Feature request: add an eco print mode with grayscale, duplex, and low-ink preset PdfCraft currently exposes generic print functionality, but it does not provide a dedicated toner/ink-saving preset. When printing documents for office or home use, users often need a mode that minimizes ink consumption by default. Requested behavior: - Add an `Eco / Draft` print preset - Allow grayscale or black-and-white output - Allow duplex / two-sided printing - Allow a lower DPI or "print as image" option for draft or low-ink output - Preserve the last used print mode across app restarts - Keep full-quality output available as `Standard` or `High Quality` Why this matters: - It reduces
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- YusufMntm (imported from pull request #4972, open)
- Base
1dd2737b47a9c1c9562f0af563053d1bf510e4bf- Size
- +95 −14 in 4 files
- Review
- pending
- Shared
- 2026-10-28
- Id
9569b4898963fd31a392bbff
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/app/prefs.rs crates/ui-egui/src/workflows/print_ui/body.rs crates/ui-egui/src/workflows/print_ui/draft.rs crates/ui-egui/tests/it/print.rs
@@ -147,6 +147,7 @@ impl PdfCraftApp { v["organize_zoom"] = serde_json::json!(self.organize_zoom); // Canonical names survive changes to the order of PAPERS and do not depend on UI language. v["print_paper"] = serde_json::json!(pdfcraft_engine::print::PAPERS.get(self.print_draft.paper).map(|(name, _)| *name));+ v["print_eco_mode"] = serde_json::Value::Bool(self.print_draft.eco_mode); v.to_string() } @@ -227,6 +228,13 @@ impl PdfCraftApp { { self.print_draft.paper = paper; }+ if let Some(on) = v.get("print_eco_mode").and_then(serde_json::Value::as_bool) {+ self.print_draft.eco_mode = on;+ if on {+ self.print_draft.grayscale = true;+ self.print_draft.duplex = Some(pdfcraft_engine::print::spool::Duplex::LongEdge);+ }+ } if let Some(on) = v["highlight_fields"].as_bool() { self.view_defaults.highlight_fields = on; }@@ -173,7 +173,9 @@ pub(crate) fn body( ui.horizontal(|ui| { ui.add(egui::DragValue::new(&mut d.copies).range(1..=999)).labelled_by(copies_label.id); ui.add(widgets::checkbox(&mut d.collate, tl!("Collate")));- ui.add(widgets::checkbox(&mut d.grayscale, tl!("Print in grayscale")));+ if ui.add(widgets::checkbox(&mut d.grayscale, tl!("Print in grayscale"))).changed() {+ d.eco_mode = d.grayscale && d.duplex == Some(spool::Duplex::LongEdge);+ } }); }); ui.end_row();@@ -182,19 +184,34 @@ pub(crate) fn body( if d.handling != Handling::Booklet { ui.label(tl!("Two-sided:")); ui.add_enabled_ui(!browser, |ui| {- combo(- ui,- "duplex",- tl!("Two-sided:"),- &mut d.duplex,- &[- (None, tl!("Printer default")),- (Some(spool::Duplex::Off), tl!("Off")),- (Some(spool::Duplex::LongEdge), tl!("Flip on long edge")),- (Some(spool::Duplex::ShortEdge), tl!("Flip on short edge")),- ],- 160.0,- );+ ui.horizontal(|ui| {+ let before_duplex = d.duplex;+ combo(+ ui,+ "duplex",+ tl!("Two-sided:"),+ &mut d.duplex,+ &[+ (None, tl!("Printer default")),+ (Some(spool::Duplex::Off), tl!("Off")),+ (Some(spool::Duplex::LongEdge), tl!("Flip on long edge")),+ (Some(spool::Duplex::ShortEdge), tl!("Flip on short edge")),+ ],+ 160.0,+ );+ if d.duplex != before_duplex {+ d.eco_mode = d.grayscale && d.duplex == Some(spool::Duplex::LongEdge);+ }+ if ui.add(widgets::checkbox(&mut d.eco_mode, tl!("Eco mode (Grayscale, two-sided)"))).changed() {+ if d.eco_mode {+ d.grayscale = true;+ d.duplex = Some(spool::Duplex::LongEdge);+ } else {+ d.grayscale = false;+ d.duplex = None;+ }+ }+ }); }); ui.end_row(); }@@ -33,6 +33,8 @@ pub struct PrintDraft { pub copies: u32, pub collate: bool, pub grayscale: bool,+ /// Eco print mode: quickly enables grayscale and duplex (two-sided) printing to save ink and paper.+ pub eco_mode: bool, /// `None` = the printer's own default (its Properties), as Acrobat does. pub duplex: Option<spool::Duplex>, pub which: Which,@@ -96,6 +98,7 @@ impl Default for PrintDraft { copies: 1, collate: true, grayscale: false,+ eco_mode: false, duplex: None, which: Which::All, range: String::new(),@@ -913,3 +913,56 @@ fn a_booklet_decides_two_sided_itself() { assert!(h.query_by_role_and_label(Role::ComboBox, "Two-sided:").is_some()); assert_eq!(h.state().print_draft.job("x").duplex, Some(Duplex::LongEdge), "back to the choice"); }++#[test]+fn eco_mode_enables_grayscale_and_duplex_and_persists() {+ use pdfcraft_engine::print::spool::Duplex;+ let mut h = harness();+ assert!(h.state_mut().execute("print.dialog"));+ h.run_steps(3);++ // Initial state: eco_mode is false, grayscale is false, duplex is None.+ assert!(!h.state().print_draft.eco_mode);+ assert!(!h.state().print_draft.grayscale);+ assert_eq!(h.state().print_draft.duplex, None);++ // Click Eco mode checkbox in the dialog.+ h.get_by_label("Eco mode (Grayscale, two-sided)").click();+ h.run_steps(2);++ assert!(h.state().print_draft.eco_mode);+ assert!(h.state().print_draft.grayscale);+ assert_eq!(h.state().print_draft.duplex, Some(Duplex::LongEdge));++ // Persist and verify JSON serialization.+ let saved = h.state().persist();+ let json: serde_json::Value = serde_json::from_str(&saved).unwrap();+ assert_eq!(json["print_eco_mode"], true);++ // Restore into a fresh app and verify settings are applied.+ let mut app = PdfCraftApp::new();+ app.restore(&saved);+ assert!(app.print_draft.eco_mode);+ assert!(app.print_draft.grayscale);+ assert_eq!(app.print_draft.duplex, Some(Duplex::LongEdge));++ // In the dialog, unticking grayscale unsets eco_mode.+ h.get_by_label("Print in grayscale").click();+ h.run_steps(2);+ assert!(!h.state().print_draft.grayscale);+ assert!(!h.state().print_draft.eco_mode);++ // Clicking Eco mode again re-enables both.+ h.get_by_label("Eco mode (Grayscale, two-sided)").click();+ h.run_steps(2);+ assert!(h.state().print_draft.eco_mode);+ assert!(h.state().print_draft.grayscale);+ assert_eq!(h.state().print_draft.duplex, Some(Duplex::LongEdge));++ // Unticking Eco mode resets grayscale and duplex.+ h.get_by_label("Eco mode (Grayscale, two-sided)").click();+ h.run_steps(2);+ assert!(!h.state().print_draft.eco_mode);+ assert!(!h.state().print_draft.grayscale);+ assert_eq!(h.state().print_draft.duplex, None);+}