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