This repository has no description
0

Configure Feed

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

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::RawSVG(..) => self.render_raw_svg(), 34 }; 35 36 Ok(match rendered { 37 svg::Node::Element(el) => el.dataset("object", id).into(), 38 svg::Node::SVG(svg) => { 39 if svg.trim().starts_with("<") { 40 let (before, after) = 41 svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 42 svg::Node::SVG(format!( 43 r#"{before} data-object="{id}" {after}"# 44 )) 45 } else { 46 eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 47 svg::Node::SVG(svg) 48 } 49 } 50 _ => { 51 panic!("Expected Element or SVG, got {:?}", rendered); 52 } 53 }) 54 } 55} 56 57impl Shape { 58 fn render_image(&self, cell_size: usize) -> svg::Node { 59 if let Shape::Image(region, path) = self { 60 return svg::tag("image") 61 .coords(region.start.coords(cell_size)) 62 .attr("width", region.width() * cell_size) 63 .attr("height", region.height() * cell_size) 64 .attr("href", path.clone()) 65 .into(); 66 } 67 68 panic!("Expected Image, got {:?}", self); 69 } 70 71 fn render_raw_svg(&self) -> svg::Node { 72 if let Shape::RawSVG(svg) = self { 73 return svg::Node::SVG(svg.clone()); 74 } 75 76 panic!("Expected RawSVG, got {:?}", self); 77 } 78 79 fn render_text(&self, cell_size: usize) -> svg::Node { 80 match self { 81 Shape::Text(position, content, font_size) 82 | Shape::CenteredText(position, content, font_size) => { 83 let centered = matches!(self, Shape::CenteredText(..)); 84 85 svg::tag("text") 86 .coords( 87 if centered { 88 position.as_centered() 89 } else { 90 position.as_corner() 91 } 92 .coords(cell_size), 93 ) 94 .attr("font-size", format!("{}pt", font_size)) 95 .attr("font-family", "Inconsolata") 96 .attr( 97 "dominant-baseline", 98 if centered { "middle" } else { "hanging" }, 99 ) 100 .attr( 101 "text-anchor", 102 if centered { "middle" } else { "start" }, 103 ) 104 .wrapping(vec![svg::Node::Text(content.to_string())]) 105 .into() 106 } 107 _ => panic!("Expected Text, got {:?}", self), 108 } 109 } 110 111 // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 112 // if let Object::FittedText(region, content) = self { 113 // let (x, y) = region.start.coords(cell_size); 114 // let width = region.width() * cell_size as f32; 115 // let height = region.height() * cell_size as f32; 116 117 // return Box::new( 118 // svg::node::element::Text::new(content.clone()) 119 // .set("x", x) 120 // .set("y", y) 121 // .set("") 122 // .set("font-size", format!("{}pt", 10.0)) 123 // .set("font-family", "sans-serif"), 124 // ); 125 // } 126 127 // panic!("Expected FittedText, got {:?}", self); 128 // } 129 130 fn render_rectangle(&self, cell_size: usize) -> svg::Node { 131 if let Shape::Rectangle(start, end) = self { 132 return svg::tag("rect").region((start, end), cell_size).into(); 133 } 134 135 panic!("Expected Rectangle, got {:?}", self); 136 } 137 138 fn render_polygon(&self, cell_size: usize) -> svg::Node { 139 if let Shape::Polygon(start, lines) = self { 140 let mut path = svg::Path::new(); 141 path.move_to(*start, cell_size); 142 for line in lines { 143 match line { 144 LineSegment::Straight(end) 145 | LineSegment::InwardCurve(end) 146 | LineSegment::OutwardCurve(end) => { 147 path.line_to(*end, cell_size); 148 } 149 }; 150 } 151 path.close(); 152 return path.node(); 153 } 154 155 panic!("Expected Polygon, got {:?}", self); 156 } 157 158 fn render_line(&self, cell_size: usize) -> svg::Node { 159 if let Shape::Line(start, end, width) = self { 160 return svg::tag("line") 161 .position_pair(*start, *end, cell_size) 162 .attr("stroke-width", *width) 163 .into(); 164 } 165 166 panic!("Expected Line, got {:?}", self); 167 } 168 169 fn render_curve(&self, cell_size: usize) -> svg::Node { 170 if let Shape::CurveOutward(start, end, stroke_width) 171 | Shape::CurveInward(start, end, stroke_width) = self 172 { 173 let inward = matches!(self, Shape::CurveInward(..)); 174 175 let (start_x, start_y) = start.coords(cell_size); 176 let (end_x, end_y) = end.coords(cell_size); 177 178 let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 179 let start_from_midpoint = 180 (start_x - midpoint.0, start_y - midpoint.1); 181 let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 182 183 let control = { 184 let relative = (end_x - start_x, end_y - start_y); 185 if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 186 && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 187 { 188 if inward { 189 ( 190 midpoint.0 + relative.0.abs() / 2.0, 191 midpoint.1 - relative.1.abs() / 2.0, 192 ) 193 } else { 194 ( 195 midpoint.0 - relative.0.abs() / 2.0, 196 midpoint.1 + relative.1.abs() / 2.0, 197 ) 198 } 199 // diagonal line is going like this: / 200 } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 201 && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 202 { 203 if inward { 204 ( 205 midpoint.0 - relative.0.abs() / 2.0, 206 midpoint.1 - relative.1.abs() / 2.0, 207 ) 208 } else { 209 ( 210 midpoint.0 + relative.0.abs() / 2.0, 211 midpoint.1 + relative.1.abs() / 2.0, 212 ) 213 } 214 // line is horizontal 215 } else if start_y == end_y { 216 ( 217 midpoint.0, 218 midpoint.1 219 + (if inward { -1.0 } else { 1.0 }) 220 * relative.0.abs() 221 / 2.0, 222 ) 223 // line is vertical 224 } else if start_x == end_x { 225 ( 226 midpoint.0 227 + (if inward { -1.0 } else { 1.0 }) 228 * relative.1.abs() 229 / 2.0, 230 midpoint.1, 231 ) 232 } else { 233 unreachable!() 234 } 235 }; 236 237 let mut path = svg::Path::new(); 238 path.move_to(*start, cell_size); 239 path.quadratic_curve_to(control, *end, cell_size); 240 return path 241 .element() 242 .attr("stroke-width", format!("{stroke_width}")) 243 .into(); 244 } 245 246 panic!("Expected Curve, got {:?}", self); 247 } 248 249 fn render_circle( 250 &self, 251 cell_size: usize, 252 object_sizes: ObjectSizes, 253 ) -> svg::Node { 254 let center = match self { 255 Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), 256 Shape::BigCircle(at) | Shape::SmallCircle(at) => { 257 at.as_centered().coords(cell_size) 258 } 259 260 _ => panic!( 261 "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 262 self 263 ), 264 }; 265 266 let radius = match self { 267 Shape::BigDot(_) => object_sizes.small_circle_radius, 268 Shape::Dot(_) => object_sizes.dot_radius, 269 Shape::BigCircle(_) => cell_size as f32 / 2.0, 270 Shape::SmallCircle(_) => object_sizes.small_circle_radius, 271 _ => unreachable!(), 272 }; 273 274 return svg::tag("circle") 275 .attr("cx", center.0) 276 .attr("cy", center.1) 277 .attr("r", radius) 278 .into(); 279 } 280}