app: wire web export, preview, and print flows through the plot UI

- plot dialog: the web build defaults to 'Save to PDF file...' (its only
  output choice); native dropdowns and defaults are untouched.
- on_plot_dlg_commit forces to_file on wasm so a stale page setup can no
  longer route the web build into the (unreachable) printer path; both
  preview branches now go through preview_page_pdf, which is identical to
  the old inline temp-PDF + OS-viewer flow on desktop.
- Print ribbon / PRINTALL on wasm render the PDF and open it in a tab
  instead of erroring; PrintAllPdf picks a virtual path and downloads.
- pdf_plot_options maps the dialog's Quality dropdown into PdfPlotOptions.
This commit is contained in:
Afonsop committed 2026-09-15 17:51:29 +01:00
1 parent 4812bc775d
commit 892d693d2d
4 files changed
+101 -52

No files matched your search

+2
View File
@@ -3201,6 +3201,8 @@ pub enum Message {
/// Send current layout to the system printer (via lp / lpr). /// Send current layout to the system printer (via lp / lpr).
PrintToPrinter, PrintToPrinter,
/// Callback from the async printer job. /// Callback from the async printer job.
/// Web never constructs it — printing opens the PDF preview instead.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
PrintResult(Result<String, String>), PrintResult(Result<String, String>),
/// Open the full Plot / Print dialog (seeds state from the layout). /// Open the full Plot / Print dialog (seeds state from the layout).
PlotDialogOpen, PlotDialogOpen,
+62 -39
View File
@@ -3210,33 +3210,37 @@ impl OpenCADStudio {
} }
pub(super) fn on_print_all_print(&mut self) -> Task<Message> { pub(super) fn on_print_all_print(&mut self) -> Task<Message> {
let dialog = self.plot_dialog.clone();
if self.print_all_settings_override && dialog.style_missing && dialog.apply_plot_styles {
self.command_line.push_error(
crate::tf!("Plot style table '{}' is not loaded.", dialog.style_name).as_ref(),
);
return Task::none();
}
let pages = match self.print_all_pages() {
Ok(pages) => pages,
Err(error) => {
self.command_line.push_error(&error);
return Task::none();
}
};
self.save_config();
self.close_active_modal();
// The web build has no system printers: render every layout into one
// PDF and open it in a browser tab for the browser print dialog.
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
self.command_line let work = move || {
.push_error(crate::t!("Printing is not available in the web version.").as_ref()); crate::io::print_to_printer::preview_pages_pdf(&pages)
Task::none() .map(|_| crate::t!("Opened plot preview.").into_owned())
.map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
};
self.run_print_all_work(true, work)
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
let dialog = self.plot_dialog.clone();
if self.print_all_settings_override && dialog.style_missing && dialog.apply_plot_styles
{
self.command_line.push_error(
crate::tf!("Plot style table '{}' is not loaded.", dialog.style_name).as_ref(),
);
return Task::none();
}
let pages = match self.print_all_pages() {
Ok(pages) => pages,
Err(error) => {
self.command_line.push_error(&error);
return Task::none();
}
};
let options = self.plot_print_options(&dialog); let options = self.plot_print_options(&dialog);
let temp_path = crate::io::print_to_printer::temp_pdf_path("print_all"); let temp_path = crate::io::print_to_printer::temp_pdf_path("print_all");
self.save_config();
self.close_active_modal();
self.command_line self.command_line
.push_info(crate::t!("Sending selected layouts to the system printer…").as_ref()); .push_info(crate::t!("Sending selected layouts to the system printer…").as_ref());
let work = move || { let work = move || {
@@ -3477,17 +3481,33 @@ impl OpenCADStudio {
); );
return Task::none(); return Task::none();
}; };
let options = self.plot_print_options(&self.plot_dialog); // The web build has no system printers: PRINT opens the rendered PDF
self.command_line // in a browser tab, where the browser's own print dialog takes over.
.push_info(crate::t!("Sending to system printer…").as_ref()); #[cfg(target_arch = "wasm32")]
background_task( {
move || { background_task(
iced::futures::executor::block_on( move || {
crate::io::print_to_printer::print_wires_with(page, options), crate::io::print_to_printer::preview_page_pdf(&page)
) .map(|_| crate::t!("Opened plot preview.").into_owned())
}, .map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
Message::PrintResult, },
) |result| Message::BackgroundIoFinished(result, false),
)
}
#[cfg(not(target_arch = "wasm32"))]
{
let options = self.plot_print_options(&self.plot_dialog);
self.command_line
.push_info(crate::t!("Sending to system printer…").as_ref());
background_task(
move || {
iced::futures::executor::block_on(
crate::io::print_to_printer::print_wires_with(page, options),
)
},
Message::PrintResult,
)
}
} }
/// QUICKPRINT / QP — use the current selection's bounding box as the plot /// QUICKPRINT / QP — use the current selection's bounding box as the plot
@@ -4354,6 +4374,12 @@ impl OpenCADStudio {
/// Open a preview PDF, export a PDF, or send the job to the chosen printer. /// Open a preview PDF, export a PDF, or send the job to the chosen printer.
fn on_plot_dlg_commit(&mut self, preview: bool) -> Task<Message> { fn on_plot_dlg_commit(&mut self, preview: bool) -> Task<Message> {
let d = self.plot_dialog.clone(); let d = self.plot_dialog.clone();
// The web dialog only offers PDF output, but a page setup loaded from
// the drawing can still seed `to_file = false` — always export there.
#[cfg(target_arch = "wasm32")]
let to_file = true;
#[cfg(not(target_arch = "wasm32"))]
let to_file = d.to_file;
if d.style_missing && d.apply_plot_styles { if d.style_missing && d.apply_plot_styles {
self.command_line.push_error( self.command_line.push_error(
crate::tf!("Plot style table '{}' is not loaded.", d.style_name).as_ref(), crate::tf!("Plot style table '{}' is not loaded.", d.style_name).as_ref(),
@@ -4391,16 +4417,14 @@ impl OpenCADStudio {
// Preview renders to a temp PDF and opens it — never a save dialog, // Preview renders to a temp PDF and opens it — never a save dialog,
// whatever the output target is. // whatever the output target is.
if preview { if preview {
let tmp = crate::io::print_to_printer::temp_pdf_path("preview");
let work = move || { let work = move || {
crate::io::pdf_export::export_pdf(&page, &tmp) crate::io::print_to_printer::preview_page_pdf(&page)
.and_then(|_| crate::io::print_to_printer::open_in_viewer(&tmp))
.map(|_| crate::t!("Opened plot preview.").into_owned()) .map(|_| crate::t!("Opened plot preview.").into_owned())
.map_err(|e| crate::tf!("Preview failed: {e}").into_owned()) .map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
}; };
return self.run_plot_work(d.background, true, work); return self.run_plot_work(d.background, true, work);
} }
if d.to_file { if to_file {
// Tested clipped export (opens a save dialog). // Tested clipped export (opens a save dialog).
return Task::done(Message::PlotWindowExport); return Task::done(Message::PlotWindowExport);
} }
@@ -4420,17 +4444,15 @@ impl OpenCADStudio {
let page = self.layout_plot_page(); let page = self.layout_plot_page();
if preview { if preview {
let tmp = crate::io::print_to_printer::temp_pdf_path("preview");
let work = move || { let work = move || {
crate::io::pdf_export::export_pdf(&page, &tmp) crate::io::print_to_printer::preview_page_pdf(&page)
.and_then(|_| crate::io::print_to_printer::open_in_viewer(&tmp))
.map(|_| crate::t!("Opened plot preview.").into_owned()) .map(|_| crate::t!("Opened plot preview.").into_owned())
.map_err(|e| crate::tf!("Preview failed: {e}").into_owned()) .map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
}; };
return self.run_plot_work(d.background, true, work); return self.run_plot_work(d.background, true, work);
} }
if d.to_file { if to_file {
// Reuse the tested PDF export flow (it opens a save dialog). // Reuse the tested PDF export flow (it opens a save dialog).
return Task::done(Message::PlotExport); return Task::done(Message::PlotExport);
} }
@@ -4470,6 +4492,7 @@ impl OpenCADStudio {
transparency: d.transparency, transparency: d.transparency,
stamp: d.stamp, stamp: d.stamp,
merge_lines: d.merge_lines, merge_lines: d.merge_lines,
quality: crate::io::pdf_export::PlotQuality::from_dialog(&d.quality),
} }
} }
+4 -5
View File
@@ -8032,11 +8032,10 @@ impl OpenCADStudio {
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
let _ = stem; Task::perform(
self.command_line.push_error( crate::io::pdf_export::pick_pdf_path_owned(stem),
crate::t!("PDF export is not available in the web version.").as_ref(), Message::PrintAllPdfPath,
); )
Task::none()
} }
} }
Message::PrintAllPdfPath(None) => Task::none(), Message::PrintAllPdfPath(None) => Task::none(),
+33 -8
View File
@@ -3,6 +3,8 @@
//! quality and output options into one dialog; on commit it either sends the //! quality and output options into one dialog; on commit it either sends the
//! current layout to a system printer (with the chosen options) or writes a //! current layout to a system printer (with the chosen options) or writes a
//! PDF. Styled to match the other OCS dialogs (dark pills + fields). //! PDF. Styled to match the other OCS dialogs (dark pills + fields).
//! The web build has no system printers, so PDF is the only output it
//! offers — and its default.
use crate::app::Message; use crate::app::Message;
use crate::io::paper_sizes::PaperSize; use crate::io::paper_sizes::PaperSize;
@@ -211,7 +213,8 @@ impl Default for PlotDialogState {
Self { Self {
printers: Vec::new(), printers: Vec::new(),
printer: None, printer: None,
to_file: false, // Web defaults to PDF output: it has no system printers.
to_file: cfg!(target_arch = "wasm32"),
paper: "A4".into(), paper: "A4".into(),
paper_width_mm: 297.0, paper_width_mm: 297.0,
paper_height_mm: 210.0, paper_height_mm: 210.0,
@@ -525,6 +528,24 @@ fn choices(items: &[&str]) -> Vec<PlotChoice> {
items.iter().map(|&value| PlotChoice::localized(value)).collect() items.iter().map(|&value| PlotChoice::localized(value)).collect()
} }
/// Entries for the output dropdown. The web build has no system printers, so
/// PDF is the only output it offers; native lists the system default, every
/// discovered printer, then PDF.
fn output_choices(printers: &[String]) -> Vec<PlotChoice> {
#[cfg(target_arch = "wasm32")]
{
let _ = printers;
vec![PlotChoice::localized(OUT_PDF)]
}
#[cfg(not(target_arch = "wasm32"))]
{
let mut out = vec![PlotChoice::localized(OUT_DEFAULT)];
out.extend(printers.iter().cloned().map(PlotChoice::raw));
out.push(PlotChoice::localized(OUT_PDF));
out
}
}
pub fn view_window( pub fn view_window(
s: &PlotDialogState, s: &PlotDialogState,
print_all_options: bool, print_all_options: bool,
@@ -532,9 +553,15 @@ pub fn view_window(
) -> Element<'_, Message> { ) -> Element<'_, Message> {
let width = sizing.width; let width = sizing.width;
let height = sizing.height; let height = sizing.height;
// The web build always outputs PDF, whatever a stored dialog state says;
// native follows the stored choice.
#[cfg(target_arch = "wasm32")]
let to_file = true;
#[cfg(not(target_arch = "wasm32"))]
let to_file = s.to_file;
let action = if print_all_options { let action = if print_all_options {
t!("Apply") t!("Apply")
} else if s.to_file { } else if to_file {
t!("Export PDF") t!("Export PDF")
} else { } else {
t!("Print") t!("Print")
@@ -648,10 +675,8 @@ pub fn view_window(
.spacing(4); .spacing(4);
// ── Printer / plotter ───────────────────────────────────────────────── // ── Printer / plotter ─────────────────────────────────────────────────
let mut printer_opts = vec![PlotChoice::localized(OUT_DEFAULT)]; let printer_opts = output_choices(&s.printers);
printer_opts.extend(s.printers.iter().cloned().map(PlotChoice::raw)); let printer_sel = if to_file {
printer_opts.push(PlotChoice::localized(OUT_PDF));
let printer_sel = if s.to_file {
Some(PlotChoice::localized(OUT_PDF)) Some(PlotChoice::localized(OUT_PDF))
} else { } else {
Some(match &s.printer { Some(match &s.printer {
@@ -681,7 +706,7 @@ pub fn view_window(
] ]
.spacing(8) .spacing(8)
.align_y(iced::Center); .align_y(iced::Center);
if !s.to_file { if !to_file {
output_row = output_row.push( output_row = output_row.push(
button(text(t!("Properties…")).size(11)) button(text(t!("Properties…")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::PrinterProperties)) .on_press(Message::PlotDlg(PlotDlgMsg::PrinterProperties))
@@ -695,7 +720,7 @@ pub fn view_window(
] ]
.spacing(4) .spacing(4)
.width(Length::Fill); .width(Length::Fill);
let copies_row: Element<'_, Message> = if s.to_file { let copies_row: Element<'_, Message> = if to_file {
Space::new().height(0).into() Space::new().height(0).into()
} else { } else {
field_row(t!("Copies"), &s.copies, PlotDlgMsg::Copies, 60) field_row(t!("Copies"), &s.copies, PlotDlgMsg::Copies, 60)