1use cgmath::{Deg, InnerSpace};
7use geo::{CoordsIter as _, Point, Rect, Translate};
8use microcad_core::*;
9use microcad_lang::{model::Model, render::RenderOutput};
10
11use crate::svg::{attributes::SvgTagAttribute, *};
12
13impl WriteSvg for Line {
14 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
15 let ((x1, y1), (x2, y2)) = (self.0.x_y(), self.1.x_y());
16 writer.tag(
17 &format!("line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\"",),
18 attr,
19 )
20 }
21}
22
23impl WriteSvgMapped for Line {}
24
25impl WriteSvg for Rect {
26 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
27 let x = self.min().x;
28 let y = self.min().y;
29 let width = self.width();
30 let height = self.height();
31
32 writer.tag(
33 &format!("rect x=\"{x}\" y=\"{y}\" width=\"{width}\" height=\"{height}\""),
34 attr,
35 )
36 }
37}
38
39impl WriteSvgMapped for Rect {}
40
41impl WriteSvg for Bounds2D {
42 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
43 if let Some(rect) = self.rect() {
44 rect.write_svg(writer, attr)
45 } else {
46 Ok(())
47 }
48 }
49}
50
51impl WriteSvgMapped for Bounds2D {}
52
53impl WriteSvg for Circle {
54 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
55 let r = self.radius;
56 let (cx, cy) = (self.offset.x, self.offset.y);
57 writer.tag(&format!("circle cx=\"{cx}\" cy=\"{cy}\" r=\"{r}\""), attr)
58 }
59}
60
61impl WriteSvgMapped for Circle {}
62
63impl WriteSvg for LineString {
64 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
65 let points = self.coords().fold(String::new(), |acc, p| {
66 acc + &format!("{x},{y} ", x = p.x, y = p.y)
67 });
68 writer.tag(&format!("polyline points=\"{points}\""), attr)
69 }
70}
71
72impl WriteSvgMapped for LineString {}
73
74impl WriteSvg for MultiLineString {
75 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
76 self.iter()
77 .try_for_each(|line_string| line_string.write_svg(writer, attr))
78 }
79}
80
81impl WriteSvgMapped for MultiLineString {}
82
83impl WriteSvg for Polygon {
84 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
85 fn line_string_path(l: &geo2d::LineString) -> String {
86 l.points()
87 .enumerate()
88 .fold(String::new(), |acc, (i, point)| {
89 let (x, y) = point.x_y();
90 let mut s = String::new();
91 s += if i == 0 { "M" } else { "L" };
92 s += &format!("{x},{y}");
93 if i == l.coords_count() - 1 {
94 s += " Z ";
95 }
96 acc + &s
97 })
98 }
99
100 let exterior = line_string_path(self.exterior());
101 let interior = self
102 .interiors()
103 .iter()
104 .map(line_string_path)
105 .fold(String::new(), |acc, s| acc + &s);
106
107 writer.tag(&format!("path d=\"{exterior} {interior}\""), attr)
108 }
109}
110
111impl WriteSvgMapped for Polygon {}
112
113impl WriteSvg for MultiPolygon {
114 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
115 self.iter()
116 .try_for_each(|polygon| polygon.write_svg(writer, attr))
117 }
118}
119
120impl WriteSvgMapped for MultiPolygon {}
121
122impl WriteSvg for Geometries2D {
123 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
124 self.iter().try_for_each(|geo| geo.write_svg(writer, attr))
125 }
126}
127
128impl WriteSvgMapped for Geometries2D {}
129
130impl WriteSvg for Geometry2D {
131 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
132 match self {
133 Geometry2D::LineString(line_string) => line_string.write_svg(writer, attr),
134 Geometry2D::MultiLineString(multi_line_string) => {
135 multi_line_string.write_svg(writer, attr)
136 }
137 Geometry2D::Polygon(polygon) => polygon.write_svg(writer, attr),
138 Geometry2D::MultiPolygon(multi_polygon) => multi_polygon.write_svg(writer, attr),
139 Geometry2D::Rect(rect) => rect.write_svg(writer, attr),
140 Geometry2D::Line(edge) => edge.write_svg(writer, attr),
141 Geometry2D::Collection(collection) => collection.write_svg(writer, attr),
142 }
143 }
144}
145
146impl WriteSvgMapped for Geometry2D {}
147
148impl WriteSvg for Model {
149 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
150 let node_attr = attr
151 .clone()
152 .apply_from_model(self)
153 .insert(SvgTagAttribute::class("entity"));
154
155 let self_ = self.borrow();
156 let output = self_.output();
157 match output {
158 RenderOutput::Geometry2D {
159 local_matrix,
160 geometry,
161 ..
162 } => {
163 let node_attr = match local_matrix {
164 Some(matrix) => node_attr
165 .clone()
166 .insert(SvgTagAttribute::Transform(*matrix)),
167 None => node_attr.clone(),
168 };
169
170 writer.begin_group(&node_attr)?;
171
172 match geometry {
173 Some(geometry) => {
174 geometry.write_svg_mapped(writer, attr)?;
175 }
176 None => {
177 self_
178 .children()
179 .try_for_each(|model| model.write_svg(writer, attr))?;
180 }
181 }
182
183 writer.end_group()
184 }
185 _ => Ok(()),
186 }
187 }
188}
189
190pub struct CenteredText {
192 pub text: String,
194 pub rect: Rect,
196 pub font_size: Scalar,
198}
199
200impl WriteSvg for CenteredText {
201 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
202 let (x, y) = self.rect.center().x_y();
203 writer.open_tag(
204 format!(r#"text x="{x}" y="{y}" dominant-baseline="middle" text-anchor="middle""#,)
205 .as_str(),
206 attr,
207 )?;
208 writer.with_indent(&self.text)?;
209 writer.close_tag("text")
210 }
211}
212
213impl WriteSvgMapped for CenteredText {}
214
215pub struct Grid {
217 pub bounds: Bounds2D,
219
220 pub cell_size: Size2,
222}
223
224impl Default for Grid {
225 fn default() -> Self {
226 Self {
227 bounds: Bounds2D::default(),
228 cell_size: Size2 {
229 width: 10.0,
230 height: 10.0,
231 },
232 }
233 }
234}
235
236impl WriteSvg for Grid {
237 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
238 let rect = self.bounds.rect().unwrap_or(writer.canvas().rect);
239 writer.begin_group(&attr.clone().insert(SvgTagAttribute::class("grid-stroke")))?;
240
241 rect.write_svg(writer, &SvgTagAttributes::default())?;
242
243 let mut left = rect.min().x;
244 let right = rect.max().x;
245 while left <= right {
246 Line(
247 geo::Point::new(left, rect.min().y),
248 geo::Point::new(left, rect.max().y),
249 )
250 .write_svg(writer, &SvgTagAttributes::default())?;
251 left += self.cell_size.width.map_to_canvas(writer.canvas());
252 }
253
254 let mut bottom = rect.min().y;
255 let top = rect.max().y;
256 while bottom <= top {
257 Line(
258 geo::Point::new(rect.min().x, bottom),
259 geo::Point::new(rect.max().x, bottom),
260 )
261 .write_svg(writer, &SvgTagAttributes::default())?;
262 bottom += self.cell_size.height.map_to_canvas(writer.canvas());
263 }
264
265 writer.end_group()?;
266
267 Ok(())
268 }
269}
270
271pub struct Background;
273
274impl WriteSvg for Background {
275 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
276 let x = 0;
277 let y = 0;
278 let width = writer.canvas().size.width;
279 let height = writer.canvas().size.height;
280
281 writer.tag(
282 &format!("rect class=\"background-fill\" x=\"{x}\" y=\"{y}\" width=\"{width}\" height=\"{height}\""),
283 attr,
284 )
285 }
286}
287
288pub struct EdgeLengthMeasure {
290 name: Option<String>,
292 length: Scalar,
294 edge: Line,
296 offset: Scalar,
298}
299
300impl EdgeLengthMeasure {
301 pub fn height(rect: &Rect, offset: Scalar, name: Option<&str>) -> Self {
303 let edge = Line(
304 geo::Point::new(rect.min().x, rect.min().y),
305 geo::Point::new(rect.min().x, rect.max().y),
306 );
307 Self {
308 name: name.map(|s| s.into()),
309 length: edge.vec().magnitude(),
310 edge,
311 offset: -offset,
312 }
313 }
314
315 pub fn width(rect: &Rect, offset: Scalar, name: Option<&str>) -> Self {
317 let edge = Line(
318 geo::Point::new(rect.min().x, rect.min().y),
319 geo::Point::new(rect.max().x, rect.min().y),
320 );
321
322 Self {
323 name: name.map(|s| s.into()),
324 length: edge.vec().magnitude(),
325 edge,
326 offset,
327 }
328 }
329}
330
331impl MapToCanvas for EdgeLengthMeasure {
332 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
333 Self {
334 name: self.name.clone(),
335 length: self.length,
336 edge: self.edge.map_to_canvas(canvas),
337 offset: self.offset.map_to_canvas(canvas),
338 }
339 }
340}
341
342impl WriteSvg for EdgeLengthMeasure {
343 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
344 let edge_length = self.edge.vec().magnitude();
345
346 use attributes::SvgTagAttribute::*;
347
348 writer.begin_group(&attr.clone().insert(Transform(self.edge.matrix())))?;
349
350 let center = self.offset / 2.0;
351 let bottom_left = Point::new(0.0, 0.0);
352 let bottom_right = Point::new(edge_length, 0.0);
353 let top_left = Point::new(0.0, center);
354 let top_right = Point::new(edge_length, center);
355
356 writer.begin_group(&attr.clone().insert(SvgTagAttribute::class("measure")))?;
357 Line(bottom_left, Point::new(0.0, center * 1.5)).write_svg(writer, attr)?;
358 Line(bottom_right, Point::new(edge_length, center * 1.5)).write_svg(writer, attr)?;
359 Line(top_left, top_right).shorter(1.5).write_svg(
360 writer,
361 &attr
362 .clone()
363 .insert(MarkerStart("arrow".into()))
364 .insert(MarkerEnd("arrow".into())),
365 )?;
366 writer.end_group()?;
367
368 CenteredText {
369 text: format!(
370 "{name}{length:.2}mm",
371 name = match &self.name {
372 Some(name) => format!("{name} = "),
373 None => String::new(),
374 },
375 length = self.length
376 ),
377 rect: Rect::new(bottom_left, top_right).translate(0.0, center),
378 font_size: 2.0,
379 }
380 .write_svg(writer, &SvgTagAttribute::class("measure-fill").into())?;
381
382 writer.end_group()
383 }
384}
385
386impl WriteSvgMapped for EdgeLengthMeasure {}
387
388pub struct RadiusMeasure {
390 pub circle: Circle,
392 pub radius: Scalar,
394 pub name: Option<String>,
396 pub angle: Deg<Scalar>,
398}
399
400impl RadiusMeasure {
401 pub fn new(circle: Circle, name: Option<String>, angle: Option<Deg<Scalar>>) -> Self {
403 Self {
404 radius: circle.radius,
405 circle,
406 name,
407 angle: angle.unwrap_or(Deg(-45.0)),
408 }
409 }
410}
411
412impl MapToCanvas for RadiusMeasure {
413 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
414 Self {
415 radius: self.radius,
416 circle: self.circle.map_to_canvas(canvas),
417 name: self.name.clone(),
418 angle: self.angle,
419 }
420 }
421}
422
423impl WriteSvg for RadiusMeasure {
424 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
425 writer.begin_group(attr)?;
426
427 let edge = Line::radius_edge(&self.circle, &self.angle.into());
428 edge.shorter(1.5).write_svg(
429 writer,
430 &attr
431 .clone()
432 .insert(SvgTagAttribute::MarkerEnd("arrow".into()))
433 .insert(SvgTagAttribute::class("measure")),
434 )?;
435 let center = edge.center();
436
437 CenteredText {
438 text: format!(
439 "{name}{radius:.2}mm",
440 name = match &self.name {
441 Some(name) => format!("{name} = "),
442 None => String::new(),
443 },
444 radius = self.radius,
445 ),
446 rect: Rect::new(center, center),
447 font_size: 2.0,
448 }
449 .write_svg(writer, &SvgTagAttribute::class("measure-fill").into())?;
450
451 writer.end_group()?;
452
453 Ok(())
454 }
455}
456
457impl WriteSvgMapped for RadiusMeasure {}
458
459pub struct SizeMeasure {
461 bounds: Bounds2D,
462 width: Option<EdgeLengthMeasure>,
464 height: Option<EdgeLengthMeasure>,
466}
467
468impl SizeMeasure {
469 pub fn bounds<T: CalcBounds2D>(bounds: &T) -> Self {
471 let bounds = bounds.calc_bounds_2d();
472
473 if let Some(rect) = bounds.rect() {
474 Self {
475 bounds: bounds.clone(),
476 width: Some(EdgeLengthMeasure::width(&rect, 7.0, None)),
477 height: Some(EdgeLengthMeasure::height(&rect, 7.0, None)),
478 }
479 } else {
480 Self {
481 bounds: bounds.clone(),
482 width: None,
483 height: None,
484 }
485 }
486 }
487}
488
489impl MapToCanvas for SizeMeasure {
490 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
491 Self {
492 bounds: self.bounds.map_to_canvas(canvas),
493 width: self.width.as_ref().map(|width| width.map_to_canvas(canvas)),
494 height: self
495 .height
496 .as_ref()
497 .map(|height| height.map_to_canvas(canvas)),
498 }
499 }
500}
501
502impl WriteSvg for SizeMeasure {
503 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
504 if let Some(width) = &self.width {
505 width.write_svg(writer, attr)?;
506 }
507 if let Some(height) = &self.height {
508 height.write_svg(writer, attr)?;
509 }
510 Ok(())
511 }
512}
513
514impl WriteSvgMapped for SizeMeasure {}