1use microcad_core::{Color, Scalar};
7use microcad_lang::{Id, builtin::*, model::*, parameter, render::RenderError, value::*};
8
9pub struct SvgExporter;
11
12#[derive(Clone, Debug, PartialEq)]
14pub struct Theme {
15 pub background: Color,
17 pub grid: Color,
19 pub selection: Color,
21 pub highlight: Color,
23 pub entity: Color,
25 pub outline: Color,
27 pub active: Color,
29 pub inactive: Color,
31 pub measure: Color,
33 pub snap_indicator: Color,
35 pub guide: Color,
37}
38
39impl Default for Theme {
40 fn default() -> Self {
41 Self {
42 background: Color::rgb(1.0, 1.0, 1.0),
43 grid: Color::rgb(0.85, 0.85, 0.85),
44 selection: Color::rgb(0.0, 0.4, 0.8),
45 highlight: Color::rgb(1.0, 0.6, 0.0),
46 entity: Color::rgba(0.7, 0.7, 0.7, 0.7),
47 outline: Color::rgb(0.1, 0.1, 0.1),
48 active: Color::rgb(0.2, 0.2, 0.2),
49 inactive: Color::rgb(0.8, 0.8, 0.8),
50 measure: Color::rgb(0.0, 0.8, 0.8),
51 snap_indicator: Color::rgb(0.0, 0.8, 0.8),
52 guide: Color::rgb(0.6, 0.6, 0.6),
53 }
54 }
55}
56
57pub struct SvgExporterSettings {
59 padding_factor: Scalar,
61}
62
63impl Default for SvgExporterSettings {
64 fn default() -> Self {
65 Self {
66 padding_factor: 0.05, }
68 }
69}
70
71impl SvgExporter {
72 pub fn theme_to_svg_style(theme: &Theme) -> String {
74 fn fill_stroke_style(
75 class_name: &str,
76 fill_color: Color,
77 stroke_color: Color,
78 stroke_width: Scalar,
79 ) -> String {
80 format!(
81 r#"
82 .{class_name} {{
83 fill: {fill_color};
84 stroke: {stroke_color};
85 stroke-width: {stroke_width};
86 }}
87 "#,
88 fill_color = fill_color.to_svg_color(),
89 stroke_color = stroke_color.to_svg_color()
90 )
91 }
92
93 fn fill_style(class_name: &str, fill: Color) -> String {
94 format!(
95 r#"
96 .{class_name}-fill {{
97 fill: {fill};
98 stroke: none;
99 }}
100 "#,
101 fill = fill.to_svg_color()
102 )
103 }
104
105 fn stroke_style(class_name: &str, stroke: Color, stroke_width: Scalar) -> String {
106 format!(
107 r#"
108 .{class_name}-stroke {{
109 fill: none;
110 stroke: {stroke};
111 stroke-width: {stroke_width};
112 }}
113 "#,
114 stroke = stroke.to_svg_color()
115 )
116 }
117
118 let mut style = [
119 ("background", theme.background, None),
120 ("grid", theme.grid, Some(0.2)),
121 ("measure", theme.measure, Some(0.2)),
122 ("highlight", theme.highlight, Some(0.2)),
123 ]
124 .into_iter()
125 .fold(String::new(), |mut style, item| {
126 if let Some(stroke) = item.2 {
127 style += &fill_stroke_style(item.0, item.1, item.1, stroke);
128 style += &stroke_style(item.0, item.1, stroke)
129 }
130 style += &fill_style(item.0, item.1);
131 style
132 });
133
134 style += &fill_stroke_style("entity", theme.entity, theme.outline, 0.4);
135
136 style += r#"
137 .active { fill-opacity: 1.0; stroke-opacity: 1.0; }
138 .inactive { fill-opacity: 0.3; stroke-opacity: 0.3; }
139 "#;
140
141 style
142 }
143}
144
145impl Exporter for SvgExporter {
146 fn model_parameters(&self) -> microcad_lang::value::ParameterValueList {
147 [
148 parameter!(style: String = String::new()),
149 parameter!(fill: String = String::new()),
150 ]
151 .into_iter()
152 .collect()
153 }
154
155 fn export(&self, model: &Model, filename: &std::path::Path) -> Result<Value, ExportError> {
156 use crate::svg::*;
157 use microcad_core::CalcBounds2D;
158 let settings = SvgExporterSettings::default();
159 let bounds = model.calc_bounds_2d();
160
161 if bounds.is_valid() {
162 let content_rect = bounds
163 .enlarge(2.0 * settings.padding_factor)
164 .rect()
165 .expect("Rect");
166 log::debug!("Exporting into SVG file {filename:?}");
167 let f = std::fs::File::create(filename)?;
168 let mut writer = SvgWriter::new_canvas(
169 Box::new(std::io::BufWriter::new(f)),
170 model.get_size(),
171 content_rect,
172 None,
173 )?;
174 writer.style(&SvgExporter::theme_to_svg_style(&Theme::default()))?;
175
176 model.write_svg(&mut writer, &SvgTagAttributes::default())?;
177 Ok(Value::None)
178 } else {
179 Err(ExportError::RenderError(RenderError::NothingToRender))
180 }
181 }
182
183 fn output_type(&self) -> OutputType {
184 OutputType::Geometry2D
185 }
186}
187
188impl FileIoInterface for SvgExporter {
189 fn id(&self) -> Id {
190 Id::new("svg")
191 }
192}