diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 4de6a028..8f1779d9 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -743,6 +743,11 @@ impl Path2D { self.current_contour = last_contour.unwrap_or_else(Contour::new); } + #[inline] + pub fn mirror_and_close_last(&mut self) { + self.current_contour.mirror_and_close(); + } + pub fn into_outline(mut self) -> Outline { self.flush_current_contour(); self.outline diff --git a/content/src/outline.rs b/content/src/outline.rs index 001f79ec..ad22caf1 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -20,6 +20,7 @@ use pathfinder_geometry::transform2d::Transform2F; use pathfinder_geometry::transform3d::Perspective; use pathfinder_geometry::unit_vector::UnitVector; use pathfinder_geometry::vector::{Vector2F, vec2f}; +use pathfinder_geometry::util::reflection; use std::f32::consts::PI; use std::fmt::{self, Debug, Formatter}; use std::mem; @@ -342,6 +343,11 @@ impl Contour { self.points[index as usize] } + #[inline] + pub fn first_position(&self) -> Option { + self.points.first().cloned() + } + #[inline] pub fn last_position(&self) -> Option { self.points.last().cloned() @@ -628,6 +634,31 @@ impl Contour { Some(bounds) => bounds.union_rect(self.bounds), }) } + + pub fn mirror_and_close(&mut self) { + if self.points.len() < 2 { + return; + } + let a = self.first_position().unwrap(); + let b = self.last_position().unwrap(); + if a == b { + self.close(); + return; + } + let tr = reflection(a, b); + + let mut segments: Vec<_> = self + .iter(ContourIterFlags::empty()) + .map(|segment| segment.reversed().transform(&tr)) + .collect(); + segments.reverse(); + + for segment in &segments { + self.push_segment(segment, PushSegmentFlags::UPDATE_BOUNDS); + } + + self.close(); + } } impl Debug for Contour { diff --git a/geometry/src/util.rs b/geometry/src/util.rs index 1add973b..ef5e43d8 100644 --- a/geometry/src/util.rs +++ b/geometry/src/util.rs @@ -11,6 +11,8 @@ //! Various utilities. use std::f32; +use crate::transform2d::{Transform2F, Matrix2x2F}; +use crate::vector::Vector2F; pub const EPSILON: f32 = 0.001; @@ -37,3 +39,17 @@ pub fn clamp(x: f32, min_val: f32, max_val: f32) -> f32 { pub fn alignup_i32(a: i32, b: i32) -> i32 { (a + b - 1) / b } + +pub fn reflection(a: Vector2F, b: Vector2F) -> Transform2F { + let l = b - a; + let l2 = l * l; + let l2_yx = l2.yx(); + let d = l2 - l2_yx; + let lxy2 = 2.0 * l.x() * l.y(); + let s = 1.0 / (l2.x() + l2.y()); + + Transform2F::from_translation(-a) * Transform2F { + matrix: Matrix2x2F::row_major(d.x(), lxy2, lxy2, d.y()).scale(s), + vector: a + } +}