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