use std::{collections::HashMap, fmt::Display}; use itertools::Itertools; use measure_time::debug_time; use crate::{Color, ColorMapping, Point, Region}; pub fn stringify_svg(element: Node) -> String { debug_time!("stringify_svg"); return element.to_string(); } #[derive(Debug, Clone)] pub struct Element { pub tag: String, pub attributes: HashMap, pub styles: HashMap, pub children: Vec, } #[derive(Debug, Clone)] pub enum Node { Element(Element), Text(String), SVG(String), } impl Into for Element { fn into(self) -> Node { self.node() } } pub fn tag(tag_name: &str) -> Element { Element::new(tag_name) } pub fn node(tag_name: &str) -> Node { tag(tag_name).node() } impl Node { pub fn is_empty(&self) -> bool { match self { Node::Element(e) => e.is_empty(), Node::Text(t) => t.is_empty(), Node::SVG(s) => s.is_empty(), } } } impl Element { pub fn node(self) -> Node { Node::Element(self) } pub fn new(tag: &str) -> Self { Element { tag: tag.to_string(), attributes: HashMap::new(), styles: HashMap::new(), children: Vec::new(), } } pub fn attr(self, key: &str, value: impl Display) -> Self { // assert!( // key != "style", // "Use `style` method instead of `attr` for style attributes." // ); let mut attributes = self.attributes.clone(); attributes.insert(key.to_string(), value.to_string()); Element { attributes, ..self } } /// Sets x and y pub fn coords(self, p: impl Into<(f32, f32)>) -> Self { let (x, y) = p.into(); self.attr("x", x).attr("y", y) } pub fn fill(self, c: Color, colormap: &ColorMapping) -> Self { self.attr("fill", c.render(colormap)) } /// Sets cx and cy pub fn center_position(self, p: impl Into, cell_size: usize) -> Self { let (x, y) = p.into().coords(cell_size); self.attr("cx", x).attr("cy", y) } /// Sets x1, y1 and x2, y2 pub fn position_pair( self, p1: impl Into, p2: impl Into, cell_size: usize, ) -> Self { let (x1, y1) = p1.into().coords(cell_size); let (x2, y2) = p2.into().coords(cell_size); self.attr("x1", x1) .attr("y1", y1) .attr("x2", x2) .attr("y2", y2) } /// Sets x and y pub fn position(self, p: impl Into, cell_size: usize) -> Self { self.coords(p.into().coords(cell_size)) } /// Sets width and height pub fn dimensions>( self, p: impl Into<(Coord, Coord)>, ) -> Self { let (w, h) = p.into(); self.attr("width", w.into()).attr("height", h.into()) } /// Sets width and height pub fn size(self, r: impl Into, cell_size: usize) -> Self { self.dimensions(r.into().size(cell_size)) } /// Sets x, y, width and height according to the region pub fn region(self, r: impl Into, cell_size: usize) -> Self { let region: Region = r.into(); self.position(region.start, cell_size) .size(region, cell_size) } pub fn style(self, key: &str, value: impl Display) -> Self { let mut styles = self.styles.clone(); styles.insert(key.to_string(), value.to_string()); Element { styles, ..self } } pub fn dataset(self, key: &str, value: impl Display) -> Self { self.attr(&format!("data-{key}"), value) } pub fn class(self, class: &str) -> Self { self.attr("class", class) } pub fn add(&mut self, child: impl Into) -> &mut Self { self.children.push(child.into()); self } pub fn with_attributes(self, attributes: HashMap) -> Self { Element { attributes, ..self } } pub fn wrapping( self, children: impl IntoIterator>, ) -> Self { Element { children: children.into_iter().map(|n| n.into()).collect(), ..self } } pub fn child(self, child: impl Into) -> Self { Element { children: vec![child.into()], ..self } } pub fn wrap(self, tag: &str, attrs: HashMap) -> Self { Element { tag: tag.to_string(), styles: HashMap::new(), attributes: attrs, children: vec![Node::Element(self)], } } pub fn is_empty(&self) -> bool { self.children.is_empty() } } pub enum PathInstruction { MoveTo((f32, f32)), LineTo((f32, f32)), HorizontalLineTo(f32), VerticalLineTo(f32), CurveTo((f32, f32), (f32, f32), (f32, f32)), SmoothCurveTo((f32, f32), (f32, f32)), QuadraticCurveTo((f32, f32), (f32, f32)), SmoothQuadraticCurveTo((f32, f32)), ArcTo((f32, f32), f32, bool, bool, (f32, f32)), ClosePath, } pub struct Path(Vec); impl Path { pub fn new() -> Self { Path(Vec::new()) } pub fn node(self) -> Node { self.element().node() } pub fn element(self) -> Element { tag("path").attr("d", self.to_string()) } pub fn move_to( &mut self, p: impl Into, cell_size: usize, ) -> &mut Self { self.0 .push(PathInstruction::MoveTo(p.into().coords(cell_size))); self } pub fn line_to( &mut self, p: impl Into, cell_size: usize, ) -> &mut Self { self.0 .push(PathInstruction::LineTo(p.into().coords(cell_size))); self } pub fn quadratic_curve_to( &mut self, control: impl Into<(f32, f32)>, end: impl Into, cell_size: usize, ) -> &mut Self { self.0.push(PathInstruction::QuadraticCurveTo( control.into(), end.into().coords(cell_size), )); self } pub fn close(&mut self) -> &mut Self { self.0.push(PathInstruction::ClosePath); self } } impl Display for Path { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str( &self .0 .iter() .map(|i| i.to_string()) .collect::>() .join(" "), ) } } impl Display for PathInstruction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::MoveTo((x, y)) => write!(f, "M {x} {y}"), Self::LineTo((x, y)) => write!(f, "L {x} {y}"), Self::HorizontalLineTo(x) => write!(f, "H {x}"), Self::VerticalLineTo(y) => write!(f, "V {y}"), Self::CurveTo((x1, y1), (x2, y2), (x3, y3)) => { write!(f, "C {x1} {y1} {x2} {y2} {x3} {y3}") } Self::SmoothCurveTo((x2, y2), (x3, y3)) => { write!(f, "S {x2} {y2} {x3} {y3}") } Self::QuadraticCurveTo((x1, y1), (x2, y2)) => { write!(f, "Q {x1} {y1} {x2} {y2}") } Self::SmoothQuadraticCurveTo((x2, y2)) => { write!(f, "T {x2} {y2}") } Self::ArcTo( (rx, ry), angle, large_arc_flag, sweep_flag, (x2, y2), ) => { write!( f, "A {rx} {ry} {angle} {large_arc_flag} {sweep_flag} {x2} {y2}" ) } Self::ClosePath => write!(f, "Z"), } } } fn space_if(add_space: bool) -> &'static str { if add_space { " " } else { "" } } impl Display for Node { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { debug_time!("svg::Node::fmt"); match self { Node::Text(text) => write!(f, "{}", quick_xml::escape::escape(text)), Node::SVG(svg) => write!(f, "{}", svg), Node::Element(Element { tag, attributes, styles, children, }) => { write!(f, "<{tag} ")?; let non_style_attributes: Vec<_> = attributes .iter() .filter(|(k, _)| *k != "style") .sorted_by_key(|(k, _)| *k) .collect(); for (i, (key, value)) in non_style_attributes.iter().enumerate() { write!( f, r#"{spacing}{key}="{value}""#, spacing = space_if(i > 0), key = key, value = value .replace("&", "&") .replace('"', """) .replace("'", "'") )?; } if attributes.contains_key("style") || !styles.is_empty() { write!( f, r#"{spacing}style="{value}""#, spacing = space_if(non_style_attributes.len() > 0), value = styles .iter() .map(|(k, v)| format!("{k}: {v};")) .chain::>( attributes.get("style").map(|s| s.to_string()), ) .collect::>() .join(" ") )?; } if children.is_empty() { write!(f, "/>\n")?; } else { write!(f, ">\n")?; for child in children { write!(f, "{}", child)?; } write!(f, "")?; } Ok(()) } } } }