This repository has no description
1use measure_time::debug_time;
2
3use crate::{LineSegment, Shape, graphics::objects::ObjectSizes};
4
5use super::{renderable::SVGRenderable, svg};
6
7impl SVGRenderable for Shape {
8 fn render_to_svg(
9 &self,
10 _colormap: crate::ColorMapping,
11 cell_size: usize,
12 object_sizes: crate::graphics::objects::ObjectSizes,
13 id: &str,
14 ) -> anyhow::Result<svg::Node> {
15 debug_time!("render_to_svg/object");
16 let rendered = match self {
17 Shape::Text(..) | Shape::CenteredText(..) => {
18 self.render_text(cell_size)
19 }
20 Shape::Rectangle(..) => self.render_rectangle(cell_size),
21 Shape::Polygon(..) => self.render_polygon(cell_size),
22 Shape::Line(..) => self.render_line(cell_size),
23 Shape::CurveInward(..) | Shape::CurveOutward(..) => {
24 self.render_curve(cell_size)
25 }
26 Shape::BigDot(..)
27 | Shape::Dot(..)
28 | Shape::BigCircle(..)
29 | Shape::SmallCircle(..) => {
30 self.render_circle(cell_size, object_sizes)
31 }
32 Shape::Image(..) => self.render_image(cell_size),
33 Shape::RawSVG(..) => self.render_raw_svg(),
34 };
35
36 Ok(match rendered {
37 svg::Node::Element(el) => el.dataset("object", id).into(),
38 svg::Node::SVG(svg) => {
39 if svg.trim().starts_with("<") {
40 let (before, after) =
41 svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >"));
42 svg::Node::SVG(format!(
43 r#"{before} data-object="{id}" {after}"#
44 ))
45 } else {
46 eprintln!("Malformed raw SVG, {svg} doesn't start with <");
47 svg::Node::SVG(svg)
48 }
49 }
50 _ => {
51 panic!("Expected Element or SVG, got {:?}", rendered);
52 }
53 })
54 }
55}
56
57impl Shape {
58 fn render_image(&self, cell_size: usize) -> svg::Node {
59 if let Shape::Image(region, path) = self {
60 return svg::tag("image")
61 .coords(region.start.coords(cell_size))
62 .attr("width", region.width() * cell_size)
63 .attr("height", region.height() * cell_size)
64 .attr("href", path.clone())
65 .into();
66 }
67
68 panic!("Expected Image, got {:?}", self);
69 }
70
71 fn render_raw_svg(&self) -> svg::Node {
72 if let Shape::RawSVG(svg) = self {
73 return svg::Node::SVG(svg.clone());
74 }
75
76 panic!("Expected RawSVG, got {:?}", self);
77 }
78
79 fn render_text(&self, cell_size: usize) -> svg::Node {
80 match self {
81 Shape::Text(position, content, font_size)
82 | Shape::CenteredText(position, content, font_size) => {
83 let centered = matches!(self, Shape::CenteredText(..));
84
85 svg::tag("text")
86 .coords(
87 if centered {
88 position.as_centered()
89 } else {
90 position.as_corner()
91 }
92 .coords(cell_size),
93 )
94 .attr("font-size", format!("{}pt", font_size))
95 .attr("font-family", "Inconsolata")
96 .attr(
97 "dominant-baseline",
98 if centered { "middle" } else { "hanging" },
99 )
100 .attr(
101 "text-anchor",
102 if centered { "middle" } else { "start" },
103 )
104 .wrapping(vec![svg::Node::Text(content.to_string())])
105 .into()
106 }
107 _ => panic!("Expected Text, got {:?}", self),
108 }
109 }
110
111 // fn render_fitted_text(&self, cell_size: usize) -> svg::Node {
112 // if let Object::FittedText(region, content) = self {
113 // let (x, y) = region.start.coords(cell_size);
114 // let width = region.width() * cell_size as f32;
115 // let height = region.height() * cell_size as f32;
116
117 // return Box::new(
118 // svg::node::element::Text::new(content.clone())
119 // .set("x", x)
120 // .set("y", y)
121 // .set("")
122 // .set("font-size", format!("{}pt", 10.0))
123 // .set("font-family", "sans-serif"),
124 // );
125 // }
126
127 // panic!("Expected FittedText, got {:?}", self);
128 // }
129
130 fn render_rectangle(&self, cell_size: usize) -> svg::Node {
131 if let Shape::Rectangle(start, end) = self {
132 return svg::tag("rect").region((start, end), cell_size).into();
133 }
134
135 panic!("Expected Rectangle, got {:?}", self);
136 }
137
138 fn render_polygon(&self, cell_size: usize) -> svg::Node {
139 if let Shape::Polygon(start, lines) = self {
140 let mut path = svg::Path::new();
141 path.move_to(*start, cell_size);
142 for line in lines {
143 match line {
144 LineSegment::Straight(end)
145 | LineSegment::InwardCurve(end)
146 | LineSegment::OutwardCurve(end) => {
147 path.line_to(*end, cell_size);
148 }
149 };
150 }
151 path.close();
152 return path.node();
153 }
154
155 panic!("Expected Polygon, got {:?}", self);
156 }
157
158 fn render_line(&self, cell_size: usize) -> svg::Node {
159 if let Shape::Line(start, end, width) = self {
160 return svg::tag("line")
161 .position_pair(*start, *end, cell_size)
162 .attr("stroke-width", *width)
163 .into();
164 }
165
166 panic!("Expected Line, got {:?}", self);
167 }
168
169 fn render_curve(&self, cell_size: usize) -> svg::Node {
170 if let Shape::CurveOutward(start, end, stroke_width)
171 | Shape::CurveInward(start, end, stroke_width) = self
172 {
173 let inward = matches!(self, Shape::CurveInward(..));
174
175 let (start_x, start_y) = start.coords(cell_size);
176 let (end_x, end_y) = end.coords(cell_size);
177
178 let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0);
179 let start_from_midpoint =
180 (start_x - midpoint.0, start_y - midpoint.1);
181 let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1);
182
183 let control = {
184 let relative = (end_x - start_x, end_y - start_y);
185 if start_from_midpoint.0 * start_from_midpoint.1 > 0.0
186 && end_from_midpoint.0 * end_from_midpoint.1 > 0.0
187 {
188 if inward {
189 (
190 midpoint.0 + relative.0.abs() / 2.0,
191 midpoint.1 - relative.1.abs() / 2.0,
192 )
193 } else {
194 (
195 midpoint.0 - relative.0.abs() / 2.0,
196 midpoint.1 + relative.1.abs() / 2.0,
197 )
198 }
199 // diagonal line is going like this: /
200 } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0
201 && end_from_midpoint.0 * end_from_midpoint.1 < 0.0
202 {
203 if inward {
204 (
205 midpoint.0 - relative.0.abs() / 2.0,
206 midpoint.1 - relative.1.abs() / 2.0,
207 )
208 } else {
209 (
210 midpoint.0 + relative.0.abs() / 2.0,
211 midpoint.1 + relative.1.abs() / 2.0,
212 )
213 }
214 // line is horizontal
215 } else if start_y == end_y {
216 (
217 midpoint.0,
218 midpoint.1
219 + (if inward { -1.0 } else { 1.0 })
220 * relative.0.abs()
221 / 2.0,
222 )
223 // line is vertical
224 } else if start_x == end_x {
225 (
226 midpoint.0
227 + (if inward { -1.0 } else { 1.0 })
228 * relative.1.abs()
229 / 2.0,
230 midpoint.1,
231 )
232 } else {
233 unreachable!()
234 }
235 };
236
237 let mut path = svg::Path::new();
238 path.move_to(*start, cell_size);
239 path.quadratic_curve_to(control, *end, cell_size);
240 return path
241 .element()
242 .attr("stroke-width", format!("{stroke_width}"))
243 .into();
244 }
245
246 panic!("Expected Curve, got {:?}", self);
247 }
248
249 fn render_circle(
250 &self,
251 cell_size: usize,
252 object_sizes: ObjectSizes,
253 ) -> svg::Node {
254 let center = match self {
255 Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size),
256 Shape::BigCircle(at) | Shape::SmallCircle(at) => {
257 at.as_centered().coords(cell_size)
258 }
259
260 _ => panic!(
261 "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}",
262 self
263 ),
264 };
265
266 let radius = match self {
267 Shape::BigDot(_) => object_sizes.small_circle_radius,
268 Shape::Dot(_) => object_sizes.dot_radius,
269 Shape::BigCircle(_) => cell_size as f32 / 2.0,
270 Shape::SmallCircle(_) => object_sizes.small_circle_radius,
271 _ => unreachable!(),
272 };
273
274 return svg::tag("circle")
275 .attr("cx", center.0)
276 .attr("cy", center.1)
277 .attr("r", radius)
278 .into();
279 }
280}