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, Point, graphics::objects::{LineSegment, ObjectSizes} 6}; 7 8use super::{ 9 CSSRenderable, SVGAttributesRenderable, renderable::SVGRenderable, svg, 10}; 11 12impl SVGRenderable for ColoredObject { 13 fn render_to_svg( 14 &self, 15 colormap: crate::ColorMapping, 16 cell_size: usize, 17 object_sizes: crate::graphics::objects::ObjectSizes, 18 id: &str, 19 ) -> anyhow::Result<svg::Node> { 20 debug_time!("render_to_svg/colored_object"); 21 let plain_obj = self.object.render_to_svg( 22 colormap.clone(), 23 cell_size, 24 object_sizes, 25 id, 26 )?; 27 28 let mut css = self 29 .fill 30 .render_to_css(&colormap.clone(), !self.object.fillable()); 31 32 let object_svg = if !self.transformations.is_empty() 33 || !self.filters.is_empty() 34 { 35 // transform-box is not supported by resvg yet 36 // css += "transform-box: fill-box; transform-origin: 50% 50%;"; 37 38 let (center_x, center_y) = 39 self.object.region().center_coords(cell_size); 40 41 css += &format!("transform-origin: {center_x}px {center_y}px;"); 42 43 css += self 44 .filters 45 .iter() 46 .map(|f| f.render_to_css_filled(&colormap)) 47 .join(" ") 48 .as_ref(); 49 50 svg::tag("g") 51 .dataset("object", id) 52 .with_attributes(self.transformations.render_to_svg_attributes( 53 colormap, 54 cell_size, 55 object_sizes, 56 id, 57 )?) 58 .wrapping(vec![plain_obj]) 59 .attr("style", &css) 60 .into() 61 } else { 62 match plain_obj { 63 svg::Node::Element(el) => el.attr("style", &css).into(), 64 _ => plain_obj, 65 } 66 }; 67 68 if let Some(region) = &self.clip_to { 69 Ok(svg::tag("g") 70 .attr("clip-path", region.clip_path_id()) 71 .child(object_svg) 72 .into()) 73 } else { 74 Ok(object_svg) 75 } 76 } 77} 78 79impl SVGRenderable for Object { 80 fn render_to_svg( 81 &self, 82 _colormap: crate::ColorMapping, 83 cell_size: usize, 84 object_sizes: crate::graphics::objects::ObjectSizes, 85 id: &str, 86 ) -> anyhow::Result<svg::Node> { 87 debug_time!("render_to_svg/object"); 88 let rendered = match self { 89 Object::Text(..) | Object::CenteredText(..) => { 90 self.render_text(cell_size) 91 } 92 Object::Rectangle(..) => self.render_rectangle(cell_size), 93 Object::Polygon(..) => self.render_polygon(cell_size), 94 Object::Line(..) => self.render_line(cell_size), 95 Object::CurveInward(..) | Object::CurveOutward(..) => { 96 self.render_curve(cell_size) 97 } 98 Object::BigDot(..) 99 | Object::Dot(..) 100 | Object::BigCircle(..) 101 | Object::SmallCircle(..) => { 102 self.render_circle(cell_size, object_sizes) 103 } 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_circle( 319 &self, 320 cell_size: usize, 321 object_sizes: ObjectSizes, 322 ) -> svg::Node { 323 let center = match self { 324 Object::BigDot(at) | Object::Dot(at) => at.coords(cell_size), 325 Object::BigCircle(at) | Object::SmallCircle(at) => { 326 at.center_coords(cell_size) 327 } 328 329 _ => panic!( 330 "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 331 self 332 ), 333 }; 334 335 let radius = match self { 336 Object::BigDot(_) => object_sizes.small_circle_radius, 337 Object::Dot(_) => object_sizes.dot_radius, 338 Object::BigCircle(_) => cell_size as f32 / 2.0, 339 Object::SmallCircle(_) => object_sizes.small_circle_radius, 340 _ => unreachable!(), 341 }; 342 343 return svg::tag("circle") 344 .attr("cx", center.0) 345 .attr("cy", center.1) 346 .attr("r", radius) 347 .into(); 348 } 349}