This repository has no description
0

Configure Feed

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

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