This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 12 kB View raw
1use measure_time::debug_time; 2 3use crate::{LineSegment, Shape, graphics::objects::ObjectSizes}; 4 5use super::{renderable::SVGRenderable, svg}; 6 7impl SVGRenderable for Shape { 8 fn render_to_svg( 9 &self, 10 colormap: crate::ColorMapping, 11 cell_size: usize, 12 object_sizes: crate::graphics::objects::ObjectSizes, 13 id: &str, 14 ) -> anyhow::Result<svg::Node> { 15 debug_time!("render_to_svg/object"); 16 let rendered = match self { 17 Shape::Text(..) | Shape::CenteredText(..) => { 18 self.render_text(cell_size) 19 } 20 Shape::Rectangle(..) => self.render_rectangle(cell_size), 21 Shape::Polygon(..) => self.render_polygon(cell_size), 22 Shape::Line(..) => self.render_line(cell_size), 23 Shape::CurveInward(..) | Shape::CurveOutward(..) => { 24 self.render_curve(cell_size) 25 } 26 Shape::BigDot(..) 27 | Shape::Dot(..) 28 | Shape::BigCircle(..) 29 | Shape::SmallCircle(..) => { 30 self.render_circle(cell_size, object_sizes) 31 } 32 Shape::Image(..) => self.render_image(cell_size), 33 Shape::Component { .. } => { 34 self.render_component(colormap, cell_size, object_sizes, id) 35 } 36 Shape::RawSVG { .. } => { 37 unimplemented!("RawSVG shapes require an Object to render") 38 } 39 }; 40 41 Ok(match rendered { 42 svg::Node::Element(el) => el.dataset("object", id).into(), 43 svg::Node::SVG(svg) => { 44 if svg.trim().starts_with("<") { 45 let (before, after) = 46 svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 47 svg::Node::SVG(format!( 48 r#"{before} data-object="{id}" {after}"# 49 )) 50 } else { 51 eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 52 svg::Node::SVG(svg) 53 } 54 } 55 _ => { 56 panic!("Expected Element or SVG, got {:?}", rendered); 57 } 58 }) 59 } 60} 61 62impl Shape { 63 fn render_image(&self, cell_size: usize) -> svg::Node { 64 if let Shape::Image(region, path) = self { 65 return svg::tag("image") 66 .coords(region.start.coords(cell_size)) 67 .attr("width", region.width() * cell_size) 68 .attr("height", region.height() * cell_size) 69 .attr("href", path.clone()) 70 .into(); 71 } 72 73 panic!("Expected Image, got {:?}", self); 74 } 75 76 fn render_component( 77 &self, 78 colormap: crate::ColorMapping, 79 cell_size: usize, 80 object_sizes: crate::graphics::objects::ObjectSizes, 81 id: &str, 82 ) -> svg::Node { 83 if let Shape::Component { 84 at, 85 size: (real_w, real_h), 86 objects, 87 } = self 88 { 89 let (object_w, object_h) = objects 90 .iter() 91 .map(|o| o.position().xy()) 92 .fold((0, 0), |(max_w, max_h), (w, h)| { 93 (max_w.max(w) + 1, max_h.max(h) + 1) 94 }); 95 96 let percent = 97 |num: usize, den: usize| format!("{}%", (num * 100) / (den)); 98 99 return svg::tag("svg") 100 .coords(at.coords(cell_size)) 101 .dataset("nested-dims", format!("{object_w}x{object_h}")) 102 .style( 103 "transform", 104 format!( 105 "scale({}, {})", 106 percent(*real_w, object_w), 107 percent(*real_h, object_h) 108 ), 109 ) 110 .wrapping(objects.iter().map(move |o| { 111 o.render_to_svg(colormap.clone(), cell_size, object_sizes, id) 112 .expect("Could not render component object to SVG") 113 })) 114 .into(); 115 } 116 117 panic!("Expected Component, got {:?}", self); 118 } 119 120 fn render_text(&self, cell_size: usize) -> svg::Node { 121 match self { 122 Shape::Text(position, content, font_size) 123 | Shape::CenteredText(position, content, font_size) => { 124 let centered = matches!(self, Shape::CenteredText(..)); 125 126 svg::tag("text") 127 .coords( 128 if centered { 129 position.as_centered() 130 } else { 131 position.as_corner() 132 } 133 .coords(cell_size), 134 ) 135 .attr("font-size", format!("{}pt", font_size)) 136 .attr("font-family", "Inconsolata") 137 .attr( 138 "dominant-baseline", 139 if centered { "middle" } else { "hanging" }, 140 ) 141 .attr( 142 "text-anchor", 143 if centered { "middle" } else { "start" }, 144 ) 145 .wrapping(vec![svg::Node::Text(content.to_string())]) 146 .into() 147 } 148 _ => panic!("Expected Text, got {:?}", self), 149 } 150 } 151 152 // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 153 // if let Object::FittedText(region, content) = self { 154 // let (x, y) = region.start.coords(cell_size); 155 // let width = region.width() * cell_size as f32; 156 // let height = region.height() * cell_size as f32; 157 158 // return Box::new( 159 // svg::node::element::Text::new(content.clone()) 160 // .set("x", x) 161 // .set("y", y) 162 // .set("") 163 // .set("font-size", format!("{}pt", 10.0)) 164 // .set("font-family", "sans-serif"), 165 // ); 166 // } 167 168 // panic!("Expected FittedText, got {:?}", self); 169 // } 170 171 fn render_rectangle(&self, cell_size: usize) -> svg::Node { 172 if let Shape::Rectangle(start, end) = self { 173 return svg::tag("rect").region((start, end), cell_size).into(); 174 } 175 176 panic!("Expected Rectangle, got {:?}", self); 177 } 178 179 fn render_polygon(&self, cell_size: usize) -> svg::Node { 180 if let Shape::Polygon(start, lines) = self { 181 let mut path = svg::Path::new(); 182 path.move_to(*start, cell_size); 183 for line in lines { 184 match line { 185 LineSegment::Straight(end) 186 | LineSegment::InwardCurve(end) 187 | LineSegment::OutwardCurve(end) => { 188 path.line_to(*end, cell_size); 189 } 190 }; 191 } 192 path.close(); 193 return path.node(); 194 } 195 196 panic!("Expected Polygon, got {:?}", self); 197 } 198 199 fn render_line(&self, cell_size: usize) -> svg::Node { 200 if let Shape::Line(start, end, width) = self { 201 return svg::tag("line") 202 .position_pair(*start, *end, cell_size) 203 .attr("stroke-width", *width) 204 .into(); 205 } 206 207 panic!("Expected Line, got {:?}", self); 208 } 209 210 fn render_curve(&self, cell_size: usize) -> svg::Node { 211 if let Shape::CurveOutward(start, end, stroke_width) 212 | Shape::CurveInward(start, end, stroke_width) = self 213 { 214 let inward = matches!(self, Shape::CurveInward(..)); 215 216 let (start_x, start_y) = start.coords(cell_size); 217 let (end_x, end_y) = end.coords(cell_size); 218 219 let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 220 let start_from_midpoint = 221 (start_x - midpoint.0, start_y - midpoint.1); 222 let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 223 224 let control = { 225 let relative = (end_x - start_x, end_y - start_y); 226 if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 227 && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 228 { 229 if inward { 230 ( 231 midpoint.0 + relative.0.abs() / 2.0, 232 midpoint.1 - relative.1.abs() / 2.0, 233 ) 234 } else { 235 ( 236 midpoint.0 - relative.0.abs() / 2.0, 237 midpoint.1 + relative.1.abs() / 2.0, 238 ) 239 } 240 // diagonal line is going like this: / 241 } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 242 && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 243 { 244 if inward { 245 ( 246 midpoint.0 - relative.0.abs() / 2.0, 247 midpoint.1 - relative.1.abs() / 2.0, 248 ) 249 } else { 250 ( 251 midpoint.0 + relative.0.abs() / 2.0, 252 midpoint.1 + relative.1.abs() / 2.0, 253 ) 254 } 255 // line is horizontal 256 } else if start_y == end_y { 257 ( 258 midpoint.0, 259 midpoint.1 260 + (if inward { -1.0 } else { 1.0 }) 261 * relative.0.abs() 262 / 2.0, 263 ) 264 // line is vertical 265 } else if start_x == end_x { 266 ( 267 midpoint.0 268 + (if inward { -1.0 } else { 1.0 }) 269 * relative.1.abs() 270 / 2.0, 271 midpoint.1, 272 ) 273 } else { 274 unreachable!() 275 } 276 }; 277 278 let mut path = svg::Path::new(); 279 path.move_to(*start, cell_size); 280 path.quadratic_curve_to(control, *end, cell_size); 281 return path 282 .element() 283 .attr("stroke-width", format!("{stroke_width}")) 284 .into(); 285 } 286 287 panic!("Expected Curve, got {:?}", self); 288 } 289 290 fn render_circle( 291 &self, 292 cell_size: usize, 293 object_sizes: ObjectSizes, 294 ) -> svg::Node { 295 let center = match self { 296 Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), 297 Shape::BigCircle(at) | Shape::SmallCircle(at) => { 298 at.as_centered().coords(cell_size) 299 } 300 301 _ => panic!( 302 "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 303 self 304 ), 305 }; 306 307 let radius = match self { 308 Shape::BigDot(_) => object_sizes.small_circle_radius, 309 Shape::Dot(_) => object_sizes.dot_radius, 310 Shape::BigCircle(_) => cell_size as f32 / 2.0, 311 Shape::SmallCircle(_) => object_sizes.small_circle_radius, 312 _ => unreachable!(), 313 }; 314 315 return svg::tag("circle") 316 .attr("cx", center.0) 317 .attr("cy", center.1) 318 .attr("r", radius) 319 .into(); 320 } 321}