This repository has no description
1use std::{collections::HashMap, fmt::Display};
2
3use itertools::Itertools;
4use measure_time::debug_time;
5
6use crate::{Color, ColorMapping, Point, Region};
7
8pub fn stringify_svg(element: Node) -> String {
9 debug_time!("stringify_svg");
10
11 return element.to_string();
12}
13
14#[derive(Debug, Clone)]
15pub struct Element {
16 pub tag: String,
17 pub attributes: HashMap<String, String>,
18 pub styles: HashMap<String, String>,
19 pub children: Vec<Node>,
20}
21
22#[derive(Debug, Clone)]
23pub enum Node {
24 Element(Element),
25 Text(String),
26 SVG(String),
27}
28
29impl Into<Node> for Element {
30 fn into(self) -> Node {
31 self.node()
32 }
33}
34
35pub fn tag(tag_name: &str) -> Element {
36 Element::new(tag_name)
37}
38
39pub fn node(tag_name: &str) -> Node {
40 tag(tag_name).node()
41}
42
43impl Node {
44 pub fn is_empty(&self) -> bool {
45 match self {
46 Node::Element(e) => e.is_empty(),
47 Node::Text(t) => t.is_empty(),
48 Node::SVG(s) => s.is_empty(),
49 }
50 }
51}
52
53impl Element {
54 pub fn node(self) -> Node {
55 Node::Element(self)
56 }
57
58 pub fn new(tag: &str) -> Self {
59 Element {
60 tag: tag.to_string(),
61 attributes: HashMap::new(),
62 styles: HashMap::new(),
63 children: Vec::new(),
64 }
65 }
66
67 pub fn attr(self, key: &str, value: impl Display) -> Self {
68 // assert!(
69 // key != "style",
70 // "Use `style` method instead of `attr` for style attributes."
71 // );
72 let mut attributes = self.attributes.clone();
73 attributes.insert(key.to_string(), value.to_string());
74 Element { attributes, ..self }
75 }
76
77 /// Sets x and y
78 pub fn coords(self, p: impl Into<(f32, f32)>) -> Self {
79 let (x, y) = p.into();
80 self.attr("x", x).attr("y", y)
81 }
82
83 pub fn fill(self, c: Color, colormap: &ColorMapping) -> Self {
84 self.attr("fill", c.render(colormap))
85 }
86
87 /// Sets cx and cy
88 pub fn center_position(self, p: impl Into<Point>, cell_size: usize) -> Self {
89 let (x, y) = p.into().coords(cell_size);
90 self.attr("cx", x).attr("cy", y)
91 }
92
93 /// Sets x1, y1 and x2, y2
94 pub fn position_pair(
95 self,
96 p1: impl Into<Point>,
97 p2: impl Into<Point>,
98 cell_size: usize,
99 ) -> Self {
100 let (x1, y1) = p1.into().coords(cell_size);
101 let (x2, y2) = p2.into().coords(cell_size);
102 self.attr("x1", x1)
103 .attr("y1", y1)
104 .attr("x2", x2)
105 .attr("y2", y2)
106 }
107
108 /// Sets x and y
109 pub fn position(self, p: impl Into<Point>, cell_size: usize) -> Self {
110 self.coords(p.into().coords(cell_size))
111 }
112
113 /// Sets width and height
114 pub fn dimensions<Coord: Into<f32>>(
115 self,
116 p: impl Into<(Coord, Coord)>,
117 ) -> Self {
118 let (w, h) = p.into();
119 self.attr("width", w.into()).attr("height", h.into())
120 }
121
122 /// Sets width and height
123 pub fn size(self, r: impl Into<Region>, cell_size: usize) -> Self {
124 self.dimensions(r.into().size(cell_size))
125 }
126
127 /// Sets x, y, width and height according to the region
128 pub fn region(self, r: impl Into<Region>, cell_size: usize) -> Self {
129 let region: Region = r.into();
130 self.position(region.start, cell_size)
131 .size(region, cell_size)
132 }
133
134 pub fn style(self, key: &str, value: impl Display) -> Self {
135 let mut styles = self.styles.clone();
136 styles.insert(key.to_string(), value.to_string());
137 Element { styles, ..self }
138 }
139
140 pub fn dataset(self, key: &str, value: impl Display) -> Self {
141 self.attr(&format!("data-{key}"), value)
142 }
143
144 pub fn class(self, class: &str) -> Self {
145 self.attr("class", class)
146 }
147
148 pub fn add(&mut self, child: impl Into<Node>) -> &mut Self {
149 self.children.push(child.into());
150 self
151 }
152
153 pub fn with_attributes(self, attributes: HashMap<String, String>) -> Self {
154 Element { attributes, ..self }
155 }
156
157 pub fn wrapping(
158 self,
159 children: impl IntoIterator<Item = impl Into<Node>>,
160 ) -> Self {
161 Element {
162 children: children.into_iter().map(|n| n.into()).collect(),
163 ..self
164 }
165 }
166
167 pub fn child(self, child: impl Into<Node>) -> Self {
168 Element {
169 children: vec![child.into()],
170 ..self
171 }
172 }
173
174 pub fn wrap(self, tag: &str, attrs: HashMap<String, String>) -> Self {
175 Element {
176 tag: tag.to_string(),
177 styles: HashMap::new(),
178 attributes: attrs,
179 children: vec![Node::Element(self)],
180 }
181 }
182
183 pub fn is_empty(&self) -> bool {
184 self.children.is_empty()
185 }
186}
187
188pub enum PathInstruction {
189 MoveTo((f32, f32)),
190 LineTo((f32, f32)),
191 HorizontalLineTo(f32),
192 VerticalLineTo(f32),
193 CurveTo((f32, f32), (f32, f32), (f32, f32)),
194 SmoothCurveTo((f32, f32), (f32, f32)),
195 QuadraticCurveTo((f32, f32), (f32, f32)),
196 SmoothQuadraticCurveTo((f32, f32)),
197 ArcTo((f32, f32), f32, bool, bool, (f32, f32)),
198 ClosePath,
199}
200
201pub struct Path(Vec<PathInstruction>);
202
203impl Path {
204 pub fn new() -> Self {
205 Path(Vec::new())
206 }
207
208 pub fn node(self) -> Node {
209 self.element().node()
210 }
211
212 pub fn element(self) -> Element {
213 tag("path").attr("d", self.to_string())
214 }
215
216 pub fn move_to(
217 &mut self,
218 p: impl Into<Point>,
219 cell_size: usize,
220 ) -> &mut Self {
221 self.0
222 .push(PathInstruction::MoveTo(p.into().coords(cell_size)));
223 self
224 }
225
226 pub fn line_to(
227 &mut self,
228 p: impl Into<Point>,
229 cell_size: usize,
230 ) -> &mut Self {
231 self.0
232 .push(PathInstruction::LineTo(p.into().coords(cell_size)));
233 self
234 }
235
236 pub fn quadratic_curve_to(
237 &mut self,
238 control: impl Into<(f32, f32)>,
239 end: impl Into<Point>,
240 cell_size: usize,
241 ) -> &mut Self {
242 self.0.push(PathInstruction::QuadraticCurveTo(
243 control.into(),
244 end.into().coords(cell_size),
245 ));
246 self
247 }
248
249 pub fn close(&mut self) -> &mut Self {
250 self.0.push(PathInstruction::ClosePath);
251 self
252 }
253}
254
255impl Display for Path {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 f.write_str(
258 &self
259 .0
260 .iter()
261 .map(|i| i.to_string())
262 .collect::<Vec<_>>()
263 .join(" "),
264 )
265 }
266}
267
268impl Display for PathInstruction {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 Self::MoveTo((x, y)) => write!(f, "M {x} {y}"),
272 Self::LineTo((x, y)) => write!(f, "L {x} {y}"),
273 Self::HorizontalLineTo(x) => write!(f, "H {x}"),
274 Self::VerticalLineTo(y) => write!(f, "V {y}"),
275 Self::CurveTo((x1, y1), (x2, y2), (x3, y3)) => {
276 write!(f, "C {x1} {y1} {x2} {y2} {x3} {y3}")
277 }
278 Self::SmoothCurveTo((x2, y2), (x3, y3)) => {
279 write!(f, "S {x2} {y2} {x3} {y3}")
280 }
281 Self::QuadraticCurveTo((x1, y1), (x2, y2)) => {
282 write!(f, "Q {x1} {y1} {x2} {y2}")
283 }
284 Self::SmoothQuadraticCurveTo((x2, y2)) => {
285 write!(f, "T {x2} {y2}")
286 }
287 Self::ArcTo(
288 (rx, ry),
289 angle,
290 large_arc_flag,
291 sweep_flag,
292 (x2, y2),
293 ) => {
294 write!(
295 f,
296 "A {rx} {ry} {angle} {large_arc_flag} {sweep_flag} {x2} {y2}"
297 )
298 }
299 Self::ClosePath => write!(f, "Z"),
300 }
301 }
302}
303
304fn space_if(add_space: bool) -> &'static str {
305 if add_space { " " } else { "" }
306}
307
308impl Display for Node {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 debug_time!("svg::Node::fmt");
311
312 match self {
313 Node::Text(text) => write!(f, "{}", quick_xml::escape::escape(text)),
314 Node::SVG(svg) => write!(f, "{}", svg),
315 Node::Element(Element {
316 tag,
317 attributes,
318 styles,
319 children,
320 }) => {
321 write!(f, "<{tag} ")?;
322
323 let non_style_attributes: Vec<_> = attributes
324 .iter()
325 .filter(|(k, _)| *k != "style")
326 .sorted_by_key(|(k, _)| *k)
327 .collect();
328
329 for (i, (key, value)) in non_style_attributes.iter().enumerate() {
330 write!(
331 f,
332 r#"{spacing}{key}="{value}""#,
333 spacing = space_if(i > 0),
334 key = key,
335 value = value
336 .replace("&", "&")
337 .replace('"', """)
338 .replace("'", "'")
339 )?;
340 }
341
342 if attributes.contains_key("style") || !styles.is_empty() {
343 write!(
344 f,
345 r#"{spacing}style="{value}""#,
346 spacing = space_if(non_style_attributes.len() > 0),
347 value = styles
348 .iter()
349 .map(|(k, v)| format!("{k}: {v};"))
350 .chain::<Option<String>>(
351 attributes.get("style").map(|s| s.to_string()),
352 )
353 .collect::<Vec<_>>()
354 .join(" ")
355 )?;
356 }
357
358 if children.is_empty() {
359 write!(f, "/>\n")?;
360 } else {
361 write!(f, ">\n")?;
362
363 for child in children {
364 write!(f, "{}", child)?;
365 }
366
367 write!(f, "</{tag}>")?;
368 }
369
370 Ok(())
371 }
372 }
373 }
374}