1use cgmath::{Deg, InnerSpace};
7use geo::{CoordsIter as _, Point, Rect, Translate};
8use microcad_core::*;
9use microcad_lang::{model::Model, render::GeometryOutput};
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 let geometry = &output.geometry;
158 let node_attr = match output.local_matrix {
159 Some(matrix) => node_attr
160 .clone()
161 .insert(SvgTagAttribute::Transform(mat4_to_mat3(&matrix))),
162 None => node_attr.clone(),
163 };
164
165 match geometry {
166 Some(GeometryOutput::Geometry2D(geometry)) => {
167 writer.begin_group(&node_attr)?;
168 geometry.write_svg_mapped(writer, attr)?;
169 writer.end_group()
170 }
171 None => self_
172 .children()
173 .try_for_each(|model| model.write_svg(writer, attr)),
174 _ => Ok(()),
175 }
176 }
177}
178
179pub struct CenteredText {
181 pub text: String,
183 pub rect: Rect,
185 pub font_size: Scalar,
187}
188
189impl WriteSvg for CenteredText {
190 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
191 let (x, y) = self.rect.center().x_y();
192 writer.open_tag(
193 format!(r#"text x="{x}" y="{y}" dominant-baseline="middle" text-anchor="middle""#,)
194 .as_str(),
195 attr,
196 )?;
197 writer.with_indent(&self.text)?;
198 writer.close_tag("text")
199 }
200}
201
202impl WriteSvgMapped for CenteredText {}
203
204pub struct Grid {
206 pub bounds: Bounds2D,
208
209 pub cell_size: Size2,
211}
212
213impl Default for Grid {
214 fn default() -> Self {
215 Self {
216 bounds: Bounds2D::default(),
217 cell_size: Size2 {
218 width: 10.0,
219 height: 10.0,
220 },
221 }
222 }
223}
224
225impl WriteSvg for Grid {
226 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
227 let rect = self.bounds.rect().unwrap_or(writer.canvas().rect);
228 writer.begin_group(&attr.clone().insert(SvgTagAttribute::class("grid-stroke")))?;
229
230 rect.write_svg(writer, &SvgTagAttributes::default())?;
231
232 let mut left = rect.min().x;
233 let right = rect.max().x;
234 while left <= right {
235 Line(
236 geo::Point::new(left, rect.min().y),
237 geo::Point::new(left, rect.max().y),
238 )
239 .write_svg(writer, &SvgTagAttributes::default())?;
240 left += self.cell_size.width.map_to_canvas(writer.canvas());
241 }
242
243 let mut bottom = rect.min().y;
244 let top = rect.max().y;
245 while bottom <= top {
246 Line(
247 geo::Point::new(rect.min().x, bottom),
248 geo::Point::new(rect.max().x, bottom),
249 )
250 .write_svg(writer, &SvgTagAttributes::default())?;
251 bottom += self.cell_size.height.map_to_canvas(writer.canvas());
252 }
253
254 writer.end_group()?;
255
256 Ok(())
257 }
258}
259
260pub struct Background;
262
263impl WriteSvg for Background {
264 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
265 let x = 0;
266 let y = 0;
267 let width = writer.canvas().size.width;
268 let height = writer.canvas().size.height;
269
270 writer.tag(
271 &format!("rect class=\"background-fill\" x=\"{x}\" y=\"{y}\" width=\"{width}\" height=\"{height}\""),
272 attr,
273 )
274 }
275}
276
277pub struct EdgeLengthMeasure {
279 name: Option<String>,
281 length: Scalar,
283 edge: Line,
285 offset: Scalar,
287}
288
289impl EdgeLengthMeasure {
290 pub fn height(rect: &Rect, offset: Scalar, name: Option<&str>) -> Self {
292 let edge = Line(
293 geo::Point::new(rect.min().x, rect.min().y),
294 geo::Point::new(rect.min().x, rect.max().y),
295 );
296 Self {
297 name: name.map(|s| s.into()),
298 length: edge.vec().magnitude(),
299 edge,
300 offset: -offset,
301 }
302 }
303
304 pub fn width(rect: &Rect, offset: Scalar, name: Option<&str>) -> Self {
306 let edge = Line(
307 geo::Point::new(rect.min().x, rect.min().y),
308 geo::Point::new(rect.max().x, rect.min().y),
309 );
310
311 Self {
312 name: name.map(|s| s.into()),
313 length: edge.vec().magnitude(),
314 edge,
315 offset,
316 }
317 }
318}
319
320impl MapToCanvas for EdgeLengthMeasure {
321 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
322 Self {
323 name: self.name.clone(),
324 length: self.length,
325 edge: self.edge.map_to_canvas(canvas),
326 offset: self.offset.map_to_canvas(canvas),
327 }
328 }
329}
330
331impl WriteSvg for EdgeLengthMeasure {
332 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
333 let edge_length = self.edge.vec().magnitude();
334
335 use attributes::SvgTagAttribute::*;
336
337 writer.begin_group(&attr.clone().insert(Transform(self.edge.matrix())))?;
338
339 let center = self.offset / 2.0;
340 let bottom_left = Point::new(0.0, 0.0);
341 let bottom_right = Point::new(edge_length, 0.0);
342 let top_left = Point::new(0.0, center);
343 let top_right = Point::new(edge_length, center);
344
345 writer.begin_group(&attr.clone().insert(SvgTagAttribute::class("measure")))?;
346 Line(bottom_left, Point::new(0.0, center * 1.5)).write_svg(writer, attr)?;
347 Line(bottom_right, Point::new(edge_length, center * 1.5)).write_svg(writer, attr)?;
348 Line(top_left, top_right).shorter(1.5).write_svg(
349 writer,
350 &attr
351 .clone()
352 .insert(MarkerStart("arrow".into()))
353 .insert(MarkerEnd("arrow".into())),
354 )?;
355 writer.end_group()?;
356
357 CenteredText {
358 text: format!(
359 "{name}{length:.2}mm",
360 name = match &self.name {
361 Some(name) => format!("{name} = "),
362 None => String::new(),
363 },
364 length = self.length
365 ),
366 rect: Rect::new(bottom_left, top_right).translate(0.0, center),
367 font_size: 2.0,
368 }
369 .write_svg(writer, &SvgTagAttribute::class("measure-fill").into())?;
370
371 writer.end_group()
372 }
373}
374
375impl WriteSvgMapped for EdgeLengthMeasure {}
376
377pub struct RadiusMeasure {
379 pub circle: Circle,
381 pub radius: Scalar,
383 pub name: Option<String>,
385 pub angle: Deg<Scalar>,
387}
388
389impl RadiusMeasure {
390 pub fn new(circle: Circle, name: Option<String>, angle: Option<Deg<Scalar>>) -> Self {
392 Self {
393 radius: circle.radius,
394 circle,
395 name,
396 angle: angle.unwrap_or(Deg(-45.0)),
397 }
398 }
399}
400
401impl MapToCanvas for RadiusMeasure {
402 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
403 Self {
404 radius: self.radius,
405 circle: self.circle.map_to_canvas(canvas),
406 name: self.name.clone(),
407 angle: self.angle,
408 }
409 }
410}
411
412impl WriteSvg for RadiusMeasure {
413 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
414 writer.begin_group(attr)?;
415
416 let edge = Line::radius_edge(&self.circle, &self.angle.into());
417 edge.shorter(1.5).write_svg(
418 writer,
419 &attr
420 .clone()
421 .insert(SvgTagAttribute::MarkerEnd("arrow".into()))
422 .insert(SvgTagAttribute::class("measure")),
423 )?;
424 let center = edge.center();
425
426 CenteredText {
427 text: format!(
428 "{name}{radius:.2}mm",
429 name = match &self.name {
430 Some(name) => format!("{name} = "),
431 None => String::new(),
432 },
433 radius = self.radius,
434 ),
435 rect: Rect::new(center, center),
436 font_size: 2.0,
437 }
438 .write_svg(writer, &SvgTagAttribute::class("measure-fill").into())?;
439
440 writer.end_group()?;
441
442 Ok(())
443 }
444}
445
446impl WriteSvgMapped for RadiusMeasure {}
447
448pub struct SizeMeasure {
450 bounds: Bounds2D,
451 width: Option<EdgeLengthMeasure>,
453 height: Option<EdgeLengthMeasure>,
455}
456
457impl SizeMeasure {
458 pub fn bounds<T: CalcBounds2D>(bounds: &T) -> Self {
460 let bounds = bounds.calc_bounds_2d();
461
462 if let Some(rect) = bounds.rect() {
463 Self {
464 bounds: bounds.clone(),
465 width: Some(EdgeLengthMeasure::width(&rect, 7.0, None)),
466 height: Some(EdgeLengthMeasure::height(&rect, 7.0, None)),
467 }
468 } else {
469 Self {
470 bounds: bounds.clone(),
471 width: None,
472 height: None,
473 }
474 }
475 }
476}
477
478impl MapToCanvas for SizeMeasure {
479 fn map_to_canvas(&self, canvas: &Canvas) -> Self {
480 Self {
481 bounds: self.bounds.map_to_canvas(canvas),
482 width: self.width.as_ref().map(|width| width.map_to_canvas(canvas)),
483 height: self
484 .height
485 .as_ref()
486 .map(|height| height.map_to_canvas(canvas)),
487 }
488 }
489}
490
491impl WriteSvg for SizeMeasure {
492 fn write_svg(&self, writer: &mut SvgWriter, attr: &SvgTagAttributes) -> std::io::Result<()> {
493 if let Some(width) = &self.width {
494 width.write_svg(writer, attr)?;
495 }
496 if let Some(height) = &self.height {
497 height.write_svg(writer, attr)?;
498 }
499 Ok(())
500 }
501}
502
503impl WriteSvgMapped for SizeMeasure {}