collective

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

diff --git a/crates/ui-egui/src/app/prefs.rs b/crates/ui-egui/src/app/prefs.rs--- a/crates/ui-egui/src/app/prefs.rs+++ b/crates/ui-egui/src/app/prefs.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;         }diff --git a/crates/ui-egui/src/workflows/print_ui/body.rs b/crates/ui-egui/src/workflows/print_ui/body.rs--- a/crates/ui-egui/src/workflows/print_ui/body.rs+++ b/crates/ui-egui/src/workflows/print_ui/body.rs@@ -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();                 }diff --git a/crates/ui-egui/src/workflows/print_ui/draft.rs b/crates/ui-egui/src/workflows/print_ui/draft.rs--- a/crates/ui-egui/src/workflows/print_ui/draft.rs+++ b/crates/ui-egui/src/workflows/print_ui/draft.rs@@ -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(),diff --git a/crates/ui-egui/tests/it/print.rs b/crates/ui-egui/tests/it/print.rs--- a/crates/ui-egui/tests/it/print.rs+++ b/crates/ui-egui/tests/it/print.rs@@ -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);+}