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