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