diff --git a/Cargo.toml b/Cargo.toml index f8127df..4ec4c6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["command-line-utilities", "graphics", "multimedia::images", "rende interp = { version = "1.0", features = ["interp_array"] } itertools = "0.10" kle-serial = "0.2" +kurbo = "0.9" log = "0.4" rgb = { version = "0.8", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/src/drawing/svg/font.rs b/src/drawing/svg/font.rs index fc7aa1c..2ac6497 100644 --- a/src/drawing/svg/font.rs +++ b/src/drawing/svg/font.rs @@ -1,10 +1,10 @@ use itertools::Itertools; +use kurbo::{Affine, BezPath, Shape, Vec2}; use svg::node::element::Path as SvgPath; use crate::font::Font; use crate::key::Key; use crate::profile::Profile; -use crate::utils::{Path, Vec2}; pub trait Draw { fn draw_legends(&self, profile: &Profile, key: &Key) -> Vec; @@ -21,18 +21,20 @@ impl Draw for Font { let mut path = self.text_path(&legend.text); let scale = profile.text_height.get(legend.size) / self.cap_height; - path.scale(Vec2::from(scale)); + path.apply_affine(Affine::scale(scale)); let align = Vec2::new( (j as f64) / ((key.legends.len() - 1) as f64), (i as f64) / ((key.legends[0].len() - 1) as f64), ); let margin = profile.text_margin.get(legend.size); - let point = margin.position() + (margin.size() - path.bounds.size()) * align; - path.translate(point - path.bounds.position()); + let bounds = path.bounding_box(); + let size = margin.size() - bounds.size(); + let point = margin.origin() + (align.x * size.width, align.y * size.height); + path.apply_affine(Affine::translate(point - bounds.origin())); let svg_path = SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", legend.color.to_string()) .set("stroke", "none"); @@ -45,8 +47,8 @@ impl Draw for Font { } impl Font { - fn text_path(&self, text: &str) -> Path { - let mut path = Path::new(); + fn text_path(&self, text: &str) -> BezPath { + let mut path = BezPath::new(); let first = if let Some(first) = text.chars().next() { self.glyphs.get(&first).unwrap_or(&self.notdef) @@ -54,7 +56,7 @@ impl Font { return path; }; - path.append(first.path.clone()); + path.extend(first.path.clone()); text.chars() .map(|char| self.glyphs.get(&char).unwrap_or(&self.notdef)) .tuple_windows() @@ -68,8 +70,8 @@ impl Font { }) .for_each(|(pos, glyph)| { let mut glyph = glyph.path.clone(); - glyph.translate(Vec2::new(pos, 0.)); - path.append(glyph); + glyph.apply_affine(Affine::translate((pos, 0.))); + path.extend(glyph); }); path } @@ -77,7 +79,7 @@ impl Font { #[cfg(test)] mod tests { - use crate::utils::PathSegment; + use kurbo::PathEl; use super::*; @@ -98,14 +100,13 @@ mod tests { let font = Font::from_ttf(&std::fs::read("tests/fonts/demo.ttf").unwrap()).unwrap(); let path = font.text_path("AV"); assert_eq!( - path.data - .into_iter() - .filter(|seg| matches!(seg, PathSegment::Move(..))) + path.into_iter() + .filter(|seg| matches!(seg, PathEl::MoveTo(..))) .count(), 3 ); let path = font.text_path(""); - assert!(path.data.is_empty()); + assert_eq!(path.into_iter().count(), 0); } } diff --git a/src/drawing/svg/mod.rs b/src/drawing/svg/mod.rs index 292663a..9ee4a56 100644 --- a/src/drawing/svg/mod.rs +++ b/src/drawing/svg/mod.rs @@ -1,13 +1,12 @@ mod font; -mod path; mod profile; +use kurbo::{Affine, Point, Rect, Size}; use svg::node::element::Group; use svg::Document; use crate::drawing::Drawing; use crate::key::Key; -use crate::utils::Vec2; use font::Draw as _; use profile::Draw as _; @@ -19,23 +18,34 @@ pub trait ToSvg { impl ToSvg for Drawing { #[must_use] fn to_svg(&self) -> String { - let key_size = self + let bounds = self .keys .iter() - .map(|k| k.position + k.shape.size()) - .fold(Vec2::from(1.), Vec2::max); - - // scale from keyboard units to drawing units (milliunits) - let scale = Vec2::from(1e3); - let size = key_size * scale; + .map(|k| k.position + k.shape.size().to_vec2()) + .fold( + Rect::from_origin_size(Point::ORIGIN, Size::new(1., 1.)), + |r, p| r.union_pt(p), + ); // w and h are in dpi units, the 0.75 is keyboard units per inch - let Vec2 { x, y } = key_size * self.options.dpi * 0.75; + let Size { width, height } = bounds.size() * self.options.dpi * 0.75; + + // scale from keyboard units to drawing units (milliunits) + let bounds = bounds.scale_from_origin(1e3); let document = Document::new() - .set("width", format!("{}", (1e5 * x).floor() / 1e5)) - .set("height", format!("{}", (1e5 * y).floor() / 1e5)) - .set("viewBox", format!("0 0 {:.0} {:.0}", size.x, size.y)); + .set("width", format!("{}", (1e5 * width).floor() / 1e5)) + .set("height", format!("{}", (1e5 * height).floor() / 1e5)) + .set( + "viewBox", + format!( + "{:.0} {:.0} {:.0} {:.0}", + bounds.origin().x, + bounds.origin().y, + bounds.size().width, + bounds.size().height + ), + ); let document = self .keys @@ -49,8 +59,8 @@ impl ToSvg for Drawing { impl Drawing { fn draw_key(&self, key: &Key) -> Group { - let scale = Vec2::from(1e3); - let pos = key.position * scale; + // scale from keyboard units to drawing units (milliunits) + let pos = Affine::scale(1e3) * key.position; let result = Group::new().set("transform", format!("translate({}, {})", pos.x, pos.y)); diff --git a/src/drawing/svg/path.rs b/src/drawing/svg/path.rs deleted file mode 100644 index dad7b9f..0000000 --- a/src/drawing/svg/path.rs +++ /dev/null @@ -1,294 +0,0 @@ -use svg::node::Value; - -use crate::utils::Path; -use crate::utils::{PathSegment, RoundRect, Vec2}; -use crate::ToSvg; - -#[derive(Debug, Clone, Copy)] -pub enum EdgeType { - Line, - CurveStretch, - CurveLineCurve, - InsetCurve, -} - -pub trait KeyHelpers { - fn start(rect: RoundRect) -> Self; - - fn corner_top_left(&mut self, rect: RoundRect); - fn corner_top_right(&mut self, rect: RoundRect); - fn corner_bottom_right(&mut self, rect: RoundRect); - fn corner_bottom_left(&mut self, rect: RoundRect); - - fn edge_top(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64); - fn edge_right(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64); - fn edge_bottom(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64); - fn edge_left(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64); - - fn radius(curve: f64, distance: f64) -> Vec2 { - Vec2::from((curve.powf(2.) + (distance.powf(2.) / 4.)) / (2. * curve)) - } -} - -impl KeyHelpers for Path { - fn start(rect: RoundRect) -> Self { - let mut slf = Self::new(); - slf.abs_move(rect.position() + Vec2::new(0., rect.radius().y)); - slf - } - - fn corner_top_left(&mut self, rect: RoundRect) { - self.rel_arc( - rect.radius(), - 0., - false, - true, - rect.radius() * Vec2::new(1., -1.), - ); - } - - fn corner_top_right(&mut self, rect: RoundRect) { - self.rel_arc(rect.radius(), 0., false, true, rect.radius()); - } - - fn corner_bottom_right(&mut self, rect: RoundRect) { - self.rel_arc( - rect.radius(), - 0., - false, - true, - rect.radius() * Vec2::new(-1., 1.), - ); - } - - fn corner_bottom_left(&mut self, rect: RoundRect) { - self.rel_arc(rect.radius(), 0., false, true, rect.radius() * -1.); - } - - fn edge_top(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64) { - let rect_dx = rect.size().x - 2. * rect.radius().x; - let size_dx = size.x - 1e3; - let dx = rect_dx + size_dx; - match typ { - EdgeType::Line => self.rel_horiz_line(dx), - EdgeType::CurveLineCurve if size_dx > 0.01 => { - let radius = Self::radius(curve, rect_dx); - self.rel_arc(radius, 0., false, true, Vec2::new(rect_dx / 2., -curve)); - self.rel_horiz_line(size_dx); - self.rel_arc(radius, 0., false, true, Vec2::new(rect_dx / 2., curve)); - } - EdgeType::CurveLineCurve | EdgeType::CurveStretch => { - let radius = Self::radius(curve, dx); - self.rel_arc(radius, 0., false, true, Vec2::new(dx, 0.)); - } - EdgeType::InsetCurve => { - let radius = Self::radius(curve, dx); - self.rel_arc(radius, 0., false, false, Vec2::new(dx, 0.)); - } - } - } - - fn edge_right(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64) { - let rect_dy = rect.size().y - 2. * rect.radius().y; - let size_dy = size.y - 1e3; - let dy = rect_dy + size_dy; - match typ { - EdgeType::Line => self.rel_vert_line(dy), - EdgeType::CurveLineCurve if size_dy > 0.01 => { - let radius = Self::radius(curve, rect_dy); - self.rel_arc(radius, 0., false, true, Vec2::new(curve, rect_dy / 2.)); - self.rel_vert_line(size_dy); - self.rel_arc(radius, 0., false, true, Vec2::new(-curve, rect_dy / 2.)); - } - EdgeType::CurveLineCurve | EdgeType::CurveStretch => { - let radius = Self::radius(curve, dy); - self.rel_arc(radius, 0., false, true, Vec2::new(0., dy)); - } - EdgeType::InsetCurve => { - let radius = Self::radius(curve, dy); - self.rel_arc(radius, 0., false, false, Vec2::new(0., dy)); - } - } - } - - fn edge_bottom(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64) { - let rect_dx = rect.size().x - 2. * rect.radius().x; - let size_dx = size.x - 1e3; - let dx = rect_dx + size_dx; - match typ { - EdgeType::Line => self.rel_horiz_line(-dx), - EdgeType::CurveLineCurve if size_dx > 0.01 => { - let radius = Self::radius(curve, rect_dx); - self.rel_arc(radius, 0., false, true, Vec2::new(-rect_dx / 2., curve)); - self.rel_horiz_line(-size_dx); - self.rel_arc(radius, 0., false, true, Vec2::new(-rect_dx / 2., -curve)); - } - EdgeType::CurveLineCurve | EdgeType::CurveStretch => { - let radius = Self::radius(curve, dx); - self.rel_arc(radius, 0., false, true, Vec2::new(-dx, 0.)); - } - EdgeType::InsetCurve => { - let radius = Self::radius(curve, dx); - self.rel_arc(radius, 0., false, false, Vec2::new(-dx, 0.)); - } - } - } - - fn edge_left(&mut self, rect: RoundRect, size: Vec2, typ: EdgeType, curve: f64) { - let rect_dy = rect.size().y - 2. * rect.radius().y; - let size_dy = size.y - 1e3; - let dy = rect_dy + size_dy; - match typ { - EdgeType::Line => self.rel_vert_line(-dy), - EdgeType::CurveLineCurve if size_dy > 0.01 => { - let radius = Self::radius(curve, rect_dy); - self.rel_arc(radius, 0., false, true, Vec2::new(-curve, -rect_dy / 2.)); - self.rel_vert_line(size_dy); - self.rel_arc(radius, 0., false, true, Vec2::new(curve, -rect_dy / 2.)); - } - EdgeType::CurveLineCurve | EdgeType::CurveStretch => { - let radius = Self::radius(curve, dy); - self.rel_arc(radius, 0., false, true, Vec2::new(0., -dy)); - } - EdgeType::InsetCurve => { - let radius = Self::radius(curve, dy); - self.rel_arc(radius, 0., false, false, Vec2::new(0., -dy)); - } - } - } -} - -impl ToSvg for PathSegment { - fn to_svg(&self) -> String { - match *self { - Self::Move(p) => format!( - "M{} {}", - (1e5 * p.x).floor() / 1e5, - (1e5 * p.y).floor() / 1e5 - ), - Self::Line(d) => format!( - "l{} {}", - (1e5 * d.x).floor() / 1e5, - (1e5 * d.y).floor() / 1e5 - ), - Self::CubicBezier(d1, d2, d) => format!( - "c{} {} {} {} {} {}", - (1e5 * d1.x).floor() / 1e5, - (1e5 * d1.y).floor() / 1e5, - (1e5 * d2.x).floor() / 1e5, - (1e5 * d2.y).floor() / 1e5, - (1e5 * d.x).floor() / 1e5, - (1e5 * d.y).floor() / 1e5 - ), - Self::QuadraticBezier(d1, d) => { - format!( - "q{} {} {} {}", - (1e5 * d1.x).floor() / 1e5, - (1e5 * d1.y).floor() / 1e5, - (1e5 * d.x).floor() / 1e5, - (1e5 * d.y).floor() / 1e5 - ) - } - Self::Close => "z".into(), - } - } -} - -impl ToSvg for Path { - fn to_svg(&self) -> String { - self.data - .iter() - .fold(String::new(), |result, seg| result + seg.to_svg().as_str()) - } -} - -impl From for Value { - fn from(value: Path) -> Self { - value.to_svg().into() - } -} - -#[cfg(test)] -mod tests { - use std::f64::consts::{FRAC_1_SQRT_2, SQRT_2}; - - use assert_approx_eq::assert_approx_eq; - use assert_matches::assert_matches; - - use super::*; - - #[test] - fn test_radius() { - assert_approx_eq!(Path::radius(1. - FRAC_1_SQRT_2, SQRT_2), Vec2::from(1.)); - } - - #[test] - fn test_corners() { - let position = Vec2::new(200., 100.); - let size = Vec2::from(600.); - let radius = Vec2::from(50.); - let rect = RoundRect::new(position, size, radius); - let path = Path::start(rect); - - let corner_funcs: Vec = vec![ - Path::corner_top_left, - Path::corner_top_right, - Path::corner_bottom_right, - Path::corner_bottom_left, - ]; - - for func in corner_funcs { - let mut path = path.clone(); - func(&mut path, rect); - assert_eq!(path.data.len(), 2); - assert_matches!(path.data[1], PathSegment::CubicBezier(..)); - } - } - - #[test] - fn test_edges() { - let position = Vec2::new(200., 100.); - let size = Vec2::from(600.); - let radius = Vec2::from(50.); - let rect = RoundRect::new(position, size, radius); - let size = Vec2::from(2e3); - let curve = 20.; - let path = Path::start(rect); - - // TODO 1.69+ seems to allow us to remove type annotations (even though it's not mentioned - // in the release notes). Remove it when MSRV >= 1.69 - let edge_funcs: Vec = vec![ - Path::edge_top, - Path::edge_right, - Path::edge_bottom, - Path::edge_left, - ]; - let edge_type_len = vec![ - (EdgeType::Line, 1), - (EdgeType::CurveStretch, 1), - (EdgeType::CurveLineCurve, 3), - (EdgeType::InsetCurve, 1), - ]; - - for func in edge_funcs { - for &(edge_type, len) in &edge_type_len { - let mut path = path.clone(); - func(&mut path, rect, size, edge_type, curve); - - assert_eq!(path.data.len(), len + 1); - } - } - } - - #[test] - fn test_path_to_svg() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - assert_eq!(path.to_svg(), "M0 0l1 1c0.5 0.5 1.5 0.5 2 0q0.5 -0.5 1 0z"); - } -} diff --git a/src/drawing/svg/profile.rs b/src/drawing/svg/profile.rs index 870bb8e..a0c64bf 100644 --- a/src/drawing/svg/profile.rs +++ b/src/drawing/svg/profile.rs @@ -1,11 +1,12 @@ +use std::f64::consts::{FRAC_PI_2, PI}; + use itertools::Itertools; +use kurbo::{Arc, BezPath, Circle, Point, Rect, Shape, Size, Vec2}; use svg::node::element::Path as SvgPath; use crate::key::{self, Key}; -use crate::profile::{Profile, ProfileType}; -use crate::utils::{Color, Path, Rect, RoundRect, Vec2}; - -use super::path::{EdgeType, KeyHelpers}; +use crate::profile::Profile; +use crate::utils::Color; pub trait Draw { fn draw_key(&self, key: &Key) -> Vec; @@ -41,8 +42,8 @@ impl Draw for Profile { } key::Shape::SteppedCaps => { vec![ - self.draw_key_bottom(Vec2::new(1.75e3, 1e3), color), - self.draw_key_top(typ, Vec2::new(1.25e3, 1e3), color, depth), + self.draw_key_bottom(Size::new(1.75e3, 1e3), color), + self.draw_key_top(typ, Size::new(1.25e3, 1e3), color, depth), self.draw_step(color), ] } @@ -77,34 +78,27 @@ impl Draw for Profile { .map(|s| self.text_margin.get(s)); let (pos_off, size_off) = match key.shape { - key::Shape::IsoHorizontal => (Vec2::ZERO, Vec2::new(500., 0.)), - key::Shape::IsoVertical => (Vec2::new(250., 0.), Vec2::new(250., 1000.)), - key::Shape::SteppedCaps => (Vec2::ZERO, Vec2::new(250., 0.)), - key::Shape::Normal(size) => (Vec2::ZERO, (size - Vec2::from(1.)) * 1e3), + key::Shape::IsoHorizontal => (Vec2::ZERO, Size::new(500., 0.)), + key::Shape::IsoVertical => (Vec2::new(250., 0.), Size::new(250., 1000.)), + key::Shape::SteppedCaps => (Vec2::ZERO, Size::new(250., 0.)), + key::Shape::Normal(size) => (Vec2::ZERO, (size * 1e3 - Size::new(1e3, 1e3))), }; let (pos_off, size_off) = if matches!(key.typ, key::Type::None) { ( - pos_off + (self.bottom_rect.position() - self.top_rect.position()), - size_off + (self.bottom_rect.size() - self.top_rect.size()), + pos_off + (self.bottom_rect.origin() - self.top_rect.origin()), + size_off + (self.bottom_rect.rect().size() - self.top_rect.rect().size()), ) } else { (pos_off, size_off) }; rects - .map(|r| Rect::new(r.position() + pos_off, r.size() + size_off)) - .map(|r| { - let mut path = Path::new(); - path.abs_move(r.position()); - path.rel_horiz_line(r.size().x); - path.rel_vert_line(r.size().y); - path.rel_horiz_line(-r.size().x); - path.rel_vert_line(-r.size().y); - path.close(); - + .map(|rect| { + let path = Rect::from_origin_size(rect.origin() + pos_off, rect.size() + size_off) + .into_path(1.); SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", "none") .set("stroke", Color::new(255, 0, 0).to_string()) .set("stroke-width", "5") @@ -114,283 +108,334 @@ impl Draw for Profile { } impl Profile { - fn draw_key_top(&self, typ: key::Type, size: Vec2, color: Color, depth: f64) -> SvgPath { - let rect = self.top_rect; - let curve = (depth / 19.05 * 1e3) * 0.381; - - let (edge_t, edge_r, edge_b, edge_l) = match (self.profile_type, typ) { - (ProfileType::Flat, _) => ( - EdgeType::Line, - EdgeType::Line, - EdgeType::Line, - EdgeType::Line, - ), - (_, key::Type::Space) => ( - EdgeType::Line, - EdgeType::InsetCurve, - EdgeType::Line, - EdgeType::InsetCurve, - ), - (ProfileType::Cylindrical { .. }, _) => ( - EdgeType::Line, - EdgeType::Line, - EdgeType::CurveStretch, - EdgeType::Line, - ), - (ProfileType::Spherical { .. }, _) => ( - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - ), - }; - - let mut path = Path::start(rect); - path.corner_top_left(rect); - path.edge_top(rect, size, edge_t, curve); - path.corner_top_right(rect); - path.edge_right(rect, size, edge_r, curve); - path.corner_bottom_right(rect); - path.edge_bottom(rect, size, edge_b, curve); - path.corner_bottom_left(rect); - path.edge_left(rect, size, edge_l, curve); - path.close(); + fn draw_key_top(&self, _typ: key::Type, size: Size, color: Color, _depth: f64) -> SvgPath { + let radii = self.top_rect.radii(); + let rect = self.top_rect.rect(); + let rect = rect + .with_size(rect.size() + size - Size::new(1e3, 1e3)) + .to_rounded_rect(radii); + let path = rect.into_path(1.); + + // let curve = (depth / 19.05 * 1e3) * 0.381; + // match (self.profile_type, typ) { + // (ProfileType::Flat, _) => {} + // (_, key::Type::Space) => { + // // TODO inset left & right sides + // } + // (ProfileType::Cylindrical { .. }, _) => { + // // TODO stretched curve on bottom + // } + // (ProfileType::Spherical { .. }, _) => { + // // TODO curve-line-curve on all sides + // } + // }; SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.15).to_string()) .set("stroke-width", "10") } - fn draw_key_bottom(&self, size: Vec2, color: Color) -> SvgPath { - let rect = self.bottom_rect; - - let mut path = Path::start(rect); - path.corner_top_left(rect); - path.edge_top(rect, size, EdgeType::Line, 0.); - path.corner_top_right(rect); - path.edge_right(rect, size, EdgeType::Line, 0.); - path.corner_bottom_right(rect); - path.edge_bottom(rect, size, EdgeType::Line, 0.); - path.corner_bottom_left(rect); - path.edge_left(rect, size, EdgeType::Line, 0.); - path.close(); + fn draw_key_bottom(&self, size: Size, color: Color) -> SvgPath { + let radii = self.bottom_rect.radii(); + let rect = self.bottom_rect.rect(); + let rect = rect + .with_size(rect.size() + size - Size::new(1e3, 1e3)) + .to_rounded_rect(radii); + let path = rect.into_path(1.); SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.15).to_string()) .set("stroke-width", "10") } fn draw_step(&self, color: Color) -> SvgPath { - // Take dimensions from average of top and bottom, adjusting only x and width - let rect = RoundRect::new( - Vec2::new( - 1250. - (self.top_rect.position().x + self.bottom_rect.position().x) / 2., - (self.top_rect.position().y + self.bottom_rect.position().y) / 2., + // Take average dimensions of top and bottom, adjusting only x and width + let rect = Rect::from_origin_size( + Point::new( + 1250. - (self.top_rect.origin().x + self.bottom_rect.origin().x) / 2., + (self.top_rect.origin().y + self.bottom_rect.origin().y) / 2., ), - Vec2::new( - 500. + (self.top_rect.radius().x + self.bottom_rect.radius().x), - (self.top_rect.size().y + self.bottom_rect.size().y) / 2., + Size::new( + 500., + (self.top_rect.height() + self.bottom_rect.height()) / 2., ), - (self.top_rect.radius() + self.bottom_rect.radius()) / 2., ); - // Just set 1u as the size, with the dimensions above it will all line up properly - let size = Vec2::from(1e3); - - let radius = rect.radius(); - let mut path = Path::start(rect); - path.rel_arc(radius, 0., false, false, rect.radius() * -1.); - path.edge_top(rect, size, EdgeType::Line, 0.); - path.corner_top_right(rect); - path.edge_right(rect, size, EdgeType::Line, 0.); - path.corner_bottom_right(rect); - path.edge_bottom(rect, size, EdgeType::Line, 0.); - path.rel_arc(radius, 0., false, false, rect.radius() * Vec2::new(1., -1.)); - path.edge_left(rect, size, EdgeType::Line, 0.); - path.close(); + let radius = (self.top_rect.radii().as_single_radius().unwrap() + + self.bottom_rect.radii().as_single_radius().unwrap()) + / 2.; + + let mut path = BezPath::new(); + path.move_to(rect.origin() + (0., radius)); + path.extend( + Arc::new( + rect.origin() + (-radius, radius), + (radius, radius), + 0., + -FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect.origin() + (rect.width() - radius, 0.)); + path.extend( + Arc::new( + rect.origin() + (rect.width() - radius, radius), + (radius, radius), + -FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect.origin() + (rect.width(), rect.height() - radius)); + path.extend( + Arc::new( + rect.origin() + (rect.width() - radius, rect.height() - radius), + (radius, radius), + 0., + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect.origin() + (-radius, rect.height())); + path.extend( + Arc::new( + rect.origin() + (-radius, rect.height() - radius), + (radius, radius), + FRAC_PI_2, + -FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect.origin() + (0., radius)); + path.close_path(); SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.15).to_string()) .set("stroke-width", "10") } - fn draw_iso_top(&self, typ: key::Type, color: Color, depth: f64) -> SvgPath { - let rect = self.top_rect; - let top_size = Vec2::new(1.5e3, 1e3); - let btm_size = Vec2::new(1.25e3, 2e3); - let curve = (depth / 19.05 * 1e3) * 0.381; - - let (edge_t, edge_r, edge_b, edge_l) = match (self.profile_type, typ) { - (ProfileType::Flat, _) => ( - EdgeType::Line, - EdgeType::Line, - EdgeType::Line, - EdgeType::Line, - ), - (_, key::Type::Space) => ( - EdgeType::Line, - EdgeType::InsetCurve, - EdgeType::Line, - EdgeType::InsetCurve, - ), - (ProfileType::Cylindrical { .. }, _) => ( - EdgeType::Line, - EdgeType::Line, - EdgeType::CurveStretch, - EdgeType::Line, - ), - (ProfileType::Spherical { .. }, _) => ( - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - EdgeType::CurveLineCurve, - ), - }; - - let mut path = Path::start(rect); - path.corner_top_left(rect); - path.edge_top(rect, top_size, edge_t, curve); - path.corner_top_right(rect); - path.edge_right(rect, btm_size, edge_r, curve); - path.corner_bottom_right(rect); - path.edge_bottom(rect, btm_size, edge_b, curve); - path.corner_bottom_left(rect); - - let h_line = 0.25e3 - 2. * rect.radius().x; - let v_line = 1e3 - 2. * rect.radius().y; - let h_curve = match edge_b { - // Curve deflection of the horizontal line - EdgeType::Line => 0., - // a third seems to give a nice result here - EdgeType::CurveLineCurve | EdgeType::CurveStretch => curve / 3., - EdgeType::InsetCurve => unreachable!(), // No horizontal insets currently possible - }; - let v_curve = match edge_l { - // Curve deflection of the vertical line - EdgeType::Line => 0., - EdgeType::CurveLineCurve => curve, - EdgeType::CurveStretch => unreachable!(), // No vertical stretches currently possible - EdgeType::InsetCurve => -curve, - }; + fn draw_iso_top(&self, _typ: key::Type, color: Color, _depth: f64) -> SvgPath { + let rect = self.top_rect.rect(); + let rect150 = rect.with_size(rect.size() + Size::new(500., 0.)); + let rect125 = rect + .with_origin(rect.origin() + (250., 1000.)) + .with_size(rect.size() + Size::new(250., 0.)); + let radius = self.top_rect.radii().as_single_radius().unwrap(); + + // match (self.profile_type, typ) { + // (ProfileType::Flat, _) => {} + // (_, key::Type::Space) => { + // // TODO inset left & right sides + // } + // (ProfileType::Cylindrical { .. }, _) => { + // // TODO stretched curve on bottom + // } + // (ProfileType::Spherical { .. }, _) => { + // // TODO curve-line-curve on all sides + // } + // }; + + let mut path = BezPath::new(); + path.move_to(rect150.origin() + (0., radius)); + path.extend( + Arc::new( + rect150.origin() + (radius, radius), + (radius, radius), + PI, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (rect150.width() - radius, 0.)); + path.extend( + Arc::new( + rect150.origin() + (rect150.width() - radius, radius), + (radius, radius), + -FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect125.origin() + (rect125.width(), rect125.height() - radius)); + path.extend( + Arc::new( + rect125.origin() + (rect125.width() - radius, rect125.height() - radius), + (radius, radius), + 0., + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect125.origin() + (radius, rect125.height())); + path.extend( + Arc::new( + rect125.origin() + (radius, rect125.height() - radius), + (radius, radius), + FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); - match edge_l { - EdgeType::Line => path.rel_vert_line(-(v_line - h_curve)), - EdgeType::CurveLineCurve => { - let radius = Path::radius(curve, rect.size().y); - path.rel_arc( - radius, - 0., - false, - true, - Vec2::new(-v_curve, -rect.size().y / 2.), - ); - path.rel_vert_line(-(v_line - rect.size().y / 2. - h_curve)); - } - EdgeType::CurveStretch => { - // let radius = Path::radius(curve, 2. * v_line); - // path.rel_arc(radius, 0., false, true, Vector::new(-v_curve, v_line)) - unreachable!() // No vertical stretches currently possible - } - EdgeType::InsetCurve => { - let radius = Path::radius(curve, 2. * v_line); - path.rel_arc(radius, 0., false, false, Vec2::new(-v_curve, v_line)); - } - }; + // let h_line = 0.25e3 - 2. * radius; + // let v_line = 1e3 - 2. * radius; + // TODO curvature on inner L edges of ISO enter - let radius = rect.radius(); - path.rel_arc(radius, 0., false, false, rect.radius() * -1.); - path.rel_line(Vec2::new(-(h_line - v_curve), -h_curve)); - path.corner_bottom_left(rect); - path.edge_left(rect, top_size, edge_l, curve); - path.close(); + path.line_to( + Point::new(rect125.origin().x, rect150.origin().y) + (0., rect150.height() + radius), + ); + path.extend( + Arc::new( + Point::new(rect125.origin().x, rect150.origin().y) + + (-radius, rect150.height() + radius), + (radius, radius), + 0., + -FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (radius, rect150.height())); + path.extend( + Arc::new( + rect150.origin() + (radius, rect150.height() - radius), + (radius, radius), + FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (0., radius)); + path.close_path(); SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.15).to_string()) .set("stroke-width", "10") } fn draw_iso_bottom(&self, color: Color) -> SvgPath { - let rect = self.bottom_rect; - let top_size = Vec2::new(1.5e3, 1e3); - let btm_size = Vec2::new(1.25e3, 2e3); - - let radius = rect.radius(); - let mut path = Path::start(rect); - path.corner_top_left(rect); - path.edge_top(rect, top_size, EdgeType::Line, 0.); - path.corner_top_right(rect); - path.edge_right(rect, btm_size, EdgeType::Line, 0.); - path.corner_bottom_right(rect); - path.edge_bottom(rect, btm_size, EdgeType::Line, 0.); - path.corner_bottom_left(rect); - path.rel_vert_line(-(1e3 - 2. * rect.radius().y)); - path.rel_arc(radius, 0., false, false, rect.radius() * -1.); - path.rel_horiz_line(-(0.25e3 - 2. * rect.radius().x)); - path.corner_bottom_left(rect); - path.edge_left(rect, top_size, EdgeType::Line, 0.); - path.close(); + let rect = self.bottom_rect.rect(); + let rect150 = rect.with_size(rect.size() + Size::new(500., 0.)); + let rect125 = rect + .with_origin(rect.origin() + (250., 1000.)) + .with_size(rect.size() + Size::new(250., 0.)); + let radius = self.bottom_rect.radii().as_single_radius().unwrap(); + + let mut path = BezPath::new(); + path.move_to(rect150.origin() + (0., radius)); + path.extend( + Arc::new( + rect150.origin() + (radius, radius), + (radius, radius), + PI, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (rect150.width() - radius, 0.)); + path.extend( + Arc::new( + rect150.origin() + (rect150.width() - radius, radius), + (radius, radius), + -FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect125.origin() + (rect125.width(), rect125.height() - radius)); + path.extend( + Arc::new( + rect125.origin() + (rect125.width() - radius, rect125.height() - radius), + (radius, radius), + 0., + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect125.origin() + (radius, rect125.height())); + path.extend( + Arc::new( + rect125.origin() + (radius, rect125.height() - radius), + (radius, radius), + FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to( + Point::new(rect125.origin().x, rect150.origin().y) + (0., rect150.height() + radius), + ); + path.extend( + Arc::new( + Point::new(rect125.origin().x, rect150.origin().y) + + (-radius, rect150.height() + radius), + (radius, radius), + 0., + -FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (radius, rect150.height())); + path.extend( + Arc::new( + rect150.origin() + (radius, rect150.height() - radius), + (radius, radius), + FRAC_PI_2, + FRAC_PI_2, + 0., + ) + .append_iter(1.), + ); + path.line_to(rect150.origin() + (0., radius)); + path.close_path(); SvgPath::new() - .set("d", path) + .set("d", path.to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.15).to_string()) .set("stroke-width", "10") } - fn draw_homing_bar(&self, size: Vec2, color: Color) -> SvgPath { - let center = self.top_rect.center() + (size - Vec2::from(1e3)) / 2.; - let rect = RoundRect::new( - center - self.homing.bar.size / 2. + Vec2::new(0., self.homing.bar.y_offset), - self.homing.bar.size, - Vec2::from(self.homing.bar.size.y / 2.), - ); - - let mut path = Path::new(); - path.abs_move(rect.position() + Vec2::new(rect.radius().x, 0.)); - path.rel_horiz_line(rect.size().x - 2. * rect.radius().x); - path.rel_arc( - rect.radius(), - 0., - false, - true, - Vec2::new(0., 2. * rect.radius().y), - ); - path.rel_horiz_line(-(rect.size().x - 2. * rect.radius().x)); - path.rel_arc( - rect.radius(), - 0., - false, - true, - Vec2::new(0., -2. * rect.radius().y), - ); - path.close(); + fn draw_homing_bar(&self, size: Size, color: Color) -> SvgPath { + let center = self.top_rect.center() + (size - Size::new(1e3, 1e3)).to_vec2() / 2.; + let rect = Rect::from_center_size(center, self.homing.bar.size) + .to_rounded_rect(self.homing.bar.size.height); SvgPath::new() - .set("d", path) + .set("d", rect.to_path(1.).to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.25).to_string()) .set("stroke-width", "10") } - fn draw_homing_bump(&self, size: Vec2, color: Color) -> SvgPath { - let center = self.top_rect.center() + (size - Vec2::from(1e3)) / 2.; - let r = self.homing.bump.diameter / 2.; - - let mut path = Path::new(); - path.abs_move(center + Vec2::new(0., -r)); - path.rel_arc(Vec2::from(r), 0., false, true, Vec2::new(0., 2. * r)); - path.rel_arc(Vec2::from(r), 0., false, true, Vec2::new(0., -2. * r)); - path.close(); + fn draw_homing_bump(&self, size: Size, color: Color) -> SvgPath { + let center = self.top_rect.center() + (size - Size::new(1e3, 1e3)).to_vec2() / 2.; + let radius = self.homing.bump.diameter / 2.; + let circle = Circle::new(center, radius); SvgPath::new() - .set("d", path) + .set("d", circle.to_path(1.).to_svg()) .set("fill", color.to_string()) .set("stroke", color.highlight(0.25).to_string()) .set("stroke-width", "10") @@ -399,6 +444,8 @@ impl Profile { #[cfg(test)] mod tests { + use crate::profile::ProfileType; + use super::*; #[test] @@ -409,14 +456,14 @@ mod tests { let homing_bar = key::Type::Homing(Some(key::Homing::Bar)); let homing_bump = key::Type::Homing(Some(key::Homing::Bump)); let test_config = vec![ - (key::Shape::Normal(Vec2::new(1., 1.)), key::Type::Normal, 2), + (key::Shape::Normal(Size::new(1., 1.)), key::Type::Normal, 2), (key::Shape::SteppedCaps, key::Type::Normal, 3), (key::Shape::IsoHorizontal, key::Type::Normal, 2), (key::Shape::IsoVertical, key::Type::Normal, 2), - (key::Shape::Normal(Vec2::new(1., 1.)), homing_scoop, 2), - (key::Shape::Normal(Vec2::new(1., 1.)), homing_bar, 3), - (key::Shape::Normal(Vec2::new(1., 1.)), homing_bump, 3), - (key::Shape::Normal(Vec2::new(1., 1.)), key::Type::None, 0), + (key::Shape::Normal(Size::new(1., 1.)), homing_scoop, 2), + (key::Shape::Normal(Size::new(1., 1.)), homing_bar, 3), + (key::Shape::Normal(Size::new(1., 1.)), homing_bump, 3), + (key::Shape::Normal(Size::new(1., 1.)), key::Type::None, 0), ]; for (shape, typ, len) in test_config { @@ -438,11 +485,11 @@ mod tests { let profile = Profile::default(); let key = Key::example(); let test_config = vec![ - (key::Shape::Normal(Vec2::new(1., 1.)), key::Type::Normal), + (key::Shape::Normal(Size::new(1., 1.)), key::Type::Normal), (key::Shape::SteppedCaps, key::Type::Normal), (key::Shape::IsoHorizontal, key::Type::Normal), (key::Shape::IsoVertical, key::Type::Normal), - (key::Shape::Normal(Vec2::new(1., 1.)), key::Type::None), + (key::Shape::Normal(Size::new(1., 1.)), key::Type::None), ]; for (shape, typ) in test_config { @@ -463,7 +510,7 @@ mod tests { let profile = Profile::default(); let key = Key::default(); let homing_scoop = key::Type::Homing(Some(key::Homing::Scoop)); - let size = Vec2::new(1., 1.); + let size = Size::new(1., 1.); let profile_typs = vec![ (ProfileType::Cylindrical { depth: 1.0 }, key::Type::Normal), (ProfileType::Cylindrical { depth: 1.0 }, key::Type::Space), @@ -493,7 +540,7 @@ mod tests { #[test] fn test_draw_key_bottom() { let profile = Profile::default(); - let size = Vec2::new(1., 1.); + let size = Size::new(1., 1.); let path = profile.draw_key_bottom(size, Color::new(0xCC, 0xCC, 0xCC)); @@ -551,7 +598,7 @@ mod tests { #[test] fn test_draw_homing_bar() { let profile = Profile::default(); - let size = Vec2::new(1., 1.); + let size = Size::new(1., 1.); let path = profile.draw_homing_bar(size, Color::new(0xCC, 0xCC, 0xCC)); @@ -561,7 +608,7 @@ mod tests { #[test] fn test_draw_homing_bump() { let profile = Profile::default(); - let size = Vec2::new(1., 1.); + let size = Size::new(1., 1.); let path = profile.draw_homing_bump(size, Color::new(0xCC, 0xCC, 0xCC)); diff --git a/src/font/glyph.rs b/src/font/glyph.rs index 4a113cf..4d4ecf8 100644 --- a/src/font/glyph.rs +++ b/src/font/glyph.rs @@ -1,22 +1,20 @@ -use std::f64::consts::PI; - -use crate::utils::{Path, Vec2}; - -use ttf_parser::{Face, GlyphId}; +use kurbo::{Affine, BezPath, Point}; +use ttf_parser::{Face, GlyphId, OutlineBuilder}; #[derive(Clone, Debug)] pub struct Glyph { pub codepoint: Option, pub advance: f64, - pub path: Path, + pub path: BezPath, } impl Glyph { pub fn new(face: &Face, codepoint: Option, gid: GlyphId) -> Option { let advance = f64::from(face.glyph_hor_advance(gid)?); - let mut path = Path::new(); + let mut path = KurboPathWrapper(BezPath::new()); face.outline_glyph(gid, &mut path); + let path = path.0; Some(Self { codepoint, @@ -26,55 +24,113 @@ impl Glyph { } pub fn notdef(cap_height: f64, slope: f64) -> Self { - let mut path = Path::new(); - - path.abs_move(Vec2::ZERO); // M 0 0 - path.rel_vert_line(1000.); // v 1000 - path.rel_horiz_line(650.); // h 650 - path.rel_vert_line(-1000.); // v -1000 - path.rel_horiz_line(-650.); // h -650 - path.close(); // z - - path.abs_move(Vec2::new(80., 150.)); // M 80 150 - path.rel_line(Vec2::new(190., 350.)); // l 190 350 - path.rel_line(Vec2::new(-190., 350.)); // l -190 350 - path.rel_vert_line(-700.); // v -700 - path.close(); // z - - path.abs_move(Vec2::new(125., 920.)); // M 125 920 - path.rel_line(Vec2::new(200., -360.)); // l 200 -360 - path.rel_line(Vec2::new(200., 360.)); // l 200 360 - path.rel_horiz_line(-400.); // h -400 - path.close(); // z - - path.abs_move(Vec2::new(570., 850.)); // M 570 850 - path.rel_line(Vec2::new(-190., -350.)); // l -190 -350 - path.rel_line(Vec2::new(190., -350.)); // l 190 -350 - path.rel_vert_line(700.); // v 700 - path.close(); // z - - path.abs_move(Vec2::new(525., 80.)); // M 525 80 - path.rel_line(Vec2::new(-200., 360.)); // l -200 360 - path.rel_line(Vec2::new(-200., -360.)); // l -200 -360 - path.rel_horiz_line(400.); // h 400 - path.close(); // z - - path.scale(Vec2::from(cap_height / 1e3)); - path.skew_x(slope * PI / 180.); + let mut path = BezPath::new(); + + path.move_to((0., 0.)); // M 0 0 + path.line_to((0., 1000.)); // V 1000 + path.line_to((650., 1000.)); // H 650 + path.line_to((650., 0.)); // V 0 + path.line_to((0., 0.)); // H 0 + path.close_path(); // Z + + path.move_to((80., 150.)); // M 80 150 + path.line_to((270., 500.)); // L 270 500 + path.line_to((80., 850.)); // L 80 850 + path.line_to((80., 150.)); // V 150 + path.close_path(); // Z + + path.move_to((125., 920.)); // M 125 920 + path.line_to((325., 560.)); // L 325 560 + path.line_to((525., 920.)); // L 525 920 + path.line_to((125., 920.)); // H 125 + path.close_path(); // Z + + path.move_to((570., 850.)); // M 570 850 + path.line_to((380., 500.)); // L 380 500 + path.line_to((570., 150.)); // L 570 150 + path.line_to((570., 850.)); // V 850 + path.close_path(); // Z + + path.move_to((525., 80.)); // M 525 80 + path.line_to((325., 440.)); // L 325 440 + path.line_to((125., 80.)); // L 125 80 + path.line_to((525., 80.)); // H 525 + path.close_path(); // Z + + let skew_x = slope.to_radians().tan(); + let scale = cap_height / 1e3; + let affine = Affine::skew(skew_x, 0.).pre_scale(scale); + + path.apply_affine(affine); + + // let advance = path.bounding_box().size().width; + let advance = scale * (650. + (1000. * skew_x).abs()); Self { codepoint: None, - advance: path.bounds.size().x, + advance, path, } } } +struct KurboPathWrapper(pub BezPath); + +impl From for KurboPathWrapper { + fn from(value: BezPath) -> Self { + Self(value) + } +} + +impl From for BezPath { + fn from(value: KurboPathWrapper) -> Self { + value.0 + } +} + +impl OutlineBuilder for KurboPathWrapper { + fn move_to(&mut self, x: f32, y: f32) { + // Y axis is flipped in fonts compared to SVGs + self.0.move_to(Point::new(x.into(), (-y).into())); + } + + fn line_to(&mut self, x: f32, y: f32) { + // Y axis is flipped in fonts compared to SVGs + self.0.line_to(Point::new(x.into(), (-y).into())); + } + + fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { + // Y axis is flipped in fonts compared to SVGs + self.0.quad_to( + Point::new(x1.into(), (-y1).into()), + Point::new(x.into(), (-y).into()), + ); + } + + fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { + // Y axis is flipped in fonts compared to SVGs + self.0.curve_to( + Point::new(x1.into(), (-y1).into()), + Point::new(x2.into(), (-y2).into()), + Point::new(x.into(), (-y).into()), + ); + } + + fn close(&mut self) { + self.0.close_path(); + } +} + #[cfg(test)] mod tests { + use assert_approx_eq::assert_approx_eq; + use assert_matches::assert_matches; + use itertools::Itertools; + use kurbo::{PathEl, Shape, Size}; + use super::*; - use assert_approx_eq::assert_approx_eq; + use crate::utils::KurboAbs; #[test] fn test_new() { @@ -83,7 +139,7 @@ mod tests { let a = Glyph::new(&demo, Some('A'), GlyphId(1)).unwrap(); assert_approx_eq!(a.advance, 540.); - assert_eq!(a.path.data.len(), 15); + assert_eq!(a.path.into_iter().collect_vec().len(), 15); let null = std::fs::read("tests/fonts/null.ttf").unwrap(); let null = Face::parse(&null, 0).unwrap(); @@ -99,11 +155,31 @@ mod tests { fn test_notdef() { let notdef = Glyph::notdef(500., 0.); - assert_approx_eq!(notdef.path.bounds.position(), Vec2::new(0., 0.)); - assert_approx_eq!(notdef.path.bounds.size(), Vec2::new(325., 500.)); + assert_approx_eq!(notdef.path.bounding_box().origin(), Point::new(0., 0.)); + assert_approx_eq!(notdef.path.bounding_box().size(), Size::new(325., 500.)); assert_approx_eq!(notdef.advance, 325.); let notdef = Glyph::notdef(500., 45.); - assert_approx_eq!(notdef.path.bounds.size(), Vec2::new(825., 500.)); + assert_approx_eq!(notdef.path.bounding_box().size(), Size::new(825., 500.)); + } + + #[test] + fn test_outline_builder() { + let mut path = KurboPathWrapper(BezPath::new()); + + path.move_to(0., 0.); + path.line_to(1., 1.); + path.quad_to(2., 1., 2., 0.); + path.curve_to(2., -0.5, 1.5, -1., 1., -1.); + path.close(); + + let els = path.0.into_iter().collect_vec(); + + assert_eq!(els.len(), 5); + assert_matches!(els[0], PathEl::MoveTo(..)); + assert_matches!(els[1], PathEl::LineTo(..)); + assert_matches!(els[2], PathEl::QuadTo(..)); + assert_matches!(els[3], PathEl::CurveTo(..)); + assert_matches!(els[4], PathEl::ClosePath); } } diff --git a/src/font/mod.rs b/src/font/mod.rs index 7931647..fd9d744 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -4,11 +4,11 @@ mod kerning; use std::collections::HashMap; use crate::error::Result; -use crate::utils::{Path, Vec2}; use itertools::Itertools; +use kurbo::Shape; use log::warn; -use ttf_parser::{cmap, name_id, Face, GlyphId, OutlineBuilder}; +use ttf_parser::{cmap, name_id, Face, GlyphId}; use self::glyph::Glyph; use self::kerning::Kerning; @@ -79,10 +79,10 @@ impl Font { .collect(); let cap_height = cap_height - .or_else(|| Some(glyphs.get(&'X')?.path.bounds.size().y)) + .or_else(|| Some(glyphs.get(&'X')?.path.bounding_box().size().height)) .unwrap_or(0.6 * line_height); // TODO is there a better default? let x_height = x_height - .or_else(|| Some(glyphs.get(&'x')?.path.bounds.size().y)) + .or_else(|| Some(glyphs.get(&'x')?.path.bounding_box().size().height)) .unwrap_or(0.4 * line_height); // TODO is there a better default? let notdef = if let Some(glyph) = Glyph::new(&face, None, GlyphId(0)) { @@ -129,45 +129,11 @@ impl Font { } } -impl OutlineBuilder for crate::utils::Path { - fn move_to(&mut self, x: f32, y: f32) { - // Y axis is flipped in fonts compared to SVGs - self.abs_move(Vec2::new(f64::from(x), f64::from(-y))); - } - - fn line_to(&mut self, x: f32, y: f32) { - // Y axis is flipped in fonts compared to SVGs - self.abs_line(Vec2::new(f64::from(x), f64::from(-y))); - } - - fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { - // Y axis is flipped in fonts compared to SVGs - self.abs_quadratic_bezier( - Vec2::new(f64::from(x1), f64::from(-y1)), - Vec2::new(f64::from(x), f64::from(-y)), - ); - } - - fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { - // Y axis is flipped in fonts compared to SVGs - self.abs_cubic_bezier( - Vec2::new(f64::from(x1), f64::from(-y1)), - Vec2::new(f64::from(x2), f64::from(-y2)), - Vec2::new(f64::from(x), f64::from(-y)), - ); - } - - fn close(&mut self) { - Path::close(self); - } -} - #[cfg(test)] mod tests { use super::*; use assert_approx_eq::assert_approx_eq; - use assert_matches::assert_matches; #[test] fn test_from_ttf() { @@ -203,24 +169,4 @@ mod tests { ); assert_eq!(null.kerning.len(), 0); } - - #[test] - fn test_outline_builder() { - use crate::utils::PathSegment; - - let mut path = Path::new(); - - path.move_to(0., 0.); - path.line_to(1., 1.); - path.quad_to(2., 1., 2., 0.); - path.curve_to(2., -0.5, 1.5, -1., 1., -1.); - OutlineBuilder::close(&mut path); - - assert_eq!(path.data.len(), 5); - assert_matches!(path.data[0], PathSegment::Move(..)); - assert_matches!(path.data[1], PathSegment::Line(..)); - assert_matches!(path.data[2], PathSegment::QuadraticBezier(..)); - assert_matches!(path.data[3], PathSegment::CubicBezier(..)); - assert_matches!(path.data[4], PathSegment::Close); - } } diff --git a/src/key.rs b/src/key.rs index 2087734..79ae5c2 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,4 +1,6 @@ -use crate::utils::{Color, Vec2}; +use kurbo::{Point, Size}; + +use crate::utils::Color; #[derive(Debug, Clone, Copy)] pub enum Homing { @@ -17,7 +19,7 @@ pub enum Type { #[derive(Debug, Clone, Copy)] pub enum Shape { - Normal(Vec2), + Normal(Size), SteppedCaps, IsoVertical, IsoHorizontal, @@ -25,17 +27,17 @@ pub enum Shape { impl Shape { #[inline] - pub fn size(self) -> Vec2 { + pub fn size(self) -> Size { match self { Self::Normal(s) => s, - Self::IsoHorizontal | Self::IsoVertical => Vec2::new(1.5, 2.0), - Self::SteppedCaps => Vec2::new(1.75, 1.0), + Self::IsoHorizontal | Self::IsoVertical => Size::new(1.5, 2.0), + Self::SteppedCaps => Size::new(1.75, 1.0), } } } -impl From for Shape { - fn from(value: Vec2) -> Self { +impl From for Shape { + fn from(value: Size) -> Self { Self::Normal(value) } } @@ -49,7 +51,7 @@ pub struct Legend { #[derive(Debug, Clone)] pub struct Key { - pub position: Vec2, + pub position: Point, pub shape: Shape, pub typ: Type, pub color: Color, @@ -102,8 +104,8 @@ impl Key { impl Default for Key { fn default() -> Self { Self { - position: Vec2::ZERO, - shape: Shape::Normal(Vec2::from(1.)), + position: Point::ORIGIN, + shape: Shape::Normal(Size::new(1., 1.)), typ: Type::Normal, color: Color::new(0xCC, 0xCC, 0xCC), legends: Default::default(), // [[None; 3]; 3] won't work since Option : !Copy @@ -120,26 +122,26 @@ pub mod tests { #[test] fn test_shape_size() { assert_eq!( - Shape::Normal(Vec2::new(2.25, 1.)).size(), - Vec2::new(2.25, 1.) + Shape::Normal(Size::new(2.25, 1.)).size(), + Size::new(2.25, 1.) ); - assert_eq!(Shape::IsoVertical.size(), Vec2::new(1.5, 2.0)); - assert_eq!(Shape::IsoHorizontal.size(), Vec2::new(1.5, 2.0)); - assert_eq!(Shape::SteppedCaps.size(), Vec2::new(1.75, 1.0)); + assert_eq!(Shape::IsoVertical.size(), Size::new(1.5, 2.0)); + assert_eq!(Shape::IsoHorizontal.size(), Size::new(1.5, 2.0)); + assert_eq!(Shape::SteppedCaps.size(), Size::new(1.75, 1.0)); } #[test] fn test_shape_from() { - let shape = Shape::from(Vec2::new(1.75, 1.)); - assert_matches!(shape, Shape::Normal(x) if x == Vec2::new(1.75, 1.)); + let shape = Shape::from(Size::new(1.75, 1.)); + assert_matches!(shape, Shape::Normal(x) if x == Size::new(1.75, 1.)); } #[test] fn test_key_new() { let key = Key::new(); - assert_eq!(key.position, Vec2::new(0., 0.)); - assert_matches!(key.shape, Shape::Normal(size) if size == Vec2::new(1., 1.)); + assert_eq!(key.position, Point::new(0., 0.)); + assert_matches!(key.shape, Shape::Normal(size) if size == Size::new(1., 1.)); assert_matches!(key.typ, Type::Normal); assert_eq!(key.color, Color::new(0xCC, 0xCC, 0xCC)); for row in key.legends { diff --git a/src/kle.rs b/src/kle.rs index 91d7595..eb531c4 100644 --- a/src/kle.rs +++ b/src/kle.rs @@ -1,10 +1,10 @@ use std::{array, fmt}; use kle_serial as kle; +use kurbo::{Point, Size}; use crate::error::{Error, Result}; use crate::key::{self, Key, Legend, Shape}; -use crate::utils::Vec2; #[derive(Debug)] pub(crate) struct InvalidKleLayout { @@ -42,7 +42,7 @@ fn key_shape_from_kle(key: &kle::Key) -> Result { Ok(Shape::IsoHorizontal) } else if is_close(&[x2, y2, w2, h2], &[0., 0., w, h]) { #[allow(clippy::cast_possible_truncation)] - Ok(Shape::Normal(Vec2::new(w, h))) + Ok(Shape::Normal(Size::new(w, h))) } else { // TODO support all key shapes/sizes Err(InvalidKleLayout { @@ -92,7 +92,7 @@ impl TryFrom for Key { fn try_from(key: kle::Key) -> Result { #[allow(clippy::cast_possible_truncation)] Ok(Self { - position: Vec2::new(key.x + key.x2.min(0.), key.y + key.y2.min(0.)), + position: Point::new(key.x + key.x2.min(0.), key.y + key.y2.min(0.)), shape: key_shape_from_kle(&key)?, typ: key_type_from_kle(&key), color: key.color.rgb().into(), diff --git a/src/profile/de.rs b/src/profile/de.rs index d8a8a32..c7adc4e 100644 --- a/src/profile/de.rs +++ b/src/profile/de.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; +use kurbo::{Point, Rect, RoundedRect, Size}; use serde::de::{Error, Unexpected}; use serde::{Deserialize, Deserializer}; use crate::profile::{HomingProps, ProfileType, TextHeight, TextRect}; -use crate::utils::{Rect, RoundRect, Vec2}; use super::{BarProps, BumpProps, Profile}; @@ -24,7 +24,7 @@ impl<'de> Deserialize<'de> for BarProps { RawBarProps::deserialize(deserializer).map(|props| { // Convert mm to milli units BarProps { - size: Vec2::new(props.width, props.height) * (1e3 / 19.05), + size: Size::new(props.width, props.height) * (1e3 / 19.05), y_offset: props.y_offset * (1000. / 19.05), } }) @@ -65,14 +65,14 @@ where RawRect::deserialize(deserializer).map(|rect| { // Convert mm to milli units - let size = Vec2::new(rect.width, rect.height) * (1e3 / 19.05); - let position = ((Vec2::from(1e3)) - size) / 2.; + let center = Point::new(500., 500.); + let size = Size::new(rect.width, rect.height) * (1e3 / 19.05); - Rect::new(position, size) + Rect::from_center_size(center, size) }) } -fn deserialize_round_rect<'de, D>(deserializer: D) -> Result +fn deserialize_round_rect<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -85,11 +85,11 @@ where RawRect::deserialize(deserializer).map(|rect| { // Convert mm to milli units - let size = Vec2::new(rect.width, rect.height) * (1e3 / 19.05); - let position = (Vec2::from(1e3) - size) / 2.; - let radius = Vec2::from(rect.radius * (1000. / 19.05)); + let center = Point::new(500., 500.); + let size = Size::new(rect.width, rect.height) * (1e3 / 19.05); + let radius = rect.radius * (1000. / 19.05); - RoundRect::new(position, size, radius) + Rect::from_center_size(center, size).to_rounded_rect(radius) }) } @@ -108,15 +108,15 @@ where RawOffsetRect::deserialize(deserializer).map(|rect| { // Convert mm to milli units - let size = Vec2::new(rect.width, rect.height) * (1e3 / 19.05); + let center = Point::new(500., 500.); + let size = Size::new(rect.width, rect.height) * (1e3 / 19.05); let offset = rect.y_offset * (1e3 / 19.05); - let position = (Vec2::from(1e3) - size) / 2. + Vec2::new(0., offset); - Rect::new(position, size) + Rect::from_center_size(center + (0., offset), size) }) } -fn deserialize_offset_round_rect<'de, D>(deserializer: D) -> Result +fn deserialize_offset_round_rect<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { @@ -132,12 +132,12 @@ where RawOffsetRoundRect::deserialize(deserializer).map(|rect| { // Convert mm to milli units - let size = Vec2::new(rect.width, rect.height) * (1e3 / 19.05); + let center = Point::new(500., 500.); + let size = Size::new(rect.width, rect.height) * (1e3 / 19.05); let offset = rect.y_offset * (1e3 / 19.05); - let position = (Vec2::from(1e3) - size) / 2. + Vec2::new(0., offset); - let radius = Vec2::from(rect.radius * (1000. / 19.05)); + let radius = rect.radius * (1000. / 19.05); - RoundRect::new(position, size, radius) + Rect::from_center_size(center + (0., offset), size).to_rounded_rect(radius) }) } @@ -176,9 +176,9 @@ impl<'de> Deserialize<'de> for Profile { #[serde(flatten)] profile_type: ProfileType, #[serde(deserialize_with = "deserialize_round_rect")] - bottom: RoundRect, + bottom: RoundedRect, #[serde(deserialize_with = "deserialize_offset_round_rect")] - top: RoundRect, + top: RoundedRect, #[serde(deserialize_with = "deserialize_legend_map")] legend: HashMap, homing: HomingProps, @@ -191,7 +191,7 @@ impl<'de> Deserialize<'de> for Profile { .legend .into_iter() .map(|(i, (s, r))| { - let r = Rect::new(r.position() + Vec2::new(0., top_offset), r.size()); + let r = r.with_origin(r.origin() + (0., top_offset)); ((i, s), (i, r)) }) .unzip(); @@ -214,6 +214,8 @@ mod tests { use super::*; + use crate::utils::KurboAbs; + #[test] fn test_text_height_new() { let expected = vec![0., 2., 4., 6., 8., 10., 12., 14., 16., 18.]; @@ -257,39 +259,39 @@ mod tests { #[test] fn test_text_rect_new() { - let expected = vec![Rect::new(Vec2::ZERO, Vec2::from(1e3)); 10]; + let expected = vec![Rect::from_origin_size(Point::ORIGIN, Size::new(1e3, 1e3)); 10]; let result = TextRect::new(&hashmap! {}).0; assert_eq!(expected.len(), result.len()); for (e, r) in expected.iter().zip(result.iter()) { - assert_approx_eq!(e.position(), r.position()); + assert_approx_eq!(e.origin(), r.origin()); assert_approx_eq!(e.size(), r.size()); } let expected = vec![ - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), ]; let result = TextRect::new(&hashmap! { - 2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - 5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - 7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + 2 => Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + 5 => Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + 7 => Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), }) .0; assert_eq!(expected.len(), result.len()); for (e, r) in expected.iter().zip(result.iter()) { - assert_approx_eq!(e.position(), r.position()); + assert_approx_eq!(e.origin(), r.origin()); assert_approx_eq!(e.size(), r.size()); } } @@ -297,18 +299,18 @@ mod tests { #[test] fn test_text_rect_get() { let rects = TextRect::new(&hashmap! { - 2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - 5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - 7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + 2 => Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + 5 => Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + 7 => Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), }); let r = rects.get(2); - assert_approx_eq!(r.position(), Vec2::from(200.)); - assert_approx_eq!(r.size(), Vec2::from(600.)); + assert_approx_eq!(r.origin(), Point::new(200., 200.)); + assert_approx_eq!(r.size(), Size::new(600., 600.)); let r = rects.get(62); - assert_approx_eq!(r.position(), Vec2::from(300.)); - assert_approx_eq!(r.size(), Vec2::from(400.)); + assert_approx_eq!(r.origin(), Point::new(300., 300.)); + assert_approx_eq!(r.size(), Size::new(400., 400.)); } #[test] @@ -323,8 +325,8 @@ mod tests { )) .unwrap(); - assert_approx_eq!(rect.position(), Vec2::from(100.)); - assert_approx_eq!(rect.size(), Vec2::from(800.)); + assert_approx_eq!(rect.origin(), Point::new(100., 100.)); + assert_approx_eq!(rect.size(), Size::new(800., 800.)); } #[test] @@ -340,9 +342,9 @@ mod tests { )) .unwrap(); - assert_approx_eq!(rect.position(), Vec2::from(100.)); - assert_approx_eq!(rect.size(), Vec2::from(800.)); - assert_approx_eq!(rect.radius(), Vec2::from(100.)); + assert_approx_eq!(rect.origin(), Point::new(100., 100.)); + assert_approx_eq!(rect.rect().size(), Size::new(800., 800.)); + assert_approx_eq!(rect.radii().as_single_radius().unwrap(), 100.); } #[test] @@ -358,8 +360,8 @@ mod tests { )) .unwrap(); - assert_approx_eq!(rect.position(), Vec2::new(100., 150.)); - assert_approx_eq!(rect.size(), Vec2::from(800.)); + assert_approx_eq!(rect.origin(), Point::new(100., 150.)); + assert_approx_eq!(rect.size(), Size::new(800., 800.)); } #[test] @@ -376,8 +378,8 @@ mod tests { )) .unwrap(); - assert_approx_eq!(rect.position(), Vec2::new(100., 150.)); - assert_approx_eq!(rect.size(), Vec2::from(800.)); - assert_approx_eq!(rect.radius(), Vec2::from(100.)); + assert_approx_eq!(rect.origin(), Point::new(100., 150.)); + assert_approx_eq!(rect.rect().size(), Size::new(800., 800.)); + assert_approx_eq!(rect.radii().as_single_radius().unwrap(), 100.); } } diff --git a/src/profile/mod.rs b/src/profile/mod.rs index 9a53555..7e9b425 100644 --- a/src/profile/mod.rs +++ b/src/profile/mod.rs @@ -5,11 +5,11 @@ use std::{array, iter}; use interp::interp_array; use itertools::Itertools; +use kurbo::{Point, Rect, RoundedRect, Size}; use serde::Deserialize; use crate::error::Result; use crate::key; -use crate::utils::{Rect, RoundRect, Vec2}; #[derive(Debug, Clone, Copy, Deserialize)] #[serde(tag = "type", rename_all = "kebab-case")] @@ -42,7 +42,7 @@ pub struct ScoopProps { #[derive(Debug, Clone, Copy)] pub struct BarProps { - pub size: Vec2, + pub size: Size, pub y_offset: f64, } @@ -80,7 +80,7 @@ impl Default for HomingProps { depth: 2. * ProfileType::default().depth(), // 2x the regular depth }, bar: BarProps { - size: Vec2::new(3.81, 0.51), // = 0.15in, 0.02in + size: Size::new(3.81, 0.51), // = 0.15in, 0.02in y_offset: 6.35, // = 0.25in }, bump: BumpProps { @@ -179,7 +179,7 @@ impl TextRect { impl Default for TextRect { fn default() -> Self { - let rect = Rect::new(Vec2::ZERO, Vec2::from(1e3)); + let rect = Rect::from_origin_size(Point::ORIGIN, Size::new(1e3, 1e3)); Self([rect; Self::NUM_RECTS]) } } @@ -187,8 +187,8 @@ impl Default for TextRect { #[derive(Debug, Clone)] pub struct Profile { pub profile_type: ProfileType, - pub bottom_rect: RoundRect, - pub top_rect: RoundRect, + pub bottom_rect: RoundedRect, + pub top_rect: RoundedRect, pub text_margin: TextRect, pub text_height: TextHeight, pub homing: HomingProps, @@ -204,8 +204,16 @@ impl Default for Profile { fn default() -> Self { Self { profile_type: ProfileType::default(), - bottom_rect: RoundRect::new(Vec2::from(25.), Vec2::from(950.), Vec2::from(65.)), - top_rect: RoundRect::new(Vec2::new(170., 55.), Vec2::new(660., 735.), Vec2::from(65.)), + bottom_rect: RoundedRect::from_origin_size( + Point::new(25., 25.), + Size::new(950., 950.), + 65., + ), + top_rect: RoundedRect::from_origin_size( + Point::new(170., 55.), + Size::new(660., 735.), + 65., + ), text_margin: TextRect::default(), text_height: TextHeight::default(), homing: HomingProps::default(), @@ -221,6 +229,8 @@ mod tests { use super::*; + use crate::utils::KurboAbs; + #[test] fn test_profile_type_depth() { assert_eq!(ProfileType::Cylindrical { depth: 1. }.depth(), 1.); @@ -237,7 +247,7 @@ mod tests { fn test_homing_props_default() { assert_matches!(HomingProps::default().default, key::Homing::Bar); assert_eq!(HomingProps::default().scoop.depth, 2.); - assert_eq!(HomingProps::default().bar.size, Vec2::new(3.81, 0.51)); + assert_eq!(HomingProps::default().bar.size, Size::new(3.81, 0.51)); assert_eq!(HomingProps::default().bar.y_offset, 6.35); assert_eq!(HomingProps::default().bump.diameter, 0.51); assert_eq!(HomingProps::default().bump.y_offset, 0.); @@ -295,39 +305,39 @@ mod tests { #[test] fn test_text_rect_new() { - let expected = vec![Rect::new(Vec2::ZERO, Vec2::from(1e3)); 10]; + let expected = vec![Rect::from_origin_size(Point::ORIGIN, Size::new(1e3, 1e3)); 10]; let result = TextRect::new(&hashmap! {}).0; assert_eq!(expected.len(), result.len()); for (e, r) in expected.iter().zip(result.iter()) { - assert_approx_eq!(e.position(), r.position()); + assert_approx_eq!(e.origin(), r.origin()); assert_approx_eq!(e.size(), r.size()); } let expected = vec![ - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), - Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), + Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), ]; let result = TextRect::new(&hashmap! { - 2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - 5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - 7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + 2 => Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + 5 => Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + 7 => Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), }) .0; assert_eq!(expected.len(), result.len()); for (e, r) in expected.iter().zip(result.iter()) { - assert_approx_eq!(e.position(), r.position()); + assert_approx_eq!(e.origin(), r.origin()); assert_approx_eq!(e.size(), r.size()); } } @@ -335,18 +345,18 @@ mod tests { #[test] fn test_text_rect_get() { let rects = TextRect::new(&hashmap! { - 2 => Rect::new(Vec2::new(200., 200.), Vec2::new(600., 600.)), - 5 => Rect::new(Vec2::new(250., 250.), Vec2::new(500., 500.)), - 7 => Rect::new(Vec2::new(300., 300.), Vec2::new(400., 400.)), + 2 => Rect::from_origin_size(Point::new(200., 200.), Size::new(600., 600.)), + 5 => Rect::from_origin_size(Point::new(250., 250.), Size::new(500., 500.)), + 7 => Rect::from_origin_size(Point::new(300., 300.), Size::new(400., 400.)), }); let r = rects.get(2); - assert_approx_eq!(r.position(), Vec2::from(200.)); - assert_approx_eq!(r.size(), Vec2::from(600.)); + assert_approx_eq!(r.origin(), Point::new(200., 200.)); + assert_approx_eq!(r.size(), Size::new(600., 600.)); let r = rects.get(62); - assert_approx_eq!(r.position(), Vec2::from(300.)); - assert_approx_eq!(r.size(), Vec2::from(400.)); + assert_approx_eq!(r.origin(), Point::new(300., 300.)); + assert_approx_eq!(r.size(), Size::new(400., 400.)); } #[test] @@ -354,8 +364,8 @@ mod tests { let rects = TextRect::default(); for r in rects.0.into_iter() { - assert_approx_eq!(r.position(), Vec2::ZERO); - assert_approx_eq!(r.size(), Vec2::from(1e3)); + assert_approx_eq!(r.origin(), Point::ORIGIN); + assert_approx_eq!(r.size(), Size::new(1e3, 1e3)); } } @@ -408,13 +418,25 @@ mod tests { matches!(profile.profile_type, ProfileType::Cylindrical { depth } if f64::abs(depth - 0.5) < 1e-6) ); - assert_approx_eq!(profile.bottom_rect.position(), Vec2::from(20.), 0.5); - assert_approx_eq!(profile.bottom_rect.size(), Vec2::from(960.), 0.5); - assert_approx_eq!(profile.bottom_rect.radius(), Vec2::from(20.), 0.5); + assert_approx_eq!(profile.bottom_rect.origin(), Point::new(20., 20.), 0.5); + assert_approx_eq!( + profile.bottom_rect.rect().size(), + Size::new(960., 960.), + 0.5 + ); + assert_approx_eq!( + profile.bottom_rect.radii().as_single_radius().unwrap(), + 20., + 0.5 + ); - assert_approx_eq!(profile.top_rect.position(), Vec2::new(190., 50.), 0.5); - assert_approx_eq!(profile.top_rect.size(), Vec2::new(620., 730.), 0.5); - assert_approx_eq!(profile.top_rect.radius(), Vec2::new(80., 80.), 0.5); + assert_approx_eq!(profile.top_rect.origin(), Point::new(190., 50.), 0.5); + assert_approx_eq!(profile.top_rect.rect().size(), Size::new(620., 730.), 0.5); + assert_approx_eq!( + profile.top_rect.radii().as_single_radius().unwrap(), + 80., + 0.5 + ); assert_eq!(profile.text_height.0.len(), 10); let expected = vec![0., 40., 80., 120., 167., 254., 341., 428., 515., 603., 690.]; @@ -424,25 +446,25 @@ mod tests { assert_eq!(profile.text_margin.0.len(), 10); let expected = vec![ - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 593.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 593.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 593.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 593.)), - Rect::new(Vec2::new(250., 185.), Vec2::new(500., 502.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 606.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 606.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 606.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 606.)), - Rect::new(Vec2::new(252., 112.), Vec2::new(496., 606.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 593.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 593.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 593.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 593.)), + Rect::from_origin_size(Point::new(250., 185.), Size::new(500., 502.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 606.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 606.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 606.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 606.)), + Rect::from_origin_size(Point::new(252., 112.), Size::new(496., 606.)), ]; for (e, r) in expected.iter().zip(profile.text_margin.0.iter()) { - assert_approx_eq!(e.position(), r.position(), 0.5); + assert_approx_eq!(e.origin(), r.origin(), 0.5); assert_approx_eq!(e.size(), r.size(), 0.5); } assert_matches!(profile.homing.default, key::Homing::Scoop); assert_approx_eq!(profile.homing.scoop.depth, 1.5); - assert_approx_eq!(profile.homing.bar.size, Vec2::new(202., 21.), 0.5); + assert_approx_eq!(profile.homing.bar.size, Size::new(202., 21.), 0.5); assert_approx_eq!(profile.homing.bar.y_offset, 265., 0.5); assert_approx_eq!(profile.homing.bump.diameter, 21., 0.5); assert_approx_eq!(profile.homing.bump.y_offset, -10., 0.5); @@ -468,13 +490,13 @@ expected `.`, `=` assert_matches!(profile.profile_type, ProfileType::Cylindrical { depth } if depth == 1.); - assert_approx_eq!(profile.bottom_rect.position(), Vec2::from(25.)); - assert_approx_eq!(profile.bottom_rect.size(), Vec2::from(950.)); - assert_approx_eq!(profile.bottom_rect.radius(), Vec2::from(65.)); + assert_approx_eq!(profile.bottom_rect.origin(), Point::new(25., 25.)); + assert_approx_eq!(profile.bottom_rect.rect().size(), Size::new(950., 950.)); + assert_approx_eq!(profile.bottom_rect.radii().as_single_radius().unwrap(), 65.); - assert_approx_eq!(profile.top_rect.position(), Vec2::new(170., 55.)); - assert_approx_eq!(profile.top_rect.size(), Vec2::new(660., 735.)); - assert_approx_eq!(profile.top_rect.radius(), Vec2::from(65.)); + assert_approx_eq!(profile.top_rect.origin(), Point::new(170., 55.)); + assert_approx_eq!(profile.top_rect.rect().size(), Size::new(660., 735.)); + assert_approx_eq!(profile.top_rect.radii().as_single_radius().unwrap(), 65.); assert_eq!(profile.text_height.0.len(), 10); let expected = TextHeight::default(); @@ -485,7 +507,7 @@ expected `.`, `=` assert_eq!(profile.text_margin.0.len(), 10); let expected = TextRect::default(); for (e, r) in expected.0.iter().zip(profile.text_margin.0.iter()) { - assert_approx_eq!(e.position(), r.position(), 0.5); + assert_approx_eq!(e.origin(), r.origin(), 0.5); assert_approx_eq!(e.size(), r.size(), 0.5); } } diff --git a/src/utils/geometry/mod.rs b/src/utils/geometry/mod.rs deleted file mode 100644 index 1be23ce..0000000 --- a/src/utils/geometry/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod rect; -mod vector; - -pub use rect::{Rect, RoundRect}; -pub use vector::Vec2; diff --git a/src/utils/geometry/rect.rs b/src/utils/geometry/rect.rs deleted file mode 100644 index 0cc69fd..0000000 --- a/src/utils/geometry/rect.rs +++ /dev/null @@ -1,767 +0,0 @@ -use std::ops; - -use super::Vec2; - -#[derive(Debug, Clone, Copy)] -pub struct Rect { - position: Vec2, - size: Vec2, -} - -impl Rect { - #[inline] - pub fn new(position: Vec2, size: Vec2) -> Self { - let (p1, p2) = (position, position + size); - let (min, max) = (Vec2::min(p1, p2), Vec2::max(p1, p2)); - Self { - position: min, - size: max - min, - } - } - - #[inline] - pub fn from_points(point1: Vec2, point2: Vec2) -> Self { - let (min, max) = (Vec2::min(point1, point2), Vec2::max(point1, point2)); - Self { - position: min, - size: max - min, - } - } - - #[inline] - pub const fn position(self) -> Vec2 { - self.position - } - - #[inline] - pub const fn size(self) -> Vec2 { - self.size - } - - #[inline] - pub fn center(self) -> Vec2 { - self.position + (self.size * 0.5) - } -} - -impl PartialEq for Rect { - fn eq(&self, other: &Self) -> bool { - self.position == other.position && self.size == other.size - } -} - -impl ops::Mul for Rect -where - Vec2: ops::Mul, -{ - type Output = Self; - - #[inline] - fn mul(self, rhs: T) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self.position * rhs, self.size * rhs) - } -} - -impl ops::Mul for f64 { - type Output = Rect; - - #[inline] - fn mul(self, rhs: Rect) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self * rhs.position, self * rhs.size) - } -} - -impl ops::MulAssign for Rect -where - Vec2: ops::MulAssign, -{ - #[inline] - fn mul_assign(&mut self, rhs: T) { - self.position *= rhs; - self.size *= rhs; - - // The call to new will ensure that rhs < 0 is handled correctly - *self = Self::new(self.position, self.size); - } -} - -impl ops::Div for Rect -where - Vec2: ops::Div, -{ - type Output = Self; - - #[inline] - fn div(self, rhs: T) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self.position / rhs, self.size / rhs) - } -} - -impl ops::DivAssign for Rect -where - Vec2: ops::DivAssign, -{ - #[inline] - fn div_assign(&mut self, rhs: T) { - self.position /= rhs; - self.size /= rhs; - - // The call to new will ensure that rhs < 0 is handled correctly - *self = Self::new(self.position, self.size); - } -} - -impl Rect {} - -impl From for (f64, f64, f64, f64) { - fn from(rect: Rect) -> (f64, f64, f64, f64) { - (rect.position.x, rect.position.y, rect.size.x, rect.size.y) - } -} - -impl From<(f64, f64, f64, f64)> for Rect { - fn from(tuple: (f64, f64, f64, f64)) -> Self { - // The call to new will ensure that width/height < 0 is handled correctly - Self::new((tuple.0, tuple.1).into(), (tuple.2, tuple.3).into()) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct RoundRect { - position: Vec2, - size: Vec2, - radius: Vec2, -} - -impl RoundRect { - #[inline] - pub fn new(position: Vec2, size: Vec2, radius: Vec2) -> Self { - let (p1, p2) = (position, position + size); - let (min, max) = (Vec2::min(p1, p2), Vec2::max(p1, p2)); - let abs_r = Vec2::new(radius.x.abs(), radius.y.abs()).min((max - min) / 2.); - Self { - position: min, - size: max - min, - radius: abs_r, - } - } - - #[inline] - pub fn from_points(point1: Vec2, point2: Vec2, radius: Vec2) -> Self { - let (min, max) = (Vec2::min(point1, point2), Vec2::max(point1, point2)); - let abs_r = Vec2::new(radius.x.abs(), radius.y.abs()).min((max - min) / 2.); - Self { - position: min, - size: max - min, - radius: abs_r, - } - } - - #[inline] - pub const fn position(self) -> Vec2 { - self.position - } - - #[inline] - pub const fn size(self) -> Vec2 { - self.size - } - - #[inline] - pub const fn radius(self) -> Vec2 { - self.radius - } - - #[inline] - pub fn center(self) -> Vec2 { - self.position + (self.size / 2.) - } -} - -impl PartialEq for RoundRect { - fn eq(&self, other: &Self) -> bool { - self.position == other.position && self.size == other.size && self.radius == other.radius - } -} - -impl ops::Mul for RoundRect -where - Vec2: ops::Mul, -{ - type Output = Self; - - #[inline] - fn mul(self, rhs: T) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self.position * rhs, self.size * rhs, self.radius * rhs) - } -} - -impl ops::Mul for f64 { - type Output = RoundRect; - - #[inline] - fn mul(self, rhs: RoundRect) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self * rhs.position, self * rhs.size, self * rhs.radius) - } -} - -impl ops::MulAssign for RoundRect -where - Vec2: ops::MulAssign, -{ - #[inline] - fn mul_assign(&mut self, rhs: T) { - self.position *= rhs; - self.size *= rhs; - self.radius *= rhs; - - // The call to new will ensure that rhs < 0 is handled correctly - *self = Self::new(self.position, self.size, self.radius); - } -} - -impl ops::Div for RoundRect -where - Vec2: ops::Div, -{ - type Output = Self; - - #[inline] - fn div(self, rhs: T) -> Self::Output { - // The call to new will ensure that rhs < 0 is handled correctly - Self::Output::new(self.position / rhs, self.size / rhs, self.radius / rhs) - } -} - -impl ops::DivAssign for RoundRect -where - Vec2: ops::DivAssign, -{ - #[inline] - fn div_assign(&mut self, rhs: T) { - self.position /= rhs; - self.size /= rhs; - self.radius /= rhs; - - // The call to new will ensure that rhs < 0 is handled correctly - *self = Self::new(self.position, self.size, self.radius); - } -} - -impl From for (f64, f64, f64, f64, f64, f64) { - fn from(rect: RoundRect) -> (f64, f64, f64, f64, f64, f64) { - let ((x, y), (w, h), (rx, ry)) = - (rect.position.into(), rect.size.into(), rect.radius.into()); - (x, y, w, h, rx, ry) - } -} - -impl From<(f64, f64, f64, f64, f64, f64)> for RoundRect { - fn from(tuple: (f64, f64, f64, f64, f64, f64)) -> Self { - // The call to new will ensure that width/height < 0 is handled correctly - Self::new( - (tuple.0, tuple.1).into(), - (tuple.2, tuple.3).into(), - (tuple.4, tuple.5).into(), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use assert_approx_eq::assert_approx_eq; - - // this is required for assert_approx_eq to work - impl ops::Sub for Rect { - type Output = f64; - - fn sub(self, other: Self) -> f64 { - [ - (other.position() - self.position()), - (other.size() - self.size()), - ] - .into_iter() - .map(|v| v.abs().powi(2)) - .sum::() - .sqrt() - } - } - - // this is required for assert_approx_eq to work - impl ops::Sub for RoundRect { - type Output = f64; - - fn sub(self, other: Self) -> f64 { - [ - (other.position - self.position), - (other.size - self.size), - (other.radius - self.radius), - ] - .into_iter() - .map(|v| v.abs().powi(2)) - .sum::() - .sqrt() - } - } - - #[test] - fn test_rect_new() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect::new(position, size); - assert_approx_eq!(rect, (1., 2., 3., 4.).into()); - - let rect = RoundRect::new(position, size, radius); - assert_approx_eq!(rect, (1., 2., 3., 4., 1.5, 0.5).into()); - } - - #[test] - fn test_rect_from_points() { - let point1 = Vec2::new(1., 2.); - let point2 = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect::from_points(point1, point2); - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(1., 2.), - size: Vec2::new(2., 2.) - } - ); - - let rect = RoundRect::from_points(point1, point2, radius); - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(1., 2.), - size: Vec2::new(2., 2.), - radius: Vec2::new(1., 0.5) - } - ); - } - - #[test] - fn test_rect_position() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect { position, size }; - assert_approx_eq!(rect.position(), Vec2::new(1., 2.)); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!(rect.position(), Vec2::new(1., 2.)); - } - - #[test] - fn test_rect_size() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect { position, size }; - assert_approx_eq!(rect.size(), Vec2::new(3., 4.)); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!(rect.size(), Vec2::new(3., 4.)); - } - - #[test] - fn test_rect_center() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect { position, size }; - assert_approx_eq!(rect.center(), Vec2::new(2.5, 4.)); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!(rect.center(), Vec2::new(2.5, 4.)); - } - - #[test] - fn test_roundrect_radius() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!(rect.radius(), Vec2::new(2., 0.5)); - } - - #[test] - fn test_rect_eq() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect = Rect { position, size }; - assert_eq!(rect, Rect { position, size }); - assert_ne!( - rect, - Rect { - position: -position, - size - } - ); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_eq!( - rect, - RoundRect { - position, - size, - radius - } - ); - assert_ne!( - rect, - RoundRect { - position: size, - size: position, - radius - } - ); - } - - #[test] - fn test_rect_into() { - let position = Vec2::new(1., 2.); - let size = Vec2::new(3., 4.); - let radius = Vec2::new(2., 0.5); - - let rect: (f64, f64, f64, f64) = Rect { position, size }.into(); - assert_eq!(rect, (1., 2., 3., 4.)); - - let rect: (f64, f64, f64, f64, f64, f64) = RoundRect { - position, - size, - radius, - } - .into(); - assert_eq!(rect, (1., 2., 3., 4., 2., 0.5)); - } - - #[test] - fn test_rect_from() { - let rect: Rect = (1., 2., 3., 4.).into(); - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(1., 2.), - size: Vec2::new(3., 4.) - } - ); - - let rect: RoundRect = (1., 2., 3., 4., 1., 0.5).into(); - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(1., 2.), - size: Vec2::new(3., 4.), - radius: Vec2::new(1., 0.5) - } - ); - } - - #[test] - fn test_rect_mul_vector() { - let position = Vec2::new(2., 4.5); - let size = Vec2::new(0.5, 1.8); - let radius = Vec2::new(1., 0.9); - - let scale = Vec2::new(1.2, 1. / 9.); - - let rect = Rect { position, size }; - assert_approx_eq!( - rect * scale, - Rect { - position: Vec2::new(2.4, 0.5), - size: Vec2::new(0.6, 0.2) - } - ); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!( - rect * scale, - RoundRect { - position: Vec2::new(2.4, 0.5), - size: Vec2::new(0.6, 0.2), - radius: Vec2::new(0.3, 0.1) - } - ); - } - - #[test] - fn test_rect_mul_assign_vector() { - let position = Vec2::new(3., 2.3); - let size = Vec2::new(0.5, 5.75); - let radius = Vec2::new(2., 2.3); - - let scale = Vec2::new(-1., 1. / 2.3); - - let mut rect = Rect { position, size }; - rect *= scale; - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(-3.5, 1.), - size: Vec2::new(0.5, 2.5) - } - ); - - let mut rect = RoundRect { - position, - size, - radius, - }; - rect *= scale; - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(-3.5, 1.), - size: Vec2::new(0.5, 2.5), - radius: Vec2::new(0.25, 1.) - } - ); - } - - #[test] - fn test_rect_mul_f64() { - let position = Vec2::new(2., 4.5); - let size = Vec2::new(1.5, 2.); - let radius = Vec2::new(1., 0.9); - - let rect = Rect { position, size }; - assert_approx_eq!( - rect * 2., - Rect { - position: Vec2::new(4., 9.), - size: Vec2::new(3., 4.) - } - ); - assert_approx_eq!( - 2. * rect, - Rect { - position: Vec2::new(4., 9.), - size: Vec2::new(3., 4.) - } - ); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!( - rect * 2., - RoundRect { - position: Vec2::new(4., 9.), - size: Vec2::new(3., 4.), - radius: Vec2::new(1.5, 1.8) - } - ); - assert_approx_eq!( - 2. * rect, - RoundRect { - position: Vec2::new(4., 9.), - size: Vec2::new(3., 4.), - radius: Vec2::new(1.5, 1.8) - } - ); - } - - #[test] - fn test_rect_mul_assign_f64() { - let position = Vec2::new(3., 2.3); - let size = Vec2::new(5. / 7., 1.); - let radius = Vec2::new(2., 2.3); - - let mut rect = Rect { position, size }; - rect *= 1.4; - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(4.2, 3.22), - size: Vec2::new(1., 1.4) - } - ); - - let mut rect = RoundRect { - position, - size, - radius, - }; - rect *= 1.4; - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(4.2, 3.22), - size: Vec2::new(1., 1.4), - radius: Vec2::new(0.5, 0.7) - } - ); - } - - #[test] - fn test_rect_div_vector() { - let position = Vec2::new(2., 4.5); - let size = Vec2::new(6., 3.6); - let radius = Vec2::new(1., 0.9); - - let scale = Vec2::new(1.2, 9.); - - let rect = Rect { position, size }; - assert_approx_eq!( - rect / scale, - Rect { - position: Vec2::new(5. / 3., 0.5), - size: Vec2::new(5., 0.4) - } - ); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!( - rect / scale, - RoundRect { - position: Vec2::new(5. / 3., 0.5), - size: Vec2::new(5., 0.4), - radius: Vec2::new(5. / 6., 0.1) - } - ); - } - - #[test] - fn test_rect_div_assign_vector() { - let position = Vec2::new(3., 2.3); - let size = Vec2::new(2., 0.322); - let radius = Vec2::new(1., 0.161); - - let scale = Vec2::new(-1., 0.23); - - let mut rect = Rect { position, size }; - rect /= scale; - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(-5., 10.), - size: Vec2::new(2., 1.4) - } - ); - - let mut rect = RoundRect { - position, - size, - radius, - }; - rect /= scale; - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(-5., 10.), - size: Vec2::new(2., 1.4), - radius: Vec2::new(1., 0.7) - } - ); - } - - #[test] - fn test_rect_div_f64() { - let position = Vec2::new(2., 4.5); - let size = Vec2::new(0.4, 9.); - let radius = Vec2::new(1., 0.6); - - let rect = Rect { position, size }; - assert_approx_eq!( - rect / 2., - Rect { - position: Vec2::new(1., 2.25), - size: Vec2::new(0.2, 4.5) - } - ); - - let rect = RoundRect { - position, - size, - radius, - }; - assert_approx_eq!( - rect / 2., - RoundRect { - position: Vec2::new(1., 2.25), - size: Vec2::new(0.2, 4.5), - radius: Vec2::new(0.1, 0.3) - } - ); - } - - #[test] - fn test_rect_div_assign_f64() { - let position = Vec2::new(3., 2.25); - let size = Vec2::new(1.5, 1.); - let radius = Vec2::new(0.6, 0.45); - - let mut rect = Rect { position, size }; - rect /= 1.5; - assert_approx_eq!( - rect, - Rect { - position: Vec2::new(2., 1.5), - size: Vec2::new(1., 2. / 3.) - } - ); - - let mut rect = RoundRect { - position, - size, - radius, - }; - rect /= 1.5; - assert_approx_eq!( - rect, - RoundRect { - position: Vec2::new(2., 1.5), - size: Vec2::new(1., 2. / 3.), - radius: Vec2::new(0.4, 0.3) - } - ); - } -} diff --git a/src/utils/geometry/vector.rs b/src/utils/geometry/vector.rs deleted file mode 100644 index ab8f783..0000000 --- a/src/utils/geometry/vector.rs +++ /dev/null @@ -1,441 +0,0 @@ -use std::ops; - -#[derive(Debug, Clone, Copy)] -pub struct Vec2 { - pub x: f64, - pub y: f64, -} - -impl Vec2 { - pub const ZERO: Self = Self::from(0.); - - #[inline] - pub const fn new(x: f64, y: f64) -> Self { - Self { x, y } - } - - #[inline] - pub const fn from(value: f64) -> Self { - Self { x: value, y: value } - } - - #[inline] - pub fn min(self, other: Self) -> Self { - Self { - x: f64::min(self.x, other.x), - y: f64::min(self.y, other.y), - } - } - - #[inline] - pub fn max(self, other: Self) -> Self { - Self { - x: f64::max(self.x, other.x), - y: f64::max(self.y, other.y), - } - } - - #[inline] - pub fn abs(self) -> f64 { - f64::sqrt(self.x * self.x + self.y * self.y) - } - - #[inline] - pub fn arg(self) -> f64 { - f64::atan2(self.y, self.x) - } - - #[inline] - pub fn rotate(self, angle: f64) -> Self { - let (sin, cos) = angle.sin_cos(); - Self { - x: self.x * cos - self.y * sin, - y: self.x * sin + self.y * cos, - } - } -} - -impl PartialEq for Vec2 { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.x == other.x && self.y == other.y - } -} - -impl ops::Neg for Vec2 { - type Output = Self; - - #[inline] - fn neg(self) -> Self::Output { - Self::Output { - x: -self.x, - y: -self.y, - } - } -} - -impl From for (f64, f64) { - fn from(value: Vec2) -> Self { - (value.x, value.y) - } -} - -impl From<(f64, f64)> for Vec2 { - fn from(value: (f64, f64)) -> Self { - Self { - x: value.0, - y: value.1, - } - } -} - -impl ops::Mul for Vec2 -where - f64: ops::Mul, -{ - type Output = Self; - - #[inline] - fn mul(self, rhs: T) -> Self::Output { - Self::Output { - x: self.x * rhs, - y: self.y * rhs, - } - } -} - -impl ops::Mul for f64 { - type Output = Vec2; - - #[inline] - fn mul(self, rhs: Vec2) -> Self::Output { - Self::Output { - x: self * rhs.x, - y: self * rhs.y, - } - } -} - -impl ops::MulAssign for Vec2 -where - f64: ops::MulAssign, -{ - #[inline] - fn mul_assign(&mut self, rhs: T) { - self.x *= rhs; - self.y *= rhs; - } -} - -impl ops::Div for Vec2 -where - f64: ops::Div, -{ - type Output = Self; - - #[inline] - fn div(self, scale: T) -> Self::Output { - Self::Output { - x: self.x / scale, - y: self.y / scale, - } - } -} - -impl ops::DivAssign for Vec2 -where - f64: ops::DivAssign, -{ - #[inline] - fn div_assign(&mut self, scale: T) { - self.x /= scale; - self.y /= scale; - } -} - -impl ops::Add for Vec2 { - type Output = Self; - - #[inline] - fn add(self, other: Vec2) -> Self::Output { - Self::Output { - x: self.x + other.x, - y: self.y + other.y, - } - } -} - -impl ops::AddAssign for Vec2 { - #[inline] - fn add_assign(&mut self, other: Vec2) { - self.x += other.x; - self.y += other.y; - } -} - -impl ops::Sub for Vec2 { - type Output = Self; - - #[inline] - fn sub(self, other: Vec2) -> Self::Output { - Self::Output { - x: self.x - other.x, - y: self.y - other.y, - } - } -} - -impl ops::SubAssign for Vec2 { - #[inline] - fn sub_assign(&mut self, other: Vec2) { - self.x -= other.x; - self.y -= other.y; - } -} - -impl ops::Mul for Vec2 { - type Output = Self; - - #[inline] - fn mul(self, scale: Vec2) -> Self::Output { - Self::Output { - x: self.x * scale.x, - y: self.y * scale.y, - } - } -} - -impl ops::MulAssign for Vec2 { - #[inline] - fn mul_assign(&mut self, scale: Vec2) { - self.x *= scale.x; - self.y *= scale.y; - } -} - -impl ops::Div for Vec2 { - type Output = Self; - - #[inline] - fn div(self, rhs: Self) -> Self::Output { - Self::Output { - x: self.x / rhs.x, - y: self.y / rhs.y, - } - } -} - -impl ops::DivAssign for Vec2 { - #[inline] - fn div_assign(&mut self, scale: Vec2) { - self.x /= scale.x; - self.y /= scale.y; - } -} - -#[cfg(test)] -mod tests { - use std::f64::consts::PI; - - use super::*; - - use assert_approx_eq::assert_approx_eq; - - #[test] - fn test_vector_new() { - let vec = Vec2::new(2., 4.5); - - assert_approx_eq!(vec.x, 2.); - assert_approx_eq!(vec.y, 4.5); - } - - #[test] - fn test_vector_from_number() { - let vec = Vec2::from(3.2); - - assert_approx_eq!(vec.x, 3.2); - assert_approx_eq!(vec.y, 3.2); - } - - #[test] - fn test_vector_min() { - let vec1 = Vec2 { x: 2., y: 4.5 }; - let vec2 = Vec2 { x: 3., y: 0.2 }; - - let min = Vec2::min(vec1, vec2); - - assert_approx_eq!(min.x, 2.); - assert_approx_eq!(min.y, 0.2); - } - - #[test] - fn test_vector_max() { - let vec1 = Vec2 { x: 2., y: 4.5 }; - let vec2 = Vec2 { x: 3., y: 0.2 }; - - let min = Vec2::max(vec1, vec2); - - assert_approx_eq!(min.x, 3.); - assert_approx_eq!(min.y, 4.5); - } - - #[test] - fn test_vector_abs() { - let vec = Vec2 { x: 1.5, y: 2. }; - - assert_approx_eq!(vec.abs(), 2.5); - } - - #[test] - fn test_vector_arg() { - let vec = Vec2 { x: 1., y: 1. }; - - assert_approx_eq!(vec.arg(), PI / 4.); - } - - #[test] - fn test_vector_rotate() { - let vec = Vec2 { x: 1., y: 1. }; - - assert_approx_eq!(vec.rotate(PI / 2.), Vec2 { x: -1., y: 1. }); - } - - #[test] - fn test_vector_eq() { - let point1 = Vec2 { x: 1., y: 3. }; - let point2 = Vec2 { x: 1., y: 0.2 }; - let point3 = Vec2 { x: 1., y: 3. }; - - assert_ne!(point1, point2); - assert_eq!(point1, point3); - } - - #[test] - fn test_vector_neg() { - let point = Vec2 { x: 1., y: 3. }; - - assert_approx_eq!(-point, Vec2 { x: -1., y: -3. }); - } - - #[test] - fn test_vector_into() { - let point: (f64, f64) = Vec2 { x: 1., y: 3. }.into(); - - assert_eq!(point, (1., 3.)); - } - - #[test] - fn test_vector_from_tuple() { - let point: Vec2 = (1., 3.).into(); - - assert_eq!(point, Vec2 { x: 1., y: 3. }); - } - - #[test] - fn test_vector_add() { - let point = Vec2 { x: 2., y: 4.5 }; - let size = Vec2 { x: 3., y: 0.2 }; - - assert_approx_eq!(point + size, Vec2 { x: 5., y: 4.7 }); - } - - #[test] - fn test_vector_add_assign() { - let mut point = Vec2 { x: 3., y: 2.3 }; - let size = Vec2 { x: 1., y: 1.5 }; - - point += size; - - assert_approx_eq!(point, Vec2 { x: 4., y: 3.8 }); - } - - #[test] - fn test_vector_sub() { - let point = Vec2 { x: 2., y: 4.5 }; - let size = Vec2 { x: 3., y: 0.2 }; - - assert_approx_eq!(point - size, Vec2 { x: -1., y: 4.3 }); - } - - #[test] - fn test_vector_sub_assign() { - let mut point = Vec2 { x: 3., y: 2.3 }; - let size = Vec2 { x: 1., y: 1.5 }; - - point -= size; - - assert_approx_eq!(point, Vec2 { x: 2., y: 0.8 }); - } - - #[test] - fn test_vector_mul() { - let point = Vec2 { x: 2., y: 4.5 }; - let scale = Vec2 { x: 1.2, y: 1. / 9. }; - - assert_approx_eq!(point * scale, Vec2 { x: 2.4, y: 0.5 }); - } - - #[test] - fn test_vector_mul_assign() { - let mut point = Vec2 { x: 3., y: 2.3 }; - let scale = Vec2 { - x: -1., - y: 1. / 2.3, - }; - - point *= scale; - - assert_approx_eq!(point, Vec2 { x: -3., y: 1. }); - } - - #[test] - fn test_vector_mul_f64() { - let point = Vec2 { x: 2., y: 4.5 }; - - assert_approx_eq!(point * 2., Vec2 { x: 4., y: 9. }); - assert_approx_eq!(2. * point, Vec2 { x: 4., y: 9. }); - } - - #[test] - fn test_vector_mul_assign_f64() { - let mut point = Vec2 { x: 3., y: 2.3 }; - - point *= 1.4; - - assert_approx_eq!(point, Vec2 { x: 4.2, y: 3.22 }); - } - - #[test] - fn test_vector_div() { - let point = Vec2 { x: 2., y: 4.5 }; - let scale = Vec2 { x: 1.2, y: 9. }; - - assert_approx_eq!(point / scale, Vec2 { x: 5. / 3., y: 0.5 }); - } - - #[test] - fn test_vector_div_assign() { - let mut point = Vec2 { x: 3., y: 2.3 }; - let scale = Vec2 { x: -1., y: 0.23 }; - - point /= scale; - - assert_approx_eq!(point, Vec2 { x: -3., y: 10. }); - } - - #[test] - fn test_vector_div_f64() { - let point = Vec2 { x: 2., y: 4.5 }; - - assert_approx_eq!(point / 2., Vec2 { x: 1., y: 2.25 }); - } - - #[test] - fn test_vector_div_assign_f64() { - let mut point = Vec2 { x: 3., y: 2.25 }; - - point /= 1.5; - - assert_approx_eq!(point, Vec2 { x: 2., y: 1.5 }); - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 078a53e..8b35232 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,26 @@ mod color; -mod geometry; -mod path; pub(crate) use color::Color; -pub(crate) use geometry::{Rect, RoundRect, Vec2}; -pub(crate) use path::{Path, PathSegment}; + +// Shim to allow us to use assert_approx_eq with Kurbo types +#[cfg(test)] +mod kurbo_shim { + pub trait KurboAbs { + fn abs(&self) -> f64; + } + + impl KurboAbs for kurbo::Vec2 { + fn abs(&self) -> f64 { + self.length() + } + } + + impl KurboAbs for kurbo::Size { + fn abs(&self) -> f64 { + self.to_vec2().length() + } + } +} + +#[cfg(test)] +pub(crate) use kurbo_shim::*; diff --git a/src/utils/path/arc_to_bezier.rs b/src/utils/path/arc_to_bezier.rs deleted file mode 100644 index 2c6db3d..0000000 --- a/src/utils/path/arc_to_bezier.rs +++ /dev/null @@ -1,378 +0,0 @@ -use std::f64::consts::{FRAC_PI_2, PI}; - -use crate::utils::Vec2; - -const TOL: f64 = 1e-6; - -pub fn arc_to_bezier(r: Vec2, xar: f64, laf: bool, sf: bool, d: Vec2) -> Vec<(Vec2, Vec2, Vec2)> { - if d.abs() < TOL { - return vec![]; - } - - // Ensure our radii are large enough - // If either radius is 0 we just return a straight line - let r = Vec2::new(r.x.abs(), r.y.abs()); - if r.x < TOL || r.y < TOL { - return vec![(d / 3., d * (2. / 3.), d)]; - } - - // Rotate the point by -xar. We calculate the result as if xar==0 and then re-rotate the result - // It's a lot easier this way, I swear - let d = d.rotate(-xar); - - // Scale the radii up if they can't meet the distance, maintaining their ratio - let lambda = (d / (r * 2.)).abs().max(1.); - let r = r * lambda; - - let c = get_center(r, laf, sf, d); - - let phi0 = (-c / r).arg(); - - let dphi = ((d - c) / r).arg() - phi0; - - // Add and subtract 2pi (360 deg) to make sure dphi is the correct angle to sweep - let dphi = match (laf, sf) { - (true, true) if dphi < PI => dphi + 2. * PI, - (true, false) if dphi > -PI => dphi - 2. * PI, - (false, true) if dphi < 0. => dphi + 2. * PI, - (false, false) if dphi > 0. => dphi - 2. * PI, - _ => dphi, - }; - - // Double checks the quadrant of dphi - // TODO remove these? They shouldn't ever fail I think aside from the odd tolerance issue - // match (laf, sf) { - // (false, false) => assert!((-PI..=0.).contains(&dphi)), - // (false, true) => assert!((0. ..=PI).contains(&dphi)), - // (true, false) => assert!((-(2. * PI)..=-PI).contains(&dphi)), - // (true, true) => assert!((PI..=(2. * PI)).contains(&dphi)), - // } - - // Subtract TOL so 90.0001 deg doesn't become 2 segs - let segments = ((dphi / FRAC_PI_2).abs() - TOL).ceil(); - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let i_segments = segments as u8; // u8 is fine since segments <= 4 - let dphi = dphi / segments; - - (0..i_segments) - .map(|i| phi0 + f64::from(i) * dphi) // Starting angle for segment - .map(|phi0| create_arc(r, phi0, dphi)) // Create seggment arc - .map(|(ctrl1, ctrl2, point)| { - // Re-rotate by xar - let [ctrl1, ctrl2, point] = [ctrl1, ctrl2, point].map(|p| p.rotate(xar)); - (ctrl1, ctrl2, point) - }) - .collect() -} - -fn get_center(r: Vec2, laf: bool, sf: bool, d: Vec2) -> Vec2 { - // Since we only use half d in this calculation, pre-halve it - let d_2 = d / 2.; - - let sign = if laf == sf { 1. } else { -1. }; - - let expr = (r.x * d_2.y).powi(2) + (r.y * d_2.x).powi(2); - let v = ((r.x * r.y).powi(2) - expr) / expr; - - let co = if v.abs() < TOL { 0. } else { sign * v.sqrt() }; - let c = Vec2::new(r.x * d_2.y / r.y, -r.y * d_2.x / r.x); - - c * co + d_2 -} - -fn create_arc(r: Vec2, phi0: f64, dphi: f64) -> (Vec2, Vec2, Vec2) { - let a = (4. / 3.) * (dphi / 4.).tan(); - - let swap = |(a, b)| (b, a); - let d1: Vec2 = swap(phi0.sin_cos()).into(); - let d4: Vec2 = swap((phi0 + dphi).sin_cos()).into(); - - let d2 = Vec2::new(d1.x - d1.y * a, d1.y + d1.x * a); - let d3 = Vec2::new(d4.x + d4.y * a, d4.y - d4.x * a); - - ((d2 - d1) * r, (d3 - d1) * r, (d4 - d1) * r) -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::f64::consts::{FRAC_PI_2, PI, SQRT_2}; - - use assert_approx_eq::assert_approx_eq; - - use crate::utils::Vec2; - - #[test] - fn test_arc_to_bezier() { - struct Params { - r: Vec2, - xar: f64, - laf: bool, - sf: bool, - d: Vec2, - exp: Vec, - } - let params = vec![ - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: false, - sf: false, - d: Vec2::new(1., 1.), - exp: vec![Vec2::new(1., 1.)], - }, - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: true, - sf: false, - d: Vec2::new(1., 1.), - exp: vec![Vec2::new(-1., 1.), Vec2::new(1., 1.), Vec2::new(1., -1.)], - }, - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: true, - sf: true, - d: Vec2::new(1., 1.), - exp: vec![Vec2::new(1., -1.), Vec2::new(1., 1.), Vec2::new(-1., 1.)], - }, - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: true, - sf: true, - d: Vec2::new(1., -1.), - exp: vec![Vec2::new(-1., -1.), Vec2::new(1., -1.), Vec2::new(1., 1.)], - }, - Params { - r: Vec2::new(1., 2.), - xar: 0., - laf: false, - sf: false, - d: Vec2::new(1., 2.), - exp: vec![Vec2::new(1., 2.)], - }, - Params { - r: Vec2::new(1., 2.), - xar: FRAC_PI_2, - laf: false, - sf: false, - d: Vec2::new(2., -1.), - exp: vec![Vec2::new(2., -1.)], - }, - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: false, - sf: false, - d: Vec2::ZERO, - exp: vec![], - }, - Params { - r: Vec2::new(SQRT_2, SQRT_2), - xar: 0., - laf: false, - sf: true, - d: Vec2::new(0., -2.), - exp: vec![Vec2::new(0., -2.)], - }, - Params { - r: Vec2::new(SQRT_2, SQRT_2), - xar: 0., - laf: false, - sf: false, - d: Vec2::new(0., 2.), - exp: vec![Vec2::new(0., 2.)], - }, - Params { - r: Vec2::new(1., 1.), - xar: 0., - laf: false, - sf: false, - d: Vec2::new(4., 0.), - exp: vec![Vec2::new(2., 2.), Vec2::new(2., -2.)], - }, - Params { - r: Vec2::ZERO, - xar: 0., - laf: false, - sf: false, - d: Vec2::new(1., 0.), - exp: vec![Vec2::new(1., 0.)], - }, - ]; - - for Params { - r, - xar, - laf, - sf, - d, - exp, - } in params - { - let points = arc_to_bezier(r, xar, laf, sf, d); - let points = points.into_iter().map(|i| i.2); - - assert_eq!(points.len(), exp.len()); - for (pnt, res) in points.zip(exp) { - assert_approx_eq!(pnt.x, res.x); - assert_approx_eq!(pnt.y, res.y); - } - } - } - - #[test] - fn test_get_center() { - struct Params { - r: Vec2, - laf: bool, - sf: bool, - d: Vec2, - exp: Vec2, - } - let params = vec![ - Params { - r: Vec2::new(1., 1.), - laf: false, - sf: false, - d: Vec2::new(1., 1.), - exp: Vec2::new(1., 0.), - }, - Params { - r: Vec2::new(1., 1.), - laf: true, - sf: false, - d: Vec2::new(1., 1.), - exp: Vec2::new(0., 1.), - }, - Params { - r: Vec2::new(1., 1.), - laf: false, - sf: true, - d: Vec2::new(1., 1.), - exp: Vec2::new(0., 1.), - }, - Params { - r: Vec2::new(1., 1.), - laf: true, - sf: true, - d: Vec2::new(1., 1.), - exp: Vec2::new(1., 0.), - }, - Params { - r: Vec2::new(1., 1.), - laf: false, - sf: false, - d: Vec2::new(2., 0.), - exp: Vec2::new(1., 0.), - }, - ]; - - for Params { r, laf, sf, d, exp } in params { - let point = get_center(r, laf, sf, d); - assert_approx_eq!(point.x, exp.x); - assert_approx_eq!(point.y, exp.y); - } - } - - #[test] - fn test_create_arc() { - const A: f64 = (4. / 3.) * (SQRT_2 - 1.); // (4 / 3) * tan(90deg / 4) - struct Params { - r: Vec2, - phi0: f64, - dphi: f64, - p: (Vec2, Vec2, Vec2), - } - let params = vec![ - Params { - r: Vec2::new(1., 1.), - phi0: 0., - dphi: FRAC_PI_2, - p: (Vec2::new(0., A), Vec2::new(A - 1., 1.), Vec2::new(-1., 1.)), - }, - Params { - r: Vec2::new(1., 1.), - phi0: FRAC_PI_2, - dphi: FRAC_PI_2, - p: ( - Vec2::new(-A, 0.), - Vec2::new(-1., A - 1.), - Vec2::new(-1., -1.), - ), - }, - Params { - r: Vec2::new(1., 1.), - phi0: PI, - dphi: FRAC_PI_2, - p: ( - Vec2::new(0., -A), - Vec2::new(1. - A, -1.), - Vec2::new(1., -1.), - ), - }, - Params { - r: Vec2::new(1., 1.), - phi0: -FRAC_PI_2, - dphi: FRAC_PI_2, - p: (Vec2::new(A, 0.), Vec2::new(1., 1. - A), Vec2::new(1., 1.)), - }, - Params { - r: Vec2::new(1., 1.), - phi0: 0., - dphi: -FRAC_PI_2, - p: ( - Vec2::new(0., -A), - Vec2::new(A - 1., -1.), - Vec2::new(-1., -1.), - ), - }, - Params { - r: Vec2::new(1., 1.), - phi0: FRAC_PI_2, - dphi: -FRAC_PI_2, - p: (Vec2::new(A, 0.), Vec2::new(1., A - 1.), Vec2::new(1., -1.)), - }, - Params { - r: Vec2::new(1., 1.), - phi0: PI, - dphi: -FRAC_PI_2, - p: (Vec2::new(0., A), Vec2::new(1. - A, 1.), Vec2::new(1., 1.)), - }, - Params { - r: Vec2::new(1., 1.), - phi0: -FRAC_PI_2, - dphi: -FRAC_PI_2, - p: ( - Vec2::new(-A, 0.), - Vec2::new(-1., 1. - A), - Vec2::new(-1., 1.), - ), - }, - Params { - r: Vec2::new(2., 1.), - phi0: 0., - dphi: FRAC_PI_2, - p: ( - Vec2::new(0., A), - Vec2::new(2. * (A - 1.), 1.), - Vec2::new(-2., 1.), - ), - }, - ]; - - for Params { r, phi0, dphi, p } in params { - let points = create_arc(r, phi0, dphi); - - assert_approx_eq!(p.0.x, points.0.x); - assert_approx_eq!(p.0.y, points.0.y); - assert_approx_eq!(p.1.x, points.1.x); - assert_approx_eq!(p.1.y, points.1.y); - assert_approx_eq!(p.2.x, points.2.x); - assert_approx_eq!(p.2.y, points.2.y); - } - } -} diff --git a/src/utils/path/mod.rs b/src/utils/path/mod.rs deleted file mode 100644 index b5463bb..0000000 --- a/src/utils/path/mod.rs +++ /dev/null @@ -1,502 +0,0 @@ -mod arc_to_bezier; -mod segment; - -use arc_to_bezier::arc_to_bezier; -pub use segment::PathSegment; - -use super::{Rect, Vec2}; - -#[derive(Debug, Clone)] -pub struct Path { - pub data: Vec, - start: Vec2, - point: Vec2, - pub bounds: Rect, -} - -impl Path { - pub fn new() -> Self { - Self { - data: vec![], - start: Vec2::ZERO, - point: Vec2::ZERO, - bounds: Rect::new(Vec2::ZERO, Vec2::ZERO), - } - } - - pub fn append(&mut self, other: Self) { - if other.data.is_empty() { - // Do nothing - } else if self.data.is_empty() { - *self = other; - } else { - // Add leading move to 0,0 if we don't already start with a move - if !matches!(other.data[0], PathSegment::Move(..)) { - self.data.push(PathSegment::Move(Vec2::ZERO)); - } - self.data.extend(other.data); - - let sp = ( - self.bounds.position(), - self.bounds.position() + self.bounds.size(), - ); - let op = ( - other.bounds.position(), - other.bounds.position() + other.bounds.size(), - ); - self.bounds = Rect::from_points(Vec2::min(sp.0, op.0), Vec2::max(sp.1, op.1)); - - self.start = other.start; - self.point = other.point; - } - } - - pub fn add(mut self, other: Self) -> Self { - self.append(other); - self - } - - pub fn rel_move(&mut self, d: Vec2) { - let point = self.point; - self.abs_move(point + d); - } - - pub fn rel_line(&mut self, d: Vec2) { - self.data.push(PathSegment::Line(d)); - self.point += d; - self.bounds = Self::update_bounds(self.bounds, self.point); - } - - pub fn rel_horiz_line(&mut self, dx: f64) { - let point = self.point; - self.abs_horiz_line(point.x + dx); - } - - pub fn rel_vert_line(&mut self, dy: f64) { - let point = self.point; - self.abs_vert_line(point.y + dy); - } - - pub fn rel_cubic_bezier(&mut self, d1: Vec2, d2: Vec2, d: Vec2) { - self.data.push(PathSegment::CubicBezier(d1, d2, d)); - self.point += d; - self.bounds = Self::update_bounds(self.bounds, self.point); - } - - pub fn rel_smooth_cubic_bezier(&mut self, d2: Vec2, d: Vec2) { - let d1 = match self.data.last() { - Some(&PathSegment::CubicBezier(_, prev_d2, prev_d)) => prev_d - prev_d2, - _ => Vec2::ZERO, - }; - self.rel_cubic_bezier(d1, d2, d); - } - - pub fn rel_quadratic_bezier(&mut self, d1: Vec2, d: Vec2) { - self.data.push(PathSegment::QuadraticBezier(d1, d)); - self.point += d; - self.bounds = Self::update_bounds(self.bounds, self.point); - } - - pub fn rel_smooth_quadratic_bezier(&mut self, d: Vec2) { - let d1 = match self.data.last() { - Some(&PathSegment::QuadraticBezier(prev_d2, prev_d)) => prev_d - prev_d2, - _ => Vec2::ZERO, - }; - self.rel_quadratic_bezier(d1, d); - } - - pub fn rel_arc(&mut self, r: Vec2, xar: f64, laf: bool, sf: bool, d: Vec2) { - for (d1, d2, d) in arc_to_bezier(r, xar, laf, sf, d) { - self.data.push(PathSegment::CubicBezier(d1, d2, d)); - self.point += d; - self.bounds = Self::update_bounds(self.bounds, self.point); - } - } - - pub fn close(&mut self) { - self.data.push(PathSegment::Close); - self.point = self.start; - } - - pub fn abs_move(&mut self, p: Vec2) { - self.bounds = if self.data.is_empty() { - Rect::from_points(p, p) - } else { - Self::update_bounds(self.bounds, p) - }; - self.data.push(PathSegment::Move(p)); - self.start = p; - self.point = p; - } - - pub fn abs_line(&mut self, p: Vec2) { - let point = self.point; - self.rel_line(p - point); - } - - pub fn abs_horiz_line(&mut self, x: f64) { - let point = Vec2::new(x, self.point.y); - self.abs_line(point); - } - - pub fn abs_vert_line(&mut self, y: f64) { - let point = Vec2::new(self.point.x, y); - self.abs_line(point); - } - - pub fn abs_cubic_bezier(&mut self, p1: Vec2, p2: Vec2, p: Vec2) { - let point = self.point; - self.rel_cubic_bezier(p1 - point, p2 - point, p - point); - } - - pub fn abs_smooth_cubic_bezier(&mut self, p2: Vec2, p: Vec2) { - let point = self.point; - self.rel_smooth_cubic_bezier(p2 - point, p - point); - } - - pub fn abs_quadratic_bezier(&mut self, p1: Vec2, p: Vec2) { - let point = self.point; - self.rel_quadratic_bezier(p1 - point, p - point); - } - - pub fn abs_smooth_quadratic_bezier(&mut self, p: Vec2) { - let point = self.point; - self.rel_smooth_quadratic_bezier(p - point); - } - - pub fn abs_arc(&mut self, r: Vec2, xar: f64, laf: bool, sf: bool, p: Vec2) { - let point = self.point; - self.rel_arc(r, xar, laf, sf, p - point); - } - - pub fn scale(&mut self, scale: Vec2) { - self.data.iter_mut().for_each(|s| s.scale(scale)); - self.start *= scale; - self.point *= scale; - self.bounds *= scale; - } - - pub fn translate(&mut self, dist: Vec2) { - self.data.iter_mut().for_each(|s| s.translate(dist)); - self.start += dist; - self.point += dist; - self.bounds = Rect::new(self.bounds.position() + dist, self.bounds.size()); - } - - pub fn rotate(&mut self, angle: f64) { - self.data.iter_mut().for_each(|s| s.rotate(angle)); - self.start = self.start.rotate(angle); - self.point = self.point.rotate(angle); - self.bounds = Self::recalculate_bounds(&self.data); - } - - pub fn skew_x(&mut self, angle: f64) { - let tan = angle.tan(); - self.data.iter_mut().for_each(|s| s.skew_x(angle)); - self.start += Vec2::new(-self.start.y * tan, 0.); - self.point += Vec2::new(-self.point.y * tan, 0.); - self.bounds = Self::recalculate_bounds(&self.data); - } - - pub fn skew_y(&mut self, angle: f64) { - let tan = angle.tan(); - self.data.iter_mut().for_each(|s| s.skew_y(angle)); - self.start += Vec2::new(0., self.start.x * tan); - self.point += Vec2::new(0., self.point.x * tan); - self.bounds = Self::recalculate_bounds(&self.data); - } - - fn recalculate_bounds(data: &Vec) -> Rect { - use PathSegment::{Close, CubicBezier, Line, Move, QuadraticBezier}; - - if data.is_empty() { - Rect::new(Vec2::ZERO, Vec2::ZERO) - } else { - let mov = if let Move(_) = data[0] { - None - } else { - // Add leading move to 0,0 if we don't already start with a move - Some(Move(Vec2::ZERO)) - }; - // We need a reference here since we don't want to consume self.data - let (min, max) = mov - .iter() - .chain(data.iter()) - .filter(|seg| !matches!(seg, Close)) - .scan(Vec2::ZERO, |point, seg| { - *point = match *seg { - Move(p) => p, - Line(d) | CubicBezier(_, _, d) | QuadraticBezier(_, d) => *point + d, - Close => unreachable!(), - }; - Some(*point) - }) - .fold( - (Vec2::from(f64::INFINITY), Vec2::from(f64::NEG_INFINITY)), - |(min, max), p| (Vec2::min(min, p), Vec2::max(max, p)), - ); - - Rect::from_points(min, max) - } - } - - fn update_bounds(bounds: Rect, p: Vec2) -> Rect { - let (pt1, pt2) = (bounds.position(), bounds.position() + bounds.size()); - Rect::from_points(Vec2::min(pt1, p), Vec2::max(pt2, p)) - } -} - -impl Default for Path { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; - - use assert_approx_eq::assert_approx_eq; - - #[test] - fn test_path_new() { - let paths = vec![Path::new(), Path::default()]; - - for path in paths { - assert!(path.data.is_empty()); - assert_approx_eq!(path.start, Vec2::ZERO); - assert_approx_eq!(path.point, Vec2::ZERO); - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::ZERO)); - } - } - - #[test] - fn test_path_add() { - let empty = Path::new(); - let mut line1 = Path::new(); - line1.abs_line(Vec2::new(1., 1.)); - - let mut line2 = Path::new(); - line2.abs_line(Vec2::new(1., 0.)); - - let mut line3 = Path::new(); - line3.abs_move(Vec2::new(0., 1.)); - line3.abs_line(Vec2::new(1., 0.)); - - let mut angle = Path::new(); - angle.abs_line(Vec2::new(1., 1.)); - angle.abs_move(Vec2::ZERO); - angle.abs_line(Vec2::new(1., 0.)); - - let mut cross = Path::new(); - cross.abs_line(Vec2::new(1., 1.)); - cross.abs_move(Vec2::new(0., 1.)); - cross.abs_line(Vec2::new(1., 0.)); - - let params = vec![ - (empty.clone(), empty.clone(), empty.clone()), - (line1.clone(), empty.clone(), line1.clone()), - (empty.clone(), line1.clone(), line1.clone()), - (line1.clone(), line2.clone(), angle.clone()), - (line1.clone(), line3.clone(), cross.clone()), - ]; - - for (first, second, expected) in params { - let result = first.add(second); - - assert_eq!(result.data.len(), expected.data.len()); - assert_approx_eq!(result.start, expected.start); - assert_approx_eq!(result.point, expected.point); - assert_approx_eq!(result.bounds, expected.bounds); - } - } - - #[test] - fn test_commands() { - let mut r#move = Path::new(); - r#move.abs_move(Vec2::ZERO); - r#move.rel_move(Vec2::new(2., 2.)); - r#move.close(); - - let mut line = Path::new(); - line.abs_line(Vec2::new(1., 1.)); - line.rel_line(Vec2::new(1., 1.)); - line.close(); - - let mut vert_horiz = Path::new(); - vert_horiz.abs_vert_line(2.); - vert_horiz.rel_horiz_line(2.); - vert_horiz.close(); - - let mut horiz_vert = Path::new(); - horiz_vert.abs_horiz_line(2.); - horiz_vert.rel_vert_line(2.); - horiz_vert.close(); - - let mut curve1 = Path::new(); - curve1.abs_cubic_bezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)); - curve1.rel_smooth_quadratic_bezier(Vec2::new(1., 1.)); - - let mut curve2 = Path::new(); - curve2.abs_quadratic_bezier(Vec2::new(0., 1.), Vec2::new(1., 1.)); - curve2.rel_smooth_cubic_bezier(Vec2::new(1., 0.5), Vec2::new(1., 1.)); - - let mut curve3 = Path::new(); - curve3.rel_cubic_bezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)); - curve3.abs_smooth_cubic_bezier(Vec2::new(2., 1.5), Vec2::new(2., 2.)); - curve3.close(); - - let mut curve4 = Path::new(); - curve4.rel_quadratic_bezier(Vec2::new(0., 1.), Vec2::new(1., 1.)); - curve4.abs_smooth_quadratic_bezier(Vec2::new(2., 2.)); - curve4.close(); - - let mut curve5 = Path::new(); - curve5.abs_smooth_cubic_bezier(Vec2::new(0., 2.), Vec2::new(2., 2.)); - - let mut curve6 = Path::new(); - curve6.abs_smooth_quadratic_bezier(Vec2::new(2., 2.)); - - let mut arc = Path::new(); - arc.abs_arc(Vec2::new(1., 1.), 0., false, false, Vec2::new(1., 1.)); - arc.rel_arc(Vec2::new(1., 1.), 0., false, true, Vec2::new(1., 1.)); - - let params = vec![ - r#move, line, vert_horiz, horiz_vert, curve1, curve2, curve3, curve4, curve5, curve6, - arc, - ]; - - for path in params { - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(2., 2.))); - } - } - - #[test] - fn test_path_scale() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - let mut scale1 = path.clone(); - scale1.scale(Vec2::new(0.5, 0.5)); - - let mut scale2 = path.clone(); - scale2.scale(Vec2::new(-1., -2.)); - - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 1.))); - assert_approx_eq!(scale1.bounds, Rect::new(Vec2::ZERO, Vec2::new(2., 0.5))); - assert_approx_eq!( - scale2.bounds, - Rect::new(Vec2::new(-4., -2.), Vec2::new(4., 2.)) - ); - } - - #[test] - fn test_translate() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - let mut translate = path.clone(); - translate.translate(Vec2::new(2., 1.)); - - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 1.))); - assert_approx_eq!( - translate.bounds, - Rect::new(Vec2::new(2., 1.), Vec2::new(4., 1.)) - ); - } - - #[test] - fn test_rotate() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - let mut rotate = path.clone(); - rotate.rotate(FRAC_PI_2); - - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 1.))); - assert_approx_eq!( - rotate.bounds, - Rect::new(Vec2::new(-1., 0.), Vec2::new(1., 4.)) - ); - } - - #[test] - fn test_skew_x() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - let mut skew = path.clone(); - skew.skew_x(-FRAC_PI_4); - - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 1.))); - assert_approx_eq!(skew.bounds, Rect::new(Vec2::ZERO, Vec2::new(5., 1.))); - } - - #[test] - fn test_skew_y() { - let mut path = Path::new(); - path.abs_move(Vec2::ZERO); - path.rel_line(Vec2::new(1., 1.)); - path.rel_cubic_bezier(Vec2::new(0.5, 0.5), Vec2::new(1.5, 0.5), Vec2::new(2., 0.)); - path.rel_quadratic_bezier(Vec2::new(0.5, -0.5), Vec2::new(1., 0.)); - path.close(); - - let mut skew = path.clone(); - skew.skew_y(FRAC_PI_4); - - assert_approx_eq!(path.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 1.))); - assert_approx_eq!(skew.bounds, Rect::new(Vec2::ZERO, Vec2::new(4., 5.))); - } - - #[test] - fn test_recalculate_bounds() { - let mut path1 = Path::new(); - path1.abs_line(Vec2::new(1., 1.)); - path1.close(); - - let mut path2 = Path::new(); - path2.abs_move(Vec2::ZERO); - path2.abs_line(Vec2::new(1., -1.)); - - let mut path3 = Path::new(); - path3.abs_cubic_bezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)); - path3.abs_quadratic_bezier(Vec2::new(2., 1.), Vec2::new(2., 2.)); - - let params = vec![ - (Path::new(), Rect::new(Vec2::ZERO, Vec2::ZERO)), - (path1.clone(), Rect::new(Vec2::ZERO, Vec2::new(1., 1.))), - ( - path2.clone(), - Rect::new(Vec2::new(0., -1.), Vec2::new(1., 1.)), - ), - (path3.clone(), Rect::new(Vec2::ZERO, Vec2::new(2., 2.))), - ( - path1.add(path2), - Rect::new(Vec2::new(0., -1.), Vec2::new(1., 2.)), - ), - ]; - - for (path, expected) in params { - let bounds = Path::recalculate_bounds(&path.data); - assert_eq!(bounds, expected); - } - } -} diff --git a/src/utils/path/segment.rs b/src/utils/path/segment.rs deleted file mode 100644 index 22edb06..0000000 --- a/src/utils/path/segment.rs +++ /dev/null @@ -1,255 +0,0 @@ -use crate::utils::Vec2; - -use PathSegment::{Close, CubicBezier, Line, Move, QuadraticBezier}; - -// Move is absolute, others are all relative -#[derive(Debug, Clone)] -pub enum PathSegment { - Move(Vec2), - Line(Vec2), - CubicBezier(Vec2, Vec2, Vec2), - QuadraticBezier(Vec2, Vec2), - Close, -} - -impl PathSegment { - pub fn scale(&mut self, scale: Vec2) { - match self { - Move(point) => *point *= scale, - Line(dist) => *dist *= scale, - CubicBezier(ctrl1, ctrl2, dist) => { - *ctrl1 *= scale; - *ctrl2 *= scale; - *dist *= scale; - } - QuadraticBezier(ctrl1, dist) => { - *ctrl1 *= scale; - *dist *= scale; - } - Close => (), - } - } - - pub fn translate(&mut self, dist: Vec2) { - // Everything else is relative distance - if let Move(point) = self { - *point += dist; - } - } - - pub fn rotate(&mut self, angle: f64) { - match self { - Move(point) => *point = point.rotate(angle), - Line(dist) => *dist = dist.rotate(angle), - CubicBezier(c1, c2, d) => { - *c1 = c1.rotate(angle); - *c2 = c2.rotate(angle); - *d = d.rotate(angle); - } - QuadraticBezier(c1, d) => { - *c1 = c1.rotate(angle); - *d = d.rotate(angle); - } - Close => (), - } - } - - pub fn skew_x(&mut self, angle: f64) { - let tan = angle.tan(); - match self { - Move(point) => point.x -= point.y * tan, - Line(dist) => dist.x -= dist.y * tan, - CubicBezier(c1, c2, d) => { - c1.x -= c1.y * tan; - c2.x -= c2.y * tan; - d.x -= d.y * tan; - } - QuadraticBezier(c1, d) => { - c1.x -= c1.y * tan; - d.x -= d.y * tan; - } - Close => (), - } - } - - pub fn skew_y(&mut self, angle: f64) { - let tan = angle.tan(); - match self { - Move(point) => point.y += point.x * tan, - Line(dist) => dist.y += dist.x * tan, - CubicBezier(c1, c2, d) => { - c1.y += c1.x * tan; - c2.y += c2.x * tan; - d.y += d.x * tan; - } - QuadraticBezier(c1, d) => { - c1.y += c1.x * tan; - d.y += d.x * tan; - } - Close => (), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; - use std::ops::Sub; - - use assert_approx_eq::assert_approx_eq; - - // Needed to implement assert_approx_eq!() - impl Sub for PathSegment { - type Output = f64; - fn sub(self, rhs: PathSegment) -> Self::Output { - match (self, rhs) { - (Move(p1), Move(p2)) => (p2 - p1).abs(), - (Line(d1), Line(d2)) => (d2 - d1).abs(), - (CubicBezier(c11, c21, d1), CubicBezier(c12, c22, d2)) => { - [(c12 - c11).abs(), (c22 - c21).abs(), (d2 - d1).abs()] - .into_iter() - .map(|x| x * x) - .sum() - } - (QuadraticBezier(c1, d1), QuadraticBezier(c2, d2)) => { - [(c2 - c1).abs(), (d2 - d1).abs()] - .into_iter() - .map(|x| x * x) - .sum() - } - (Close, Close) => 0., - (_, _) => unreachable!(), // Different variants - } - } - } - - // Needed to implement assert_approx_eq!() - impl Copy for PathSegment {} - - #[test] - fn test_scale() { - let input = vec![ - Move(Vec2::new(1., 1.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - let expected = vec![ - Move(Vec2::new(2., 2.)), - Line(Vec2::new(2., 2.)), - CubicBezier(Vec2::new(0., 1.), Vec2::new(1., 2.), Vec2::new(2., 2.)), - QuadraticBezier(Vec2::new(0., 2.), Vec2::new(2., 2.)), - Close, - ]; - - assert_eq!(input.len(), expected.len()); - for (inp, exp) in input.into_iter().zip(expected) { - let mut res = inp; - res.scale(Vec2::new(2., 2.)); - assert_approx_eq!(res, exp); - } - } - - #[test] - fn test_translate() { - let input = vec![ - Move(Vec2::new(1., 1.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - let expected = vec![ - Move(Vec2::new(2., 2.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - - assert_eq!(input.len(), expected.len()); - for (inp, exp) in input.into_iter().zip(expected) { - let mut res = inp; - res.translate(Vec2::new(1., 1.)); - assert_approx_eq!(res, exp); - } - } - - #[test] - fn test_rotate() { - let input = vec![ - Move(Vec2::new(1., 1.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - let expected = vec![ - Move(Vec2::new(-1., 1.)), - Line(Vec2::new(-1., 1.)), - CubicBezier(Vec2::new(-0.5, 0.), Vec2::new(-1., 0.5), Vec2::new(-1., 1.)), - QuadraticBezier(Vec2::new(-1., 0.), Vec2::new(-1., 1.)), - Close, - ]; - - assert_eq!(input.len(), expected.len()); - for (inp, exp) in input.into_iter().zip(expected) { - let mut res = inp; - res.rotate(FRAC_PI_2); - assert_approx_eq!(res, exp); - } - } - - #[test] - fn test_skew_x() { - let input = vec![ - Move(Vec2::new(1., 1.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - let expected = vec![ - Move(Vec2::new(0., 1.)), - Line(Vec2::new(0., 1.)), - CubicBezier(Vec2::new(-0.5, 0.5), Vec2::new(-0.5, 1.), Vec2::new(0., 1.)), - QuadraticBezier(Vec2::new(-1., 1.), Vec2::new(0., 1.)), - Close, - ]; - - assert_eq!(input.len(), expected.len()); - for (inp, exp) in input.into_iter().zip(expected) { - let mut res = inp; - res.skew_x(FRAC_PI_4); - assert_approx_eq!(res, exp); - } - } - - #[test] - fn test_skew_y() { - let input = vec![ - Move(Vec2::new(1., 1.)), - Line(Vec2::new(1., 1.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.), Vec2::new(1., 1.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 1.)), - Close, - ]; - let expected = vec![ - Move(Vec2::new(1., 2.)), - Line(Vec2::new(1., 2.)), - CubicBezier(Vec2::new(0., 0.5), Vec2::new(0.5, 1.5), Vec2::new(1., 2.)), - QuadraticBezier(Vec2::new(0., 1.), Vec2::new(1., 2.)), - Close, - ]; - - assert_eq!(input.len(), expected.len()); - for (inp, exp) in input.into_iter().zip(expected) { - let mut res = inp; - res.skew_y(FRAC_PI_4); - assert_approx_eq!(res, exp); - } - } -}