Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
10 kB · 374 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375use 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<String, String>, pub styles: HashMap<String, String>, pub children: Vec<Node>,}
#[derive(Debug, Clone)]pub enum Node { Element(Element), Text(String), SVG(String),}
impl Into<Node> 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<Point>, 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<Point>, p2: impl Into<Point>, 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<Point>, cell_size: usize) -> Self { self.coords(p.into().coords(cell_size)) }
/// Sets width and height pub fn dimensions<Coord: Into<f32>>( 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<Region>, 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<Region>, 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<Node>) -> &mut Self { self.children.push(child.into()); self }
pub fn with_attributes(self, attributes: HashMap<String, String>) -> Self { Element { attributes, ..self } }
pub fn wrapping( self, children: impl IntoIterator<Item = impl Into<Node>>, ) -> Self { Element { children: children.into_iter().map(|n| n.into()).collect(), ..self } }
pub fn child(self, child: impl Into<Node>) -> Self { Element { children: vec![child.into()], ..self } }
pub fn wrap(self, tag: &str, attrs: HashMap<String, String>) -> 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<PathInstruction>);
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<Point>, 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<Point>, 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<Point>, 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::<Vec<_>>() .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::<Option<String>>( attributes.get("style").map(|s| s.to_string()), ) .collect::<Vec<_>>() .join(" ") )?; }
if children.is_empty() { write!(f, "/>\n")?; } else { write!(f, ">\n")?;
for child in children { write!(f, "{}", child)?; }
write!(f, "</{tag}>")?; }
Ok(()) } } }}