Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
12 kB · 321 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322use measure_time::debug_time;
use crate::{LineSegment, Shape, graphics::objects::ObjectSizes};
use super::{renderable::SVGRenderable, svg};
impl SVGRenderable for Shape { fn render_to_svg( &self, colormap: crate::ColorMapping, cell_size: usize, object_sizes: crate::graphics::objects::ObjectSizes, id: &str, ) -> anyhow::Result<svg::Node> { debug_time!("render_to_svg/object"); let rendered = match self { Shape::Text(..) | Shape::CenteredText(..) => { self.render_text(cell_size) } Shape::Rectangle(..) => self.render_rectangle(cell_size), Shape::Polygon(..) => self.render_polygon(cell_size), Shape::Line(..) => self.render_line(cell_size), Shape::CurveInward(..) | Shape::CurveOutward(..) => { self.render_curve(cell_size) } Shape::BigDot(..) | Shape::Dot(..) | Shape::BigCircle(..) | Shape::SmallCircle(..) => { self.render_circle(cell_size, object_sizes) } Shape::Image(..) => self.render_image(cell_size), Shape::Component { .. } => { self.render_component(colormap, cell_size, object_sizes, id) } Shape::RawSVG { .. } => { unimplemented!("RawSVG shapes require an Object to render") } };
Ok(match rendered { svg::Node::Element(el) => el.dataset("object", id).into(), svg::Node::SVG(svg) => { if svg.trim().starts_with("<") { let (before, after) = svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); svg::Node::SVG(format!( r#"{before} data-object="{id}" {after}"# )) } else { eprintln!("Malformed raw SVG, {svg} doesn't start with <"); svg::Node::SVG(svg) } } _ => { panic!("Expected Element or SVG, got {:?}", rendered); } }) }}
impl Shape { fn render_image(&self, cell_size: usize) -> svg::Node { if let Shape::Image(region, path) = self { return svg::tag("image") .coords(region.start.coords(cell_size)) .attr("width", region.width() * cell_size) .attr("height", region.height() * cell_size) .attr("href", path.clone()) .into(); }
panic!("Expected Image, got {:?}", self); }
fn render_component( &self, colormap: crate::ColorMapping, cell_size: usize, object_sizes: crate::graphics::objects::ObjectSizes, id: &str, ) -> svg::Node { if let Shape::Component { at, size: (real_w, real_h), objects, } = self { let (object_w, object_h) = objects .iter() .map(|o| o.position().xy()) .fold((0, 0), |(max_w, max_h), (w, h)| { (max_w.max(w) + 1, max_h.max(h) + 1) });
let percent = |num: usize, den: usize| format!("{}%", (num * 100) / (den));
return svg::tag("svg") .coords(at.coords(cell_size)) .dataset("nested-dims", format!("{object_w}x{object_h}")) .style( "transform", format!( "scale({}, {})", percent(*real_w, object_w), percent(*real_h, object_h) ), ) .wrapping(objects.iter().map(move |o| { o.render_to_svg(colormap.clone(), cell_size, object_sizes, id) .expect("Could not render component object to SVG") })) .into(); }
panic!("Expected Component, got {:?}", self); }
fn render_text(&self, cell_size: usize) -> svg::Node { match self { Shape::Text(position, content, font_size) | Shape::CenteredText(position, content, font_size) => { let centered = matches!(self, Shape::CenteredText(..));
svg::tag("text") .coords( if centered { position.as_centered() } else { position.as_corner() } .coords(cell_size), ) .attr("font-size", format!("{}pt", font_size)) .attr("font-family", "Inconsolata") .attr( "dominant-baseline", if centered { "middle" } else { "hanging" }, ) .attr( "text-anchor", if centered { "middle" } else { "start" }, ) .wrapping(vec![svg::Node::Text(content.to_string())]) .into() } _ => panic!("Expected Text, got {:?}", self), } }
// fn render_fitted_text(&self, cell_size: usize) -> svg::Node { // if let Object::FittedText(region, content) = self { // let (x, y) = region.start.coords(cell_size); // let width = region.width() * cell_size as f32; // let height = region.height() * cell_size as f32;
// return Box::new( // svg::node::element::Text::new(content.clone()) // .set("x", x) // .set("y", y) // .set("") // .set("font-size", format!("{}pt", 10.0)) // .set("font-family", "sans-serif"), // ); // }
// panic!("Expected FittedText, got {:?}", self); // }
fn render_rectangle(&self, cell_size: usize) -> svg::Node { if let Shape::Rectangle(start, end) = self { return svg::tag("rect").region((start, end), cell_size).into(); }
panic!("Expected Rectangle, got {:?}", self); }
fn render_polygon(&self, cell_size: usize) -> svg::Node { if let Shape::Polygon(start, lines) = self { let mut path = svg::Path::new(); path.move_to(*start, cell_size); for line in lines { match line { LineSegment::Straight(end) | LineSegment::InwardCurve(end) | LineSegment::OutwardCurve(end) => { path.line_to(*end, cell_size); } }; } path.close(); return path.node(); }
panic!("Expected Polygon, got {:?}", self); }
fn render_line(&self, cell_size: usize) -> svg::Node { if let Shape::Line(start, end, width) = self { return svg::tag("line") .position_pair(*start, *end, cell_size) .attr("stroke-width", *width) .into(); }
panic!("Expected Line, got {:?}", self); }
fn render_curve(&self, cell_size: usize) -> svg::Node { if let Shape::CurveOutward(start, end, stroke_width) | Shape::CurveInward(start, end, stroke_width) = self { let inward = matches!(self, Shape::CurveInward(..));
let (start_x, start_y) = start.coords(cell_size); let (end_x, end_y) = end.coords(cell_size);
let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); let start_from_midpoint = (start_x - midpoint.0, start_y - midpoint.1); let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1);
let control = { let relative = (end_x - start_x, end_y - start_y); if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 { if inward { ( midpoint.0 + relative.0.abs() / 2.0, midpoint.1 - relative.1.abs() / 2.0, ) } else { ( midpoint.0 - relative.0.abs() / 2.0, midpoint.1 + relative.1.abs() / 2.0, ) } // diagonal line is going like this: / } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 { if inward { ( midpoint.0 - relative.0.abs() / 2.0, midpoint.1 - relative.1.abs() / 2.0, ) } else { ( midpoint.0 + relative.0.abs() / 2.0, midpoint.1 + relative.1.abs() / 2.0, ) } // line is horizontal } else if start_y == end_y { ( midpoint.0, midpoint.1 + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0, ) // line is vertical } else if start_x == end_x { ( midpoint.0 + (if inward { -1.0 } else { 1.0 }) * relative.1.abs() / 2.0, midpoint.1, ) } else { unreachable!() } };
let mut path = svg::Path::new(); path.move_to(*start, cell_size); path.quadratic_curve_to(control, *end, cell_size); return path .element() .attr("stroke-width", format!("{stroke_width}")) .into(); }
panic!("Expected Curve, got {:?}", self); }
fn render_circle( &self, cell_size: usize, object_sizes: ObjectSizes, ) -> svg::Node { let center = match self { Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), Shape::BigCircle(at) | Shape::SmallCircle(at) => { at.as_centered().coords(cell_size) }
_ => panic!( "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", self ), };
let radius = match self { Shape::BigDot(_) => object_sizes.small_circle_radius, Shape::Dot(_) => object_sizes.dot_radius, Shape::BigCircle(_) => cell_size as f32 / 2.0, Shape::SmallCircle(_) => object_sizes.small_circle_radius, _ => unreachable!(), };
return svg::tag("circle") .attr("cx", center.0) .attr("cy", center.1) .attr("r", radius) .into(); }}