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::BigDot(..) 100 | Object::Dot(..) 101 | Object::BigCircle(..) 102 | Object::SmallCircle(..) => { 103 self.render_circle(cell_size, object_sizes) 104 } 105 Object::Image(..) => self.render_image(cell_size), 106 Object::RawSVG(..) => self.render_raw_svg(), 107 }; 108 109 Ok(match rendered { 110 svg::Node::Element(el) => el.dataset("object", id).into(), 111 svg::Node::SVG(svg) => { 112 if svg.trim().starts_with("<") { 113 let (before, after) = 114 svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 115 svg::Node::SVG(format!( 116 r#"{before} data-object="{id}" {after}"# 117 )) 118 } else { 119 eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 120 svg::Node::SVG(svg) 121 } 122 } 123 _ => { 124 panic!("Expected Element or SVG, got {:?}", rendered); 125 } 126 }) 127 } 128} 129 130impl Object { 131 fn render_image(&self, cell_size: usize) -> svg::Node { 132 if let Object::Image(region, path) = self { 133 return svg::tag("image") 134 .coords(region.start.coords(cell_size)) 135 .attr("width", region.width() * cell_size) 136 .attr("height", region.height() * cell_size) 137 .attr("href", path.clone()) 138 .into(); 139 } 140 141 panic!("Expected Image, got {:?}", self); 142 } 143 144 fn render_raw_svg(&self) -> svg::Node { 145 if let Object::RawSVG(svg) = self { 146 return svg::Node::SVG(svg.clone()); 147 } 148 149 panic!("Expected RawSVG, got {:?}", self); 150 } 151 152 fn render_text(&self, cell_size: usize) -> svg::Node { 153 match self { 154 Object::Text(position, content, font_size) 155 | Object::CenteredText(position, content, font_size) => { 156 let centered = matches!(self, Object::CenteredText(..)); 157 158 svg::tag("text") 159 .coords( 160 if centered { 161 position.as_centered() 162 } else { 163 position.as_corner() 164 } 165 .coords(cell_size), 166 ) 167 .attr("font-size", format!("{}pt", font_size)) 168 .attr("font-family", "Inconsolata") 169 .attr( 170 "dominant-baseline", 171 if centered { "middle" } else { "hanging" }, 172 ) 173 .attr( 174 "text-anchor", 175 if centered { "middle" } else { "start" }, 176 ) 177 .wrapping(vec![svg::Node::Text(content.to_string())]) 178 .into() 179 } 180 _ => panic!("Expected Text, got {:?}", self), 181 } 182 } 183 184 // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 185 // if let Object::FittedText(region, content) = self { 186 // let (x, y) = region.start.coords(cell_size); 187 // let width = region.width() * cell_size as f32; 188 // let height = region.height() * cell_size as f32; 189 190 // return Box::new( 191 // svg::node::element::Text::new(content.clone()) 192 // .set("x", x) 193 // .set("y", y) 194 // .set("") 195 // .set("font-size", format!("{}pt", 10.0)) 196 // .set("font-family", "sans-serif"), 197 // ); 198 // } 199 200 // panic!("Expected FittedText, got {:?}", self); 201 // } 202 203 fn render_rectangle(&self, cell_size: usize) -> svg::Node { 204 if let Object::Rectangle(start, end) = self { 205 return svg::tag("rect").region((start, end), cell_size).into(); 206 } 207 208 panic!("Expected Rectangle, got {:?}", self); 209 } 210 211 fn render_polygon(&self, cell_size: usize) -> svg::Node { 212 if let Object::Polygon(start, lines) = self { 213 let mut path = svg::Path::new(); 214 path.move_to(*start, cell_size); 215 for line in lines { 216 match line { 217 LineSegment::Straight(end) 218 | LineSegment::InwardCurve(end) 219 | LineSegment::OutwardCurve(end) => { 220 path.line_to(*end, cell_size); 221 } 222 }; 223 } 224 path.close(); 225 return path.node(); 226 } 227 228 panic!("Expected Polygon, got {:?}", self); 229 } 230 231 fn render_line(&self, cell_size: usize) -> svg::Node { 232 if let Object::Line(start, end, width) = self { 233 return svg::tag("line") 234 .position_pair(*start, *end, cell_size) 235 .attr("stroke-width", *width) 236 .into(); 237 } 238 239 panic!("Expected Line, got {:?}", self); 240 } 241 242 fn render_curve(&self, cell_size: usize) -> svg::Node { 243 if let Object::CurveOutward(start, end, stroke_width) 244 | Object::CurveInward(start, end, stroke_width) = self 245 { 246 let inward = matches!(self, Object::CurveInward(..)); 247 248 let (start_x, start_y) = start.coords(cell_size); 249 let (end_x, end_y) = end.coords(cell_size); 250 251 let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 252 let start_from_midpoint = 253 (start_x - midpoint.0, start_y - midpoint.1); 254 let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 255 256 let control = { 257 let relative = (end_x - start_x, end_y - start_y); 258 if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 259 && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 260 { 261 if inward { 262 ( 263 midpoint.0 + relative.0.abs() / 2.0, 264 midpoint.1 - relative.1.abs() / 2.0, 265 ) 266 } else { 267 ( 268 midpoint.0 - relative.0.abs() / 2.0, 269 midpoint.1 + relative.1.abs() / 2.0, 270 ) 271 } 272 // diagonal line is going like this: / 273 } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 274 && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 275 { 276 if inward { 277 ( 278 midpoint.0 - relative.0.abs() / 2.0, 279 midpoint.1 - relative.1.abs() / 2.0, 280 ) 281 } else { 282 ( 283 midpoint.0 + relative.0.abs() / 2.0, 284 midpoint.1 + relative.1.abs() / 2.0, 285 ) 286 } 287 // line is horizontal 288 } else if start_y == end_y { 289 ( 290 midpoint.0, 291 midpoint.1 292 + (if inward { -1.0 } else { 1.0 }) 293 * relative.0.abs() 294 / 2.0, 295 ) 296 // line is vertical 297 } else if start_x == end_x { 298 ( 299 midpoint.0 300 + (if inward { -1.0 } else { 1.0 }) 301 * relative.1.abs() 302 / 2.0, 303 midpoint.1, 304 ) 305 } else { 306 unreachable!() 307 } 308 }; 309 310 let mut path = svg::Path::new(); 311 path.move_to(*start, cell_size); 312 path.quadratic_curve_to(control, *end, cell_size); 313 return path 314 .element() 315 .attr("stroke-width", format!("{stroke_width}")) 316 .into(); 317 } 318 319 panic!("Expected Curve, got {:?}", self); 320 } 321 322 fn render_circle( 323 &self, 324 cell_size: usize, 325 object_sizes: ObjectSizes, 326 ) -> svg::Node { 327 let center = match self { 328 Object::BigDot(at) | Object::Dot(at) => at.coords(cell_size), 329 Object::BigCircle(at) | Object::SmallCircle(at) => { 330 at.as_centered().coords(cell_size) 331 } 332 333 _ => panic!( 334 "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 335 self 336 ), 337 }; 338 339 let radius = match self { 340 Object::BigDot(_) => object_sizes.small_circle_radius, 341 Object::Dot(_) => object_sizes.dot_radius, 342 Object::BigCircle(_) => cell_size as f32 / 2.0, 343 Object::SmallCircle(_) => object_sizes.small_circle_radius, 344 _ => unreachable!(), 345 }; 346 347 return svg::tag("circle") 348 .attr("cx", center.0) 349 .attr("cy", center.1) 350 .attr("r", radius) 351 .into(); 352 } 353}