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:
Hakan Seven committed 2026-10-12 00:17:35 +03:00
commit fb474eb77c
10 files changed
+1274 -124

No files matched your search

+2
View File
@@ -14,6 +14,7 @@ pub mod ole_embed;
pub mod paper_catalog;
pub mod patterns;
pub mod pdf_export;
pub mod plot;
pub mod plot_device;
pub mod plot_style;
pub mod print_to_printer;
@@ -22,6 +23,7 @@ pub mod recovery;
pub mod single_instance;
pub mod step;
pub mod stl;
pub mod svg_export;
pub mod thumbnail;
pub mod update_check;
pub mod visual_styles;
+44 -93
View File
@@ -12,7 +12,6 @@ use crate::io::plot_style::PlotStyleTable;
use crate::scene::model::hatch_model::HatchModel;
#[cfg(not(target_arch = "wasm32"))]
use crate::scene::model::hatch_model::HatchPattern;
use crate::scene::model::image_model::ImageModel;
#[cfg(not(target_arch = "wasm32"))]
use crate::scene::model::wire_model::SearchableTextRun;
use crate::scene::WireModel;
@@ -25,27 +24,13 @@ use printpdf::{
};
use std::path::Path;
#[derive(Clone, Debug)]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub struct PlotWire {
pub wire: WireModel,
pub draw_depth: f32,
}
impl std::ops::Deref for PlotWire {
type Target = WireModel;
fn deref(&self) -> &Self::Target {
&self.wire
}
}
/// Decoded image geometry plus inherited block/viewport clip boundaries.
#[derive(Clone, Debug)]
pub struct PlotImage {
pub image: ImageModel,
pub clips: Vec<Vec<[f64; 2]>>,
}
// Shared plot model: `PlotWire`, `PlotImage`, `PlotContent`, `PlotGroupSplits`
// and friends live in `super::plot`. The `Pdf*` names remain as aliases so
// the scene, the print pipeline and every other call site keep compiling.
pub use super::plot::{
PlotContent, PlotGroupSplits, PlotImage, PlotOptions as PdfPlotOptions,
PlotPage as PdfPageInput, PlotWire,
};
// The web build has no `printpdf` (it pulls a wasm-incompatible `memchr` via
// lopdf → nom_locate) and no filesystem, so PDF export is native-only; the web
@@ -65,7 +50,7 @@ pub fn export_pdf_pages(
}
#[cfg(target_arch = "wasm32")]
pub async fn pick_pdf_path_owned(_stem: String) -> Option<std::path::PathBuf> {
pub async fn pick_plot_path_owned(_stem: String) -> Option<std::path::PathBuf> {
None
}
@@ -82,63 +67,6 @@ const LW_PX_TO_PT: f32 = MM_TO_PT / (96.0 / 25.4);
#[cfg(not(target_arch = "wasm32"))]
const SCREEN_DOT_MM: f32 = 25.4 / 96.0;
/// Output controls shared by preview, PDF export, and printer rendering.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PdfPlotOptions {
pub object_lineweights: bool,
pub scale_lineweights: bool,
pub transparency: bool,
pub stamp: bool,
pub merge_lines: bool,
}
/// End indexes of the first paper/model render group in each flat input list.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PlotGroupSplits {
pub wires: usize,
pub hatches: usize,
pub wipeouts: usize,
pub images: usize,
}
#[derive(Default)]
pub struct PlotContent {
pub wires: std::sync::Arc<Vec<PlotWire>>,
pub hatches: Vec<HatchModel>,
pub wipeouts: Vec<HatchModel>,
pub images: Vec<PlotImage>,
pub group_splits: PlotGroupSplits,
}
/// Owned geometry and settings for one PDF page.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub struct PdfPageInput {
pub content: PlotContent,
/// Page dimensions in mm, after any 90/270-degree rotation.
pub paper_w: f64,
pub paper_h: f64,
/// Absolute-world offsets stay f64 to preserve local detail at UTM coordinates.
pub offset_x: f64,
pub offset_y: f64,
pub rotation_deg: i32,
pub scale: f32,
pub clip: Option<(f32, f32, f32, f32)>,
pub options: PdfPlotOptions,
pub plot_style: Option<PlotStyleTable>,
}
impl Default for PdfPlotOptions {
fn default() -> Self {
Self {
object_lineweights: true,
scale_lineweights: false,
transparency: false,
stamp: false,
merge_lines: false,
}
}
}
// ── Public entry point ────────────────────────────────────────────────────
/// Export one page to a PDF file.
@@ -381,28 +309,40 @@ fn image_triangle_matrix(p: [[f32; 2]; 3], uv: [[f32; 2]; 3]) -> Option<[f32; 6]
])
}
/// Show a parented PDF save-file dialog and return the chosen path.
/// Show a parented plot save-file dialog and return the chosen path.
///
/// The parent comes from `iced::window::run`, keeping the portal request tied
/// to the visible app window on Wayland instead of silently resolving to
/// `None` on desktops that reject a parentless save dialog (#537).
/// `svg` selects the SVG device's defaults (name, title, first filter).
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
pub fn pick_pdf_path_owned(
pub fn pick_plot_path_owned(
stem: String,
parent: &dyn iced::window::Window,
svg: bool,
) -> Option<std::path::PathBuf> {
let path = crate::sys::blocking_file_dialog()
.set_parent(parent)
.set_title(crate::t!("Export as PDF").as_ref())
.set_file_name(&format!("{stem}.pdf"))
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
let dialog = crate::sys::blocking_file_dialog().set_parent(parent);
let dialog = if svg {
dialog
.set_title(crate::t!("Export as SVG").as_ref())
.set_file_name(&format!("{stem}.svg"))
.add_filter(crate::t!("SVG Files").as_ref(), &["svg"])
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
} else {
dialog
.set_title(crate::t!("Export as PDF").as_ref())
.set_file_name(&format!("{stem}.pdf"))
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
.add_filter(crate::t!("SVG Files").as_ref(), &["svg"])
};
let path = dialog
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()?;
crate::config::remember_dialog_dir(&path);
Some(path)
}
/// Windows: pick the PDF destination with the async dialog on a worker
/// Windows: pick the plot destination with the async dialog on a worker
/// thread. The parented blocking dialog ran `IFileDialog::Show` on the UI
/// thread inside the window callback, and when the target name already
/// existed the overwrite-confirmation popup is a second nested modal that
@@ -410,11 +350,22 @@ pub fn pick_pdf_path_owned(
/// backend runs the dialog off-thread; it is the same pattern the DWG
/// Save As flow uses, whose confirm popup works.
#[cfg(all(not(target_arch = "wasm32"), target_os = "windows"))]
pub async fn pick_pdf_path_async(stem: String) -> Option<std::path::PathBuf> {
let handle = crate::sys::file_dialog()
.set_title(crate::t!("Export as PDF").as_ref())
.set_file_name(format!("{stem}.pdf"))
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
pub async fn pick_plot_path_async(stem: String, svg: bool) -> Option<std::path::PathBuf> {
let dialog = crate::sys::file_dialog();
let dialog = if svg {
dialog
.set_title(crate::t!("Export as SVG").as_ref())
.set_file_name(format!("{stem}.svg"))
.add_filter(crate::t!("SVG Files").as_ref(), &["svg"])
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
} else {
dialog
.set_title(crate::t!("Export as PDF").as_ref())
.set_file_name(format!("{stem}.pdf"))
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
.add_filter(crate::t!("SVG Files").as_ref(), &["svg"])
};
let handle = dialog
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()
.await?;
+91
View File
@@ -0,0 +1,91 @@
// Shared plot model — the paper-space geometry every plot backend consumes.
//
// `PdfPageInput`/`PdfPlotOptions` used to live in `pdf_export`, which made the
// scene, the print pipeline and now the SVG exporter all import from the PDF
// backend. The structs themselves are backend-agnostic (wires, hatches,
// images, page size/offset/clip), so they live here; `pdf_export` re-exports
// the historical `Pdf*` names as aliases so existing call sites keep working.
use crate::io::plot_style::PlotStyleTable;
use crate::scene::WireModel;
use crate::scene::model::hatch_model::HatchModel;
use crate::scene::model::image_model::ImageModel;
#[derive(Clone, Debug)]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub struct PlotWire {
pub wire: WireModel,
pub draw_depth: f32,
}
impl std::ops::Deref for PlotWire {
type Target = WireModel;
fn deref(&self) -> &Self::Target {
&self.wire
}
}
/// Decoded image geometry plus inherited block/viewport clip boundaries.
#[derive(Clone, Debug)]
pub struct PlotImage {
pub image: ImageModel,
pub clips: Vec<Vec<[f64; 2]>>,
}
/// Output controls shared by preview, plot exports, and printer rendering.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlotOptions {
pub object_lineweights: bool,
pub scale_lineweights: bool,
pub transparency: bool,
pub stamp: bool,
pub merge_lines: bool,
}
/// End indexes of the first paper/model render group in each flat input list.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct PlotGroupSplits {
pub wires: usize,
pub hatches: usize,
pub wipeouts: usize,
pub images: usize,
}
#[derive(Default)]
pub struct PlotContent {
pub wires: std::sync::Arc<Vec<PlotWire>>,
pub hatches: Vec<HatchModel>,
pub wipeouts: Vec<HatchModel>,
pub images: Vec<PlotImage>,
pub group_splits: PlotGroupSplits,
}
/// Owned geometry and settings for one plot page.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub struct PlotPage {
pub content: PlotContent,
/// Page dimensions in mm, after any 90/270-degree rotation.
pub paper_w: f64,
pub paper_h: f64,
/// Absolute-world offsets stay f64 to preserve local detail at UTM coordinates.
pub offset_x: f64,
pub offset_y: f64,
pub rotation_deg: i32,
pub scale: f32,
pub clip: Option<(f32, f32, f32, f32)>,
pub options: PlotOptions,
pub plot_style: Option<PlotStyleTable>,
}
impl Default for PlotOptions {
fn default() -> Self {
Self {
object_lineweights: true,
scale_lineweights: false,
transparency: false,
stamp: false,
merge_lines: false,
}
}
}
+1007
View File
File diff suppressed because it is too large. Load diff