forked from 1b-agent/opencadstudio-upstream
Merge pull request #1701 from hvfrancesco/main
added SVG export feature # Conflicts: # src/io/mod.rs # src/io/pdf_export.rs
This commit is contained in:
commit
fb474eb77c
10 files changed
+1274
-124
No files matched your search
@@ -970,6 +970,13 @@ impl OpenCADStudio {
|
||||
"EXPORT" | "EXPORTPDF" => {
|
||||
return Some(Task::done(Message::PlotExport));
|
||||
}
|
||||
// EXPORTSVG — the same direct export, but the save dialog
|
||||
// proposes the SVG device's defaults (`stem.svg`).
|
||||
"EXPORTSVG" => {
|
||||
self.plot_dialog.to_file = true;
|
||||
self.plot_dialog.file_svg = true;
|
||||
return Some(Task::done(Message::PlotExport));
|
||||
}
|
||||
// PLOTSTYLE — load or clear CTB/STB plot style table
|
||||
cmd if cmd == "PLOTSTYLE" || cmd.starts_with("PLOTSTYLE ") => {
|
||||
let sub = cmd
|
||||
|
||||
@@ -486,6 +486,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
// Standard aliases for existing commands.
|
||||
"BMAKE",
|
||||
"EXPORTPDF",
|
||||
"EXPORTSVG",
|
||||
"PRINTALL",
|
||||
"DDIM",
|
||||
// Inquiry: list the whole drawing database.
|
||||
|
||||
+43
-17
@@ -510,8 +510,11 @@ impl OpenCADStudio {
|
||||
use crate::io::pdf_export::PdfPageInput;
|
||||
|
||||
let path = string(req, "path")?;
|
||||
if !path.to_ascii_lowercase().ends_with(".pdf") {
|
||||
return Err(failure("invalid_path", "plot writes .pdf files"));
|
||||
let lower = path.to_ascii_lowercase();
|
||||
// SVG shares the plot page with PDF: the writer follows the extension.
|
||||
let is_svg = lower.ends_with(".svg");
|
||||
if !is_svg && !lower.ends_with(".pdf") {
|
||||
return Err(failure("invalid_path", "plot writes .pdf or .svg files"));
|
||||
}
|
||||
self.stamp_plot_fields();
|
||||
let i = self.active_tab;
|
||||
@@ -740,9 +743,16 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.camera_generation = original_camera_generation;
|
||||
let pages = built.map_err(|e| failure("plot_failed", e))?;
|
||||
if req["per_page"].as_bool().unwrap_or(false) {
|
||||
// One PDF per layout: <stem>-<Layout>.pdf beside the requested
|
||||
// One file per layout: <stem>-<Layout>.pdf/.svg beside the requested
|
||||
// path (sheet-by-sheet delivery).
|
||||
let stem = path.strip_suffix(".pdf").unwrap_or(path).to_owned();
|
||||
let stem = path
|
||||
.strip_suffix(".pdf")
|
||||
.or_else(|| path.strip_suffix(".PDF"))
|
||||
.or_else(|| path.strip_suffix(".svg"))
|
||||
.or_else(|| path.strip_suffix(".SVG"))
|
||||
.unwrap_or(path)
|
||||
.to_owned();
|
||||
let ext = if is_svg { "svg" } else { "pdf" };
|
||||
let mut files = Vec::with_capacity(pages.len());
|
||||
for (name, page) in targets.iter().zip(&pages) {
|
||||
let safe: String = name
|
||||
@@ -755,13 +765,21 @@ impl OpenCADStudio {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let file = format!("{stem}-{safe}.pdf");
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
std::slice::from_ref(page),
|
||||
std::path::Path::new(&file),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
.map_err(|e| failure("plot_failed", e))?;
|
||||
let file = format!("{stem}-{safe}.{ext}");
|
||||
let result = if is_svg {
|
||||
crate::io::svg_export::export_svg_pages(
|
||||
std::slice::from_ref(page),
|
||||
std::path::Path::new(&file),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
} else {
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
std::slice::from_ref(page),
|
||||
std::path::Path::new(&file),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
};
|
||||
result.map_err(|e| failure("plot_failed", e))?;
|
||||
files.push(json!({"layout": name, "path": file}));
|
||||
}
|
||||
self.set_control_result(json!({ "files": files, "pages": pages.len() }));
|
||||
@@ -770,12 +788,20 @@ impl OpenCADStudio {
|
||||
// Pages may drop the style when a stored page setup overwrites the
|
||||
// dialog's style fields, so the explicit request style rides along as
|
||||
// the export-level fallback.
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
&pages,
|
||||
std::path::Path::new(path),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
.map_err(|e| failure("plot_failed", e))?;
|
||||
let result = if is_svg {
|
||||
crate::io::svg_export::export_svg_pages(
|
||||
&pages,
|
||||
std::path::Path::new(path),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
} else {
|
||||
crate::io::pdf_export::export_pdf_pages(
|
||||
&pages,
|
||||
std::path::Path::new(path),
|
||||
loaded_style.as_ref(),
|
||||
)
|
||||
};
|
||||
result.map_err(|e| failure("plot_failed", e))?;
|
||||
self.set_control_result(json!({
|
||||
"path": path,
|
||||
"pages": pages.len(),
|
||||
|
||||
+51
-3
@@ -3877,7 +3877,17 @@ impl OpenCADStudio {
|
||||
};
|
||||
let worker_path = path.clone();
|
||||
let work = move || {
|
||||
crate::io::pdf_export::export_pdf(&page, &worker_path)
|
||||
// SVG shares the plot page with PDF: pick the writer from the
|
||||
// chosen extension.
|
||||
let result = if worker_path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
|
||||
{
|
||||
crate::io::svg_export::export_svg(&page, &worker_path)
|
||||
} else {
|
||||
crate::io::pdf_export::export_pdf(&page, &worker_path)
|
||||
};
|
||||
result
|
||||
.map(|_| crate::tf!("Exported: {}", worker_path.display()).into_owned())
|
||||
.map_err(|e| crate::tf!("Export failed: {e}").into_owned())
|
||||
};
|
||||
@@ -3903,7 +3913,15 @@ impl OpenCADStudio {
|
||||
let worker_path = path.clone();
|
||||
self.close_active_modal();
|
||||
let work = move || {
|
||||
crate::io::pdf_export::export_pdf(&page, &worker_path)
|
||||
let result = if worker_path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
|
||||
{
|
||||
crate::io::svg_export::export_svg(&page, &worker_path)
|
||||
} else {
|
||||
crate::io::pdf_export::export_pdf(&page, &worker_path)
|
||||
};
|
||||
result
|
||||
.map(|_| {
|
||||
crate::tf!(
|
||||
"Plotted window to {}",
|
||||
@@ -4894,7 +4912,7 @@ impl OpenCADStudio {
|
||||
msg: crate::ui::window::plot::PlotDlgMsg,
|
||||
) -> Task<Message> {
|
||||
use crate::ui::window::plot::{
|
||||
PlotDlgMsg as M, PlotFlag, OUT_DEFAULT, OUT_PDF, STYLE_NONE,
|
||||
PlotDlgMsg as M, PlotFlag, OUT_DEFAULT, OUT_PDF, OUT_SVG, STYLE_NONE,
|
||||
};
|
||||
match msg {
|
||||
M::Close => {
|
||||
@@ -4910,11 +4928,17 @@ impl OpenCADStudio {
|
||||
self.plot_dialog.printer_editor = None;
|
||||
if s == OUT_PDF {
|
||||
self.plot_dialog.to_file = true;
|
||||
self.plot_dialog.file_svg = false;
|
||||
} else if s == OUT_SVG {
|
||||
self.plot_dialog.to_file = true;
|
||||
self.plot_dialog.file_svg = true;
|
||||
} else if s == OUT_DEFAULT {
|
||||
self.plot_dialog.to_file = false;
|
||||
self.plot_dialog.file_svg = false;
|
||||
self.plot_dialog.printer = None;
|
||||
} else {
|
||||
self.plot_dialog.to_file = false;
|
||||
self.plot_dialog.file_svg = false;
|
||||
self.plot_dialog.printer = Some(s);
|
||||
}
|
||||
self.save_config();
|
||||
@@ -5405,6 +5429,7 @@ impl OpenCADStudio {
|
||||
let is_model = self.tabs[self.active_tab].scene.current_layout == "Model";
|
||||
let d = &mut self.plot_dialog;
|
||||
d.to_file = true;
|
||||
d.file_svg = false;
|
||||
let a4 = crate::io::paper_catalog::default_paper();
|
||||
d.paper = a4.canonical.to_string();
|
||||
d.orientation = "Landscape".into();
|
||||
@@ -5627,14 +5652,17 @@ impl OpenCADStudio {
|
||||
match crate::io::plot_device::PlotDevice::from_stored_name(&ps.printer_name).0 {
|
||||
crate::io::plot_device::PlotDevice::Pdf => {
|
||||
d.to_file = true;
|
||||
d.file_svg = false;
|
||||
d.printer = None;
|
||||
}
|
||||
crate::io::plot_device::PlotDevice::Printer(name) => {
|
||||
d.to_file = false;
|
||||
d.file_svg = false;
|
||||
d.printer = Some(name);
|
||||
}
|
||||
crate::io::plot_device::PlotDevice::None => {
|
||||
d.to_file = false;
|
||||
d.file_svg = false;
|
||||
d.printer = None;
|
||||
}
|
||||
}
|
||||
@@ -6862,6 +6890,26 @@ mod plot_paper_tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_device_is_session_state_and_stores_as_pdf() {
|
||||
let mut app = app_with_printer_named_sheet();
|
||||
let _ = app.on_plot_dialog_open();
|
||||
// The SVG device behaves like file output…
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Printer(crate::ui::window::plot::OUT_SVG.into()));
|
||||
assert!(app.plot_dialog.to_file);
|
||||
assert!(app.plot_dialog.file_svg);
|
||||
// …but switching back to PDF (or a printer) clears it.
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Printer(crate::ui::window::plot::OUT_PDF.into()));
|
||||
assert!(app.plot_dialog.to_file);
|
||||
assert!(!app.plot_dialog.file_svg);
|
||||
// The drawing never learns about SVG: page setups only know printers
|
||||
// and PDF drivers, so an SVG plot stores the PDF driver spelling.
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::Printer(crate::ui::window::plot::OUT_SVG.into()));
|
||||
let _ = app.on_plot_dlg(PlotDlgMsg::SetCurrent);
|
||||
let ps = app.tabs[app.active_tab].scene.plot_settings_for("Layout1").unwrap();
|
||||
assert_eq!(ps.printer_name, "DWG To PDF.pc3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_pdf_label_is_upgraded_on_save() {
|
||||
let mut app = app_with_printer_named_sheet();
|
||||
|
||||
+12
-8
@@ -9899,14 +9899,14 @@ impl OpenCADStudio {
|
||||
return Task::done(Message::PrintAllPdfPath(None));
|
||||
};
|
||||
iced::window::run(window_id, move |parent| {
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
|
||||
crate::io::pdf_export::pick_plot_path_owned(stem, parent, false)
|
||||
})
|
||||
.map(Message::PrintAllPdfPath)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Task::perform(
|
||||
crate::io::pdf_export::pick_pdf_path_async(stem),
|
||||
crate::io::pdf_export::pick_plot_path_async(stem, false),
|
||||
Message::PrintAllPdfPath,
|
||||
)
|
||||
}
|
||||
@@ -9945,27 +9945,30 @@ impl OpenCADStudio {
|
||||
.and_then(|p: &std::path::Path| p.file_stem())
|
||||
.map(|s: &std::ffi::OsStr| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "drawing".into());
|
||||
// The save dialog follows the dialog's file device, so the
|
||||
// SVG choice proposes `stem.svg` up front.
|
||||
let svg = self.plot_dialog.file_svg;
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
|
||||
{
|
||||
let Some(window_id) = self.main_window else {
|
||||
return Task::done(Message::PlotExportPath(None));
|
||||
};
|
||||
iced::window::run(window_id, move |parent| {
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
|
||||
crate::io::pdf_export::pick_plot_path_owned(stem, parent, svg)
|
||||
})
|
||||
.map(Message::PlotExportPath)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Task::perform(
|
||||
crate::io::pdf_export::pick_pdf_path_async(stem),
|
||||
crate::io::pdf_export::pick_plot_path_async(stem, svg),
|
||||
Message::PlotExportPath,
|
||||
)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Task::perform(
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem),
|
||||
crate::io::pdf_export::pick_plot_path_owned(stem),
|
||||
Message::PlotExportPath,
|
||||
)
|
||||
}
|
||||
@@ -9981,27 +9984,28 @@ impl OpenCADStudio {
|
||||
.and_then(|p: &std::path::Path| p.file_stem())
|
||||
.map(|s: &std::ffi::OsStr| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "drawing".into());
|
||||
let svg = self.plot_dialog.file_svg;
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
|
||||
{
|
||||
let Some(window_id) = self.main_window else {
|
||||
return Task::done(Message::PlotWindowExportPath(None));
|
||||
};
|
||||
iced::window::run(window_id, move |parent| {
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
|
||||
crate::io::pdf_export::pick_plot_path_owned(stem, parent, svg)
|
||||
})
|
||||
.map(Message::PlotWindowExportPath)
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Task::perform(
|
||||
crate::io::pdf_export::pick_pdf_path_async(stem),
|
||||
crate::io::pdf_export::pick_plot_path_async(stem, svg),
|
||||
Message::PlotWindowExportPath,
|
||||
)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Task::perform(
|
||||
crate::io::pdf_export::pick_pdf_path_owned(stem),
|
||||
crate::io::pdf_export::pick_plot_path_owned(stem),
|
||||
Message::PlotWindowExportPath,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user