This repository has no description
0

Configure Feed

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

🎨 Format code

+503 -505
+1 -1
src/geometry/region.rs
··· 1 - use crate::{Shape, Point}; 1 + use crate::{Point, Shape}; 2 2 use anyhow::{Error, Result, anyhow, format_err}; 3 3 #[cfg(feature = "web")] 4 4 use wasm_bindgen::prelude::*;
+219 -219
src/graphics/shapes.rs
··· 1 - use self::Shape::*; 2 - use anyhow::anyhow; 3 - use crate::{Point, Region, Containable}; 4 - 5 - #[derive(Debug, Clone, PartialEq, Eq)] 6 - pub enum LineSegment { 7 - Straight(Point), 8 - InwardCurve(Point), 9 - OutwardCurve(Point), 10 - } 11 - 12 - #[derive(Debug, Clone)] 13 - pub enum Shape { 14 - Polygon(Point, Vec<LineSegment>), 15 - Line(Point, Point, f32), 16 - CurveOutward(Point, Point, f32), 17 - CurveInward(Point, Point, f32), 18 - SmallCircle(Point), 19 - BigDot(Point), 20 - Dot(Point), 21 - BigCircle(Point), 22 - Text(Point, String, f32), 23 - CenteredText(Point, String, f32), 24 - // FittedText(Region, String), 25 - Rectangle(Point, Point), 26 - Image(Region, String), 27 - RawSVG(String), 28 - // Tiling(Region, Box<Object>), 29 - } 30 - 31 - impl Shape { 32 - pub fn translate(&mut self, dx: i32, dy: i32) { 33 - match self { 34 - Polygon(start, lines) => { 35 - start.translate(dx, dy); 36 - for line in lines { 37 - match line { 38 - LineSegment::InwardCurve(anchor) 39 - | LineSegment::OutwardCurve(anchor) 40 - | LineSegment::Straight(anchor) => { 41 - anchor.translate(dx, dy) 42 - } 43 - } 44 - } 45 - } 46 - Line(start, end, _) 47 - | CurveInward(start, end, _) 48 - | CurveOutward(start, end, _) 49 - | Rectangle(start, end) => { 50 - start.translate(dx, dy); 51 - end.translate(dx, dy); 52 - } 53 - Text(anchor, _, _) 54 - | CenteredText(anchor, ..) 55 - | Dot(anchor) 56 - | BigDot(anchor) => anchor.translate(dx, dy), 57 - BigCircle(center) | SmallCircle(center) => center.translate(dx, dy), 58 - Image(region, ..) => region.translate(dx, dy), 59 - RawSVG(_) => { 60 - unimplemented!() 61 - } 62 - } 63 - } 64 - 65 - pub fn translate_with(&mut self, delta: (i32, i32)) { 66 - self.translate(delta.0, delta.1) 67 - } 68 - 69 - pub fn teleport(&mut self, x: i32, y: i32) { 70 - let (current_x, current_y) = self.region().start.xy::<i32>(); 71 - let delta_x = x - current_x; 72 - let delta_y = y - current_y; 73 - self.translate(delta_x, delta_y); 74 - } 75 - 76 - pub fn teleport_with(&mut self, position: (i32, i32)) { 77 - self.teleport(position.0, position.1) 78 - } 79 - 80 - pub fn region(&self) -> Region { 81 - match self { 82 - Polygon(start, lines) => { 83 - let mut region: Region = (start, start).into(); 84 - for line in lines { 85 - match line { 86 - LineSegment::InwardCurve(anchor) 87 - | LineSegment::OutwardCurve(anchor) 88 - | LineSegment::Straight(anchor) => { 89 - // println!( 90 - // "extending region {} with {}", 91 - // region, 92 - // Region::from((start, anchor)) 93 - // ); 94 - region = *region.max(&(start, anchor).into()) 95 - } 96 - } 97 - } 98 - // println!("region for {:?} -> {}", self, region); 99 - region 100 - } 101 - Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => { 102 - let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x()); 103 - 104 - let region = Region::new( 105 - (x1.min(x2), y1.min(y2)), 106 - (x1.max(x2), y1.max(y2)), 107 - ) 108 - .map_err(|e| { 109 - anyhow!("Could not construct region of {self:?}: {e:?}") 110 - }) 111 - .unwrap(); 112 - 113 - region.enlarged( 114 - if region.width() > 1 { -1 } else { 0 }, 115 - if region.height() > 1 { -1 } else { 0 }, 116 - ) 117 - } 118 - Rectangle(start, end) => { 119 - Region::new(*start, *end).unwrap().enlarged(-1, -1) 120 - } 121 - Text(anchor, _, _) 122 - | CenteredText(anchor, ..) 123 - | Dot(anchor) 124 - | BigDot(anchor) 125 - | SmallCircle(anchor) => anchor.region(), 126 - BigCircle(center) => center.region(), 127 - Image(region, ..) => *region, 128 - RawSVG(_) => { 129 - unimplemented!() 130 - } 131 - } 132 - } 133 - 134 - pub fn fillable(&self) -> bool { 135 - !matches!(self, Line(..) | CurveInward(..) | CurveOutward(..)) 136 - } 137 - 138 - pub fn hatchable(&self) -> bool { 139 - self.fillable() && !matches!(self, Dot(..)) 140 - } 141 - 142 - pub fn point_is_on_line(self, point: Point) -> bool { 143 - match (&self, point) { 144 - (Line(s, e, _), Point::Corner(x, y)) => { 145 - if !self.region().contains(&point) { 146 - return false; 147 - } 148 - 149 - let (sx, sy) = s.xy::<f32>(); 150 - let (ex, ey) = e.xy::<f32>(); 151 - 152 - let m = (ey - sy) / (ex - sx); 153 - let p = sy - m * sx; 154 - 155 - (m * x as f32 + p) as usize == y 156 - } 157 - (Line(..), _) => panic!("Point type not supported"), 158 - _ => panic!("{self:?} is not a line object"), 159 - } 160 - } 161 - 162 - // 163 - // ```rs 164 - // use shapemaker::{Line, Point::Corner}; 165 - // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0); 166 - // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 167 - // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 168 - // ``` 169 - pub fn intersects_with(&self, line: Shape) -> bool { 170 - match (self, &line) { 171 - (&Line(s1, e1, _), &Line(s2, e2, _)) => { 172 - let (dx1, dy1) = e1 - s1; 173 - let (dx2, dy2) = e2 - s2; 174 - let (dx3, dy3) = s2 - s1; 175 - 176 - let det = dx1 * dy2 - dy1 * dx2; 177 - 178 - let det1 = dx1 * dy3 - dx3 * dy1; 179 - let det2 = dx2 * dy3 - dx3 * dy2; 180 - 181 - if det == 0 { 182 - if det1 != 0 || det2 != 0 { 183 - return false; 184 - } 185 - 186 - let (x1, y1) = s1.xy::<isize>(); 187 - let (x2, y2) = e1.xy::<isize>(); 188 - let (x3, y3) = s2.xy::<isize>(); 189 - 190 - if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 191 - return true; 192 - } 193 - 194 - if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 195 - return true; 196 - } 197 - 198 - return false; 199 - } 200 - 201 - let frac_less_than_one = |num: isize, den: isize| { 202 - if num.signum() != den.signum() { 203 - return false; 204 - } 205 - 206 - num.abs() <= den.abs() 207 - }; 208 - 209 - frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 210 - } 211 - _ => { 212 - unimplemented!( 213 - "Intersection not implemented for {self:?} and {:?}", 214 - line.clone() 215 - ) 216 - } 217 - } 218 - } 219 - } 1 + use self::Shape::*; 2 + use crate::{Containable, Point, Region}; 3 + use anyhow::anyhow; 4 + 5 + #[derive(Debug, Clone, PartialEq, Eq)] 6 + pub enum LineSegment { 7 + Straight(Point), 8 + InwardCurve(Point), 9 + OutwardCurve(Point), 10 + } 11 + 12 + #[derive(Debug, Clone)] 13 + pub enum Shape { 14 + Polygon(Point, Vec<LineSegment>), 15 + Line(Point, Point, f32), 16 + CurveOutward(Point, Point, f32), 17 + CurveInward(Point, Point, f32), 18 + SmallCircle(Point), 19 + BigDot(Point), 20 + Dot(Point), 21 + BigCircle(Point), 22 + Text(Point, String, f32), 23 + CenteredText(Point, String, f32), 24 + // FittedText(Region, String), 25 + Rectangle(Point, Point), 26 + Image(Region, String), 27 + RawSVG(String), 28 + // Tiling(Region, Box<Object>), 29 + } 30 + 31 + impl Shape { 32 + pub fn translate(&mut self, dx: i32, dy: i32) { 33 + match self { 34 + Polygon(start, lines) => { 35 + start.translate(dx, dy); 36 + for line in lines { 37 + match line { 38 + LineSegment::InwardCurve(anchor) 39 + | LineSegment::OutwardCurve(anchor) 40 + | LineSegment::Straight(anchor) => { 41 + anchor.translate(dx, dy) 42 + } 43 + } 44 + } 45 + } 46 + Line(start, end, _) 47 + | CurveInward(start, end, _) 48 + | CurveOutward(start, end, _) 49 + | Rectangle(start, end) => { 50 + start.translate(dx, dy); 51 + end.translate(dx, dy); 52 + } 53 + Text(anchor, _, _) 54 + | CenteredText(anchor, ..) 55 + | Dot(anchor) 56 + | BigDot(anchor) => anchor.translate(dx, dy), 57 + BigCircle(center) | SmallCircle(center) => center.translate(dx, dy), 58 + Image(region, ..) => region.translate(dx, dy), 59 + RawSVG(_) => { 60 + unimplemented!() 61 + } 62 + } 63 + } 64 + 65 + pub fn translate_with(&mut self, delta: (i32, i32)) { 66 + self.translate(delta.0, delta.1) 67 + } 68 + 69 + pub fn teleport(&mut self, x: i32, y: i32) { 70 + let (current_x, current_y) = self.region().start.xy::<i32>(); 71 + let delta_x = x - current_x; 72 + let delta_y = y - current_y; 73 + self.translate(delta_x, delta_y); 74 + } 75 + 76 + pub fn teleport_with(&mut self, position: (i32, i32)) { 77 + self.teleport(position.0, position.1) 78 + } 79 + 80 + pub fn region(&self) -> Region { 81 + match self { 82 + Polygon(start, lines) => { 83 + let mut region: Region = (start, start).into(); 84 + for line in lines { 85 + match line { 86 + LineSegment::InwardCurve(anchor) 87 + | LineSegment::OutwardCurve(anchor) 88 + | LineSegment::Straight(anchor) => { 89 + // println!( 90 + // "extending region {} with {}", 91 + // region, 92 + // Region::from((start, anchor)) 93 + // ); 94 + region = *region.max(&(start, anchor).into()) 95 + } 96 + } 97 + } 98 + // println!("region for {:?} -> {}", self, region); 99 + region 100 + } 101 + Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => { 102 + let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x()); 103 + 104 + let region = Region::new( 105 + (x1.min(x2), y1.min(y2)), 106 + (x1.max(x2), y1.max(y2)), 107 + ) 108 + .map_err(|e| { 109 + anyhow!("Could not construct region of {self:?}: {e:?}") 110 + }) 111 + .unwrap(); 112 + 113 + region.enlarged( 114 + if region.width() > 1 { -1 } else { 0 }, 115 + if region.height() > 1 { -1 } else { 0 }, 116 + ) 117 + } 118 + Rectangle(start, end) => { 119 + Region::new(*start, *end).unwrap().enlarged(-1, -1) 120 + } 121 + Text(anchor, _, _) 122 + | CenteredText(anchor, ..) 123 + | Dot(anchor) 124 + | BigDot(anchor) 125 + | SmallCircle(anchor) => anchor.region(), 126 + BigCircle(center) => center.region(), 127 + Image(region, ..) => *region, 128 + RawSVG(_) => { 129 + unimplemented!() 130 + } 131 + } 132 + } 133 + 134 + pub fn fillable(&self) -> bool { 135 + !matches!(self, Line(..) | CurveInward(..) | CurveOutward(..)) 136 + } 137 + 138 + pub fn hatchable(&self) -> bool { 139 + self.fillable() && !matches!(self, Dot(..)) 140 + } 141 + 142 + pub fn point_is_on_line(self, point: Point) -> bool { 143 + match (&self, point) { 144 + (Line(s, e, _), Point::Corner(x, y)) => { 145 + if !self.region().contains(&point) { 146 + return false; 147 + } 148 + 149 + let (sx, sy) = s.xy::<f32>(); 150 + let (ex, ey) = e.xy::<f32>(); 151 + 152 + let m = (ey - sy) / (ex - sx); 153 + let p = sy - m * sx; 154 + 155 + (m * x as f32 + p) as usize == y 156 + } 157 + (Line(..), _) => panic!("Point type not supported"), 158 + _ => panic!("{self:?} is not a line object"), 159 + } 160 + } 161 + 162 + // 163 + // ```rs 164 + // use shapemaker::{Line, Point::Corner}; 165 + // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0); 166 + // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 167 + // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 168 + // ``` 169 + pub fn intersects_with(&self, line: Shape) -> bool { 170 + match (self, &line) { 171 + (&Line(s1, e1, _), &Line(s2, e2, _)) => { 172 + let (dx1, dy1) = e1 - s1; 173 + let (dx2, dy2) = e2 - s2; 174 + let (dx3, dy3) = s2 - s1; 175 + 176 + let det = dx1 * dy2 - dy1 * dx2; 177 + 178 + let det1 = dx1 * dy3 - dx3 * dy1; 179 + let det2 = dx2 * dy3 - dx3 * dy2; 180 + 181 + if det == 0 { 182 + if det1 != 0 || det2 != 0 { 183 + return false; 184 + } 185 + 186 + let (x1, y1) = s1.xy::<isize>(); 187 + let (x2, y2) = e1.xy::<isize>(); 188 + let (x3, y3) = s2.xy::<isize>(); 189 + 190 + if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 191 + return true; 192 + } 193 + 194 + if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 195 + return true; 196 + } 197 + 198 + return false; 199 + } 200 + 201 + let frac_less_than_one = |num: isize, den: isize| { 202 + if num.signum() != den.signum() { 203 + return false; 204 + } 205 + 206 + num.abs() <= den.abs() 207 + }; 208 + 209 + frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 210 + } 211 + _ => { 212 + unimplemented!( 213 + "Intersection not implemented for {self:?} and {:?}", 214 + line.clone() 215 + ) 216 + } 217 + } 218 + } 219 + }
+1 -1
src/random/canvas.rs
··· 1 - use crate::{Canvas, Object, Fill, Layer, Shape, Region}; 1 + use crate::{Canvas, Fill, Layer, Object, Region, Shape}; 2 2 use rand::{Rng, distr::uniform::SampleRange}; 3 3 use std::collections::HashMap; 4 4
+1 -1
src/random/objects.rs
··· 1 1 use rand::{Rng, distr::uniform::SampleRange}; 2 2 3 - use crate::{Object, LineSegment, Point, Region, Shape}; 3 + use crate::{LineSegment, Object, Point, Region, Shape}; 4 4 5 5 impl Shape { 6 6 pub fn random_starting_at<R: rand::Rng>(
+1 -1
src/rendering/mod.rs
··· 3 3 pub mod filter; 4 4 pub mod fonts; 5 5 pub mod layer; 6 - pub mod shapes; 7 6 pub mod objects; 8 7 pub mod rasterization; 9 8 pub mod renderable; 9 + pub mod shapes; 10 10 pub mod svg; 11 11 pub mod transform; 12 12
+280 -282
src/rendering/shapes.rs
··· 1 - use measure_time::debug_time; 2 - 3 - use crate::{LineSegment, Shape, graphics::objects::ObjectSizes}; 4 - 5 - use super::{ 6 - renderable::SVGRenderable, svg, 7 - }; 8 - 9 - impl SVGRenderable for Shape { 10 - fn render_to_svg( 11 - &self, 12 - _colormap: crate::ColorMapping, 13 - cell_size: usize, 14 - object_sizes: crate::graphics::objects::ObjectSizes, 15 - id: &str, 16 - ) -> anyhow::Result<svg::Node> { 17 - debug_time!("render_to_svg/object"); 18 - let rendered = match self { 19 - Shape::Text(..) | Shape::CenteredText(..) => { 20 - self.render_text(cell_size) 21 - } 22 - Shape::Rectangle(..) => self.render_rectangle(cell_size), 23 - Shape::Polygon(..) => self.render_polygon(cell_size), 24 - Shape::Line(..) => self.render_line(cell_size), 25 - Shape::CurveInward(..) | Shape::CurveOutward(..) => { 26 - self.render_curve(cell_size) 27 - } 28 - Shape::BigDot(..) 29 - | Shape::Dot(..) 30 - | Shape::BigCircle(..) 31 - | Shape::SmallCircle(..) => { 32 - self.render_circle(cell_size, object_sizes) 33 - } 34 - Shape::Image(..) => self.render_image(cell_size), 35 - Shape::RawSVG(..) => self.render_raw_svg(), 36 - }; 37 - 38 - Ok(match rendered { 39 - svg::Node::Element(el) => el.dataset("object", id).into(), 40 - svg::Node::SVG(svg) => { 41 - if svg.trim().starts_with("<") { 42 - let (before, after) = 43 - svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 44 - svg::Node::SVG(format!( 45 - r#"{before} data-object="{id}" {after}"# 46 - )) 47 - } else { 48 - eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 49 - svg::Node::SVG(svg) 50 - } 51 - } 52 - _ => { 53 - panic!("Expected Element or SVG, got {:?}", rendered); 54 - } 55 - }) 56 - } 57 - } 58 - 59 - impl Shape { 60 - fn render_image(&self, cell_size: usize) -> svg::Node { 61 - if let Shape::Image(region, path) = self { 62 - return svg::tag("image") 63 - .coords(region.start.coords(cell_size)) 64 - .attr("width", region.width() * cell_size) 65 - .attr("height", region.height() * cell_size) 66 - .attr("href", path.clone()) 67 - .into(); 68 - } 69 - 70 - panic!("Expected Image, got {:?}", self); 71 - } 72 - 73 - fn render_raw_svg(&self) -> svg::Node { 74 - if let Shape::RawSVG(svg) = self { 75 - return svg::Node::SVG(svg.clone()); 76 - } 77 - 78 - panic!("Expected RawSVG, got {:?}", self); 79 - } 80 - 81 - fn render_text(&self, cell_size: usize) -> svg::Node { 82 - match self { 83 - Shape::Text(position, content, font_size) 84 - | Shape::CenteredText(position, content, font_size) => { 85 - let centered = matches!(self, Shape::CenteredText(..)); 86 - 87 - svg::tag("text") 88 - .coords( 89 - if centered { 90 - position.as_centered() 91 - } else { 92 - position.as_corner() 93 - } 94 - .coords(cell_size), 95 - ) 96 - .attr("font-size", format!("{}pt", font_size)) 97 - .attr("font-family", "Inconsolata") 98 - .attr( 99 - "dominant-baseline", 100 - if centered { "middle" } else { "hanging" }, 101 - ) 102 - .attr( 103 - "text-anchor", 104 - if centered { "middle" } else { "start" }, 105 - ) 106 - .wrapping(vec![svg::Node::Text(content.to_string())]) 107 - .into() 108 - } 109 - _ => panic!("Expected Text, got {:?}", self), 110 - } 111 - } 112 - 113 - // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 114 - // if let Object::FittedText(region, content) = self { 115 - // let (x, y) = region.start.coords(cell_size); 116 - // let width = region.width() * cell_size as f32; 117 - // let height = region.height() * cell_size as f32; 118 - 119 - // return Box::new( 120 - // svg::node::element::Text::new(content.clone()) 121 - // .set("x", x) 122 - // .set("y", y) 123 - // .set("") 124 - // .set("font-size", format!("{}pt", 10.0)) 125 - // .set("font-family", "sans-serif"), 126 - // ); 127 - // } 128 - 129 - // panic!("Expected FittedText, got {:?}", self); 130 - // } 131 - 132 - fn render_rectangle(&self, cell_size: usize) -> svg::Node { 133 - if let Shape::Rectangle(start, end) = self { 134 - return svg::tag("rect").region((start, end), cell_size).into(); 135 - } 136 - 137 - panic!("Expected Rectangle, got {:?}", self); 138 - } 139 - 140 - fn render_polygon(&self, cell_size: usize) -> svg::Node { 141 - if let Shape::Polygon(start, lines) = self { 142 - let mut path = svg::Path::new(); 143 - path.move_to(*start, cell_size); 144 - for line in lines { 145 - match line { 146 - LineSegment::Straight(end) 147 - | LineSegment::InwardCurve(end) 148 - | LineSegment::OutwardCurve(end) => { 149 - path.line_to(*end, cell_size); 150 - } 151 - }; 152 - } 153 - path.close(); 154 - return path.node(); 155 - } 156 - 157 - panic!("Expected Polygon, got {:?}", self); 158 - } 159 - 160 - fn render_line(&self, cell_size: usize) -> svg::Node { 161 - if let Shape::Line(start, end, width) = self { 162 - return svg::tag("line") 163 - .position_pair(*start, *end, cell_size) 164 - .attr("stroke-width", *width) 165 - .into(); 166 - } 167 - 168 - panic!("Expected Line, got {:?}", self); 169 - } 170 - 171 - fn render_curve(&self, cell_size: usize) -> svg::Node { 172 - if let Shape::CurveOutward(start, end, stroke_width) 173 - | Shape::CurveInward(start, end, stroke_width) = self 174 - { 175 - let inward = matches!(self, Shape::CurveInward(..)); 176 - 177 - let (start_x, start_y) = start.coords(cell_size); 178 - let (end_x, end_y) = end.coords(cell_size); 179 - 180 - let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 181 - let start_from_midpoint = 182 - (start_x - midpoint.0, start_y - midpoint.1); 183 - let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 184 - 185 - let control = { 186 - let relative = (end_x - start_x, end_y - start_y); 187 - if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 188 - && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 189 - { 190 - if inward { 191 - ( 192 - midpoint.0 + relative.0.abs() / 2.0, 193 - midpoint.1 - relative.1.abs() / 2.0, 194 - ) 195 - } else { 196 - ( 197 - midpoint.0 - relative.0.abs() / 2.0, 198 - midpoint.1 + relative.1.abs() / 2.0, 199 - ) 200 - } 201 - // diagonal line is going like this: / 202 - } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 203 - && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 204 - { 205 - if inward { 206 - ( 207 - midpoint.0 - relative.0.abs() / 2.0, 208 - midpoint.1 - relative.1.abs() / 2.0, 209 - ) 210 - } else { 211 - ( 212 - midpoint.0 + relative.0.abs() / 2.0, 213 - midpoint.1 + relative.1.abs() / 2.0, 214 - ) 215 - } 216 - // line is horizontal 217 - } else if start_y == end_y { 218 - ( 219 - midpoint.0, 220 - midpoint.1 221 - + (if inward { -1.0 } else { 1.0 }) 222 - * relative.0.abs() 223 - / 2.0, 224 - ) 225 - // line is vertical 226 - } else if start_x == end_x { 227 - ( 228 - midpoint.0 229 - + (if inward { -1.0 } else { 1.0 }) 230 - * relative.1.abs() 231 - / 2.0, 232 - midpoint.1, 233 - ) 234 - } else { 235 - unreachable!() 236 - } 237 - }; 238 - 239 - let mut path = svg::Path::new(); 240 - path.move_to(*start, cell_size); 241 - path.quadratic_curve_to(control, *end, cell_size); 242 - return path 243 - .element() 244 - .attr("stroke-width", format!("{stroke_width}")) 245 - .into(); 246 - } 247 - 248 - panic!("Expected Curve, got {:?}", self); 249 - } 250 - 251 - fn render_circle( 252 - &self, 253 - cell_size: usize, 254 - object_sizes: ObjectSizes, 255 - ) -> svg::Node { 256 - let center = match self { 257 - Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), 258 - Shape::BigCircle(at) | Shape::SmallCircle(at) => { 259 - at.as_centered().coords(cell_size) 260 - } 261 - 262 - _ => panic!( 263 - "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 264 - self 265 - ), 266 - }; 267 - 268 - let radius = match self { 269 - Shape::BigDot(_) => object_sizes.small_circle_radius, 270 - Shape::Dot(_) => object_sizes.dot_radius, 271 - Shape::BigCircle(_) => cell_size as f32 / 2.0, 272 - Shape::SmallCircle(_) => object_sizes.small_circle_radius, 273 - _ => unreachable!(), 274 - }; 275 - 276 - return svg::tag("circle") 277 - .attr("cx", center.0) 278 - .attr("cy", center.1) 279 - .attr("r", radius) 280 - .into(); 281 - } 282 - } 1 + use measure_time::debug_time; 2 + 3 + use crate::{LineSegment, Shape, graphics::objects::ObjectSizes}; 4 + 5 + use super::{renderable::SVGRenderable, svg}; 6 + 7 + impl SVGRenderable for Shape { 8 + fn render_to_svg( 9 + &self, 10 + _colormap: crate::ColorMapping, 11 + cell_size: usize, 12 + object_sizes: crate::graphics::objects::ObjectSizes, 13 + id: &str, 14 + ) -> anyhow::Result<svg::Node> { 15 + debug_time!("render_to_svg/object"); 16 + let rendered = match self { 17 + Shape::Text(..) | Shape::CenteredText(..) => { 18 + self.render_text(cell_size) 19 + } 20 + Shape::Rectangle(..) => self.render_rectangle(cell_size), 21 + Shape::Polygon(..) => self.render_polygon(cell_size), 22 + Shape::Line(..) => self.render_line(cell_size), 23 + Shape::CurveInward(..) | Shape::CurveOutward(..) => { 24 + self.render_curve(cell_size) 25 + } 26 + Shape::BigDot(..) 27 + | Shape::Dot(..) 28 + | Shape::BigCircle(..) 29 + | Shape::SmallCircle(..) => { 30 + self.render_circle(cell_size, object_sizes) 31 + } 32 + Shape::Image(..) => self.render_image(cell_size), 33 + Shape::RawSVG(..) => self.render_raw_svg(), 34 + }; 35 + 36 + Ok(match rendered { 37 + svg::Node::Element(el) => el.dataset("object", id).into(), 38 + svg::Node::SVG(svg) => { 39 + if svg.trim().starts_with("<") { 40 + let (before, after) = 41 + svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 42 + svg::Node::SVG(format!( 43 + r#"{before} data-object="{id}" {after}"# 44 + )) 45 + } else { 46 + eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 47 + svg::Node::SVG(svg) 48 + } 49 + } 50 + _ => { 51 + panic!("Expected Element or SVG, got {:?}", rendered); 52 + } 53 + }) 54 + } 55 + } 56 + 57 + impl Shape { 58 + fn render_image(&self, cell_size: usize) -> svg::Node { 59 + if let Shape::Image(region, path) = self { 60 + return svg::tag("image") 61 + .coords(region.start.coords(cell_size)) 62 + .attr("width", region.width() * cell_size) 63 + .attr("height", region.height() * cell_size) 64 + .attr("href", path.clone()) 65 + .into(); 66 + } 67 + 68 + panic!("Expected Image, got {:?}", self); 69 + } 70 + 71 + fn render_raw_svg(&self) -> svg::Node { 72 + if let Shape::RawSVG(svg) = self { 73 + return svg::Node::SVG(svg.clone()); 74 + } 75 + 76 + panic!("Expected RawSVG, got {:?}", self); 77 + } 78 + 79 + fn render_text(&self, cell_size: usize) -> svg::Node { 80 + match self { 81 + Shape::Text(position, content, font_size) 82 + | Shape::CenteredText(position, content, font_size) => { 83 + let centered = matches!(self, Shape::CenteredText(..)); 84 + 85 + svg::tag("text") 86 + .coords( 87 + if centered { 88 + position.as_centered() 89 + } else { 90 + position.as_corner() 91 + } 92 + .coords(cell_size), 93 + ) 94 + .attr("font-size", format!("{}pt", font_size)) 95 + .attr("font-family", "Inconsolata") 96 + .attr( 97 + "dominant-baseline", 98 + if centered { "middle" } else { "hanging" }, 99 + ) 100 + .attr( 101 + "text-anchor", 102 + if centered { "middle" } else { "start" }, 103 + ) 104 + .wrapping(vec![svg::Node::Text(content.to_string())]) 105 + .into() 106 + } 107 + _ => panic!("Expected Text, got {:?}", self), 108 + } 109 + } 110 + 111 + // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 112 + // if let Object::FittedText(region, content) = self { 113 + // let (x, y) = region.start.coords(cell_size); 114 + // let width = region.width() * cell_size as f32; 115 + // let height = region.height() * cell_size as f32; 116 + 117 + // return Box::new( 118 + // svg::node::element::Text::new(content.clone()) 119 + // .set("x", x) 120 + // .set("y", y) 121 + // .set("") 122 + // .set("font-size", format!("{}pt", 10.0)) 123 + // .set("font-family", "sans-serif"), 124 + // ); 125 + // } 126 + 127 + // panic!("Expected FittedText, got {:?}", self); 128 + // } 129 + 130 + fn render_rectangle(&self, cell_size: usize) -> svg::Node { 131 + if let Shape::Rectangle(start, end) = self { 132 + return svg::tag("rect").region((start, end), cell_size).into(); 133 + } 134 + 135 + panic!("Expected Rectangle, got {:?}", self); 136 + } 137 + 138 + fn render_polygon(&self, cell_size: usize) -> svg::Node { 139 + if let Shape::Polygon(start, lines) = self { 140 + let mut path = svg::Path::new(); 141 + path.move_to(*start, cell_size); 142 + for line in lines { 143 + match line { 144 + LineSegment::Straight(end) 145 + | LineSegment::InwardCurve(end) 146 + | LineSegment::OutwardCurve(end) => { 147 + path.line_to(*end, cell_size); 148 + } 149 + }; 150 + } 151 + path.close(); 152 + return path.node(); 153 + } 154 + 155 + panic!("Expected Polygon, got {:?}", self); 156 + } 157 + 158 + fn render_line(&self, cell_size: usize) -> svg::Node { 159 + if let Shape::Line(start, end, width) = self { 160 + return svg::tag("line") 161 + .position_pair(*start, *end, cell_size) 162 + .attr("stroke-width", *width) 163 + .into(); 164 + } 165 + 166 + panic!("Expected Line, got {:?}", self); 167 + } 168 + 169 + fn render_curve(&self, cell_size: usize) -> svg::Node { 170 + if let Shape::CurveOutward(start, end, stroke_width) 171 + | Shape::CurveInward(start, end, stroke_width) = self 172 + { 173 + let inward = matches!(self, Shape::CurveInward(..)); 174 + 175 + let (start_x, start_y) = start.coords(cell_size); 176 + let (end_x, end_y) = end.coords(cell_size); 177 + 178 + let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 179 + let start_from_midpoint = 180 + (start_x - midpoint.0, start_y - midpoint.1); 181 + let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 182 + 183 + let control = { 184 + let relative = (end_x - start_x, end_y - start_y); 185 + if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 186 + && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 187 + { 188 + if inward { 189 + ( 190 + midpoint.0 + relative.0.abs() / 2.0, 191 + midpoint.1 - relative.1.abs() / 2.0, 192 + ) 193 + } else { 194 + ( 195 + midpoint.0 - relative.0.abs() / 2.0, 196 + midpoint.1 + relative.1.abs() / 2.0, 197 + ) 198 + } 199 + // diagonal line is going like this: / 200 + } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 201 + && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 202 + { 203 + if inward { 204 + ( 205 + midpoint.0 - relative.0.abs() / 2.0, 206 + midpoint.1 - relative.1.abs() / 2.0, 207 + ) 208 + } else { 209 + ( 210 + midpoint.0 + relative.0.abs() / 2.0, 211 + midpoint.1 + relative.1.abs() / 2.0, 212 + ) 213 + } 214 + // line is horizontal 215 + } else if start_y == end_y { 216 + ( 217 + midpoint.0, 218 + midpoint.1 219 + + (if inward { -1.0 } else { 1.0 }) 220 + * relative.0.abs() 221 + / 2.0, 222 + ) 223 + // line is vertical 224 + } else if start_x == end_x { 225 + ( 226 + midpoint.0 227 + + (if inward { -1.0 } else { 1.0 }) 228 + * relative.1.abs() 229 + / 2.0, 230 + midpoint.1, 231 + ) 232 + } else { 233 + unreachable!() 234 + } 235 + }; 236 + 237 + let mut path = svg::Path::new(); 238 + path.move_to(*start, cell_size); 239 + path.quadratic_curve_to(control, *end, cell_size); 240 + return path 241 + .element() 242 + .attr("stroke-width", format!("{stroke_width}")) 243 + .into(); 244 + } 245 + 246 + panic!("Expected Curve, got {:?}", self); 247 + } 248 + 249 + fn render_circle( 250 + &self, 251 + cell_size: usize, 252 + object_sizes: ObjectSizes, 253 + ) -> svg::Node { 254 + let center = match self { 255 + Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), 256 + Shape::BigCircle(at) | Shape::SmallCircle(at) => { 257 + at.as_centered().coords(cell_size) 258 + } 259 + 260 + _ => panic!( 261 + "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 262 + self 263 + ), 264 + }; 265 + 266 + let radius = match self { 267 + Shape::BigDot(_) => object_sizes.small_circle_radius, 268 + Shape::Dot(_) => object_sizes.dot_radius, 269 + Shape::BigCircle(_) => cell_size as f32 / 2.0, 270 + Shape::SmallCircle(_) => object_sizes.small_circle_radius, 271 + _ => unreachable!(), 272 + }; 273 + 274 + return svg::tag("circle") 275 + .attr("cx", center.0) 276 + .attr("cy", center.1) 277 + .attr("r", radius) 278 + .into(); 279 + } 280 + }