From 541ad28f64abab3552478b9c92453a3f0f194a01 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Tue, 19 May 2020 18:26:17 +0300 Subject: [PATCH 01/10] add Outline::merge to combine two outlines --- content/src/outline.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/content/src/outline.rs b/content/src/outline.rs index 8cb4f229..4ad31cde 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -218,6 +218,20 @@ impl Outline { pub fn close_all_contours(&mut self) { self.contours.iter_mut().for_each(|contour| contour.close()); } + + pub fn merge(&mut self, other: Outline) { + if other.len() == 0 { + return; + } + + if self.len() == 0 { + self.bounds = other.bounds; + } else { + self.bounds = self.bounds.union_rect(other.bounds); + } + + self.contours.extend(other.contours); + } } impl Debug for Outline { From 636d5da5896a4be00decbb2b9a1a267a6b423132 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Fri, 29 May 2020 22:42:00 +0300 Subject: [PATCH 02/10] add Outline::transformed --- content/src/outline.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/src/outline.rs b/content/src/outline.rs index 4ad31cde..001f79ec 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -178,6 +178,11 @@ impl Outline { self.bounds = new_bounds.unwrap_or_else(|| RectF::default()); } + pub fn transformed(mut self, transform: &Transform2F) -> Self { + self.transform(transform); + self + } + pub fn apply_perspective(&mut self, perspective: &Perspective) { let mut new_bounds = None; for contour in &mut self.contours { From 7e05972549411bee5d9d6aabbc57476ff2b664b4 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Sun, 14 Jun 2020 08:56:24 +0300 Subject: [PATCH 03/10] add mirror_and_close path operator --- canvas/src/lib.rs | 5 +++++ content/src/outline.rs | 31 +++++++++++++++++++++++++++++++ geometry/src/util.rs | 16 ++++++++++++++++ 3 files changed, 52 insertions(+) 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 + } +} From 26e41b82ff6764ad9c34ee33e879a5444ddcc9b4 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Wed, 17 Jun 2020 08:00:41 +0300 Subject: [PATCH 04/10] add Outline::with_capacity, Contour::transformed and a small fix --- content/src/outline.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/content/src/outline.rs b/content/src/outline.rs index ad22caf1..99847f18 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -62,6 +62,14 @@ impl Outline { } } + #[inline] + pub fn with_capacity(capacity: usize) -> Outline { + Outline { + contours: Vec::with_capacity(capacity), + bounds: RectF::default(), + } + } + #[inline] pub fn from_segments(segments: I) -> Outline where @@ -275,7 +283,7 @@ impl Contour { #[inline] pub fn from_rect(rect: RectF) -> Contour { - let mut contour = Contour::new(); + let mut contour = Contour::with_capacity(4); contour.push_point(rect.origin(), PointFlags::empty(), false); contour.push_point(rect.upper_right(), PointFlags::empty(), false); contour.push_point(rect.lower_right(), PointFlags::empty(), false); @@ -614,6 +622,12 @@ impl Contour { } } + #[inline] + pub fn transformed(mut self, transform: &Transform2F) -> Contour { + self.transform(transform); + self + } + pub fn apply_perspective(&mut self, perspective: &Perspective) { for (point_index, point) in self.points.iter_mut().enumerate() { *point = *perspective * *point; From d954cbd05c04e3e676fb14ec0732d03bdf664f7d Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Wed, 17 Jun 2020 08:35:19 +0300 Subject: [PATCH 05/10] add Contour::{from_rect_rounded, push_svg_arc} --- content/src/outline.rs | 132 ++++++++++++++++++++++++++++++++++++++++- content/src/util.rs | 9 +++ 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/content/src/outline.rs b/content/src/outline.rs index 99847f18..30b76f8d 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -14,13 +14,14 @@ use crate::clip::{self, ContourPolygonClipper}; use crate::dilation::ContourDilator; use crate::orientation::Orientation; use crate::segment::{Segment, SegmentFlags, SegmentKind}; +use crate::util::safe_sqrt; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::RectF; -use pathfinder_geometry::transform2d::Transform2F; +use pathfinder_geometry::transform2d::{Transform2F, Matrix2x2F}; use pathfinder_geometry::transform3d::Perspective; use pathfinder_geometry::unit_vector::UnitVector; use pathfinder_geometry::vector::{Vector2F, vec2f}; -use pathfinder_geometry::util::reflection; +use pathfinder_geometry::util::{reflection}; use std::f32::consts::PI; use std::fmt::{self, Debug, Formatter}; use std::mem; @@ -293,6 +294,75 @@ impl Contour { contour } + #[inline] + pub fn from_rect_rounded(rect: RectF, radius: Vector2F) -> Contour { + use std::f32::consts::SQRT_2; + const QUARTER_ARC_CP_FROM_OUTSIDE: f32 = (3.0 - 4.0 * (SQRT_2 - 1.0)) / 3.0; + + if radius.is_zero() { + return Contour::from_rect(rect); + } + let radius = radius.min(rect.size() * 0.5); + let contol_point_offset = radius * QUARTER_ARC_CP_FROM_OUTSIDE; + + let mut contour = Contour::with_capacity(8); + + // upper left corner + { + let p0 = rect.origin(); + let p1 = p0 + contol_point_offset; + let p2 = p0 + radius; + contour.push_endpoint(vec2f(p0.x(), p2.y())); + contour.push_cubic( + vec2f(p0.x(), p1.y()), + vec2f(p1.x(), p0.y()), + vec2f(p2.x(), p0.y()) + ); + } + + // upper right + { + let p0 = rect.upper_right(); + let p1 = p0 + contol_point_offset * vec2f(-1.0, 1.0); + let p2 = p0 + radius * vec2f(-1.0, 1.0); + contour.push_endpoint(vec2f(p2.x(), p0.y())); + contour.push_cubic( + vec2f(p1.x(), p0.y()), + vec2f(p0.x(), p1.y()), + vec2f(p0.x(), p2.y()) + ); + } + + // lower right + { + let p0 = rect.lower_right(); + let p1 = p0 + contol_point_offset * vec2f(-1.0, -1.0); + let p2 = p0 + radius * vec2f(-1.0, -1.0); + contour.push_endpoint(vec2f(p0.x(), p2.y())); + contour.push_cubic( + vec2f(p0.x(), p1.y()), + vec2f(p1.x(), p0.y()), + vec2f(p2.x(), p0.y()) + ); + } + + // lower left + { + let p0 = rect.lower_left(); + let p1 = p0 + contol_point_offset * vec2f(1.0, -1.0); + let p2 = p0 + radius * vec2f(1.0, -1.0); + contour.push_endpoint(vec2f(p2.x(), p0.y())); + contour.push_cubic( + vec2f(p1.x(), p0.y()), + vec2f(p0.x(), p1.y()), + vec2f(p0.x(), p2.y()) + ); + } + + contour.close(); + contour + } + // Replaces this contour with a new one, with arrays preallocated to match `self`. #[inline] pub(crate) fn take(&mut self) -> Contour { @@ -496,6 +566,64 @@ impl Contour { const EPSILON: f32 = 0.001; } + /// Push a SVG arc + /// + /// Draws an ellipse section with radii given in `radius` rotated by `x_axis_rotation` to 'to' in the given `direction`. + /// If `large_arc` is true, draws an arc bigger than a 180°, otherwise smaller than 180°. + /// note that `x_axis_rotation` is in radians. + pub fn push_svg_arc(&mut self, radius: Vector2F, x_axis_rotation: f32, large_arc: bool, direction: ArcDirection, to: Vector2F) { + let r = radius; + let p = to; + let last = self.last_position().unwrap_or_default(); + + if r.x().is_finite() & r.y().is_finite() { + let r = r.abs(); + let r_inv = r.inv(); + let sign = match (large_arc, direction) { + (false, ArcDirection::CW) | (true, ArcDirection::CCW) => 1.0, + (false, ArcDirection::CCW) | (true, ArcDirection::CW) => -1.0 + }; + let rot = Matrix2x2F::from_rotation(x_axis_rotation); + // x' + let q = rot.adjugate() * (last - p) * 0.5; + let q2 = q * q; + + let gamma = q2 * r_inv * r_inv; + let gamma = gamma.x() + gamma.y(); + + let (a, b, c) = if gamma <= 1.0 { + // normal case + let r2 = r * r; + + let r2_prod = r2.x() * r2.y(); // r_x^2 r_y^2 + + let rq2 = r2 * q2.yx(); // (r_x^2 q_y^2, r_y^2 q_x^2) + let rq2_sum = rq2.x() + rq2.y(); // r_x^2 q_y^2 + r_y^2 q_x^2 + // c' + let s = vec2f(1., -1.) * r * (q * r_inv).yx() * safe_sqrt((r2_prod - rq2_sum) / rq2_sum) * sign; + let c = rot * s + (last + p) * 0.5; + + let a = (q - s) * r_inv; + let b = -(q + s) * r_inv; + (a, b, c) + } else { + let c = (last + p) * 0.5; + let a = q * r_inv; + let b = -a; + (a, b, c) + }; + + let transform = Transform2F { + matrix: rot, + vector: c + } * Transform2F::from_scale(r); + let chord = LineSegment2F::new(a, b); + self.push_arc_from_unit_chord(&transform, chord, direction); + } else { + self.push_endpoint(p); + } + } + pub fn push_ellipse(&mut self, transform: &Transform2F) { let segment = Segment::quarter_circle_arc(); let mut rotation; diff --git a/content/src/util.rs b/content/src/util.rs index 8c0c186b..fc4808da 100644 --- a/content/src/util.rs +++ b/content/src/util.rs @@ -45,3 +45,12 @@ pub(crate) fn hash_f32x4(vector: F32x4, state: &mut H) where H: Hasher { data.hash(state); } } + +#[inline] +pub(crate) fn safe_sqrt(x: f32) -> f32 { + if x <= 0.0 { + 0.0 + } else { + x.sqrt() + } +} From 29ef4229a9c691c9faf433582cb143c7d744de67 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Wed, 17 Jun 2020 08:05:39 +0300 Subject: [PATCH 06/10] Add inv, phi and angle_to to Vector2F --- geometry/src/vector.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/geometry/src/vector.rs b/geometry/src/vector.rs index 711818cd..9ea5d12a 100644 --- a/geometry/src/vector.rs +++ b/geometry/src/vector.rs @@ -160,6 +160,29 @@ impl Vector2F { pub fn to_i32(self) -> Vector2I { Vector2I(self.0.to_i32x2()) } + + /// Returns the inverse vector + /// + /// `v · v.inv() = (1.0, 1.0)` + #[inline] + pub fn inv(self) -> Vector2F { + Vector2F::new(1.0 / self.x(), 1.0 / self.y()) + } + + /// Returns the angle of the vector + /// + /// The angle will be in [-PI, PI] + #[inline] + pub fn phi(self) -> f32 { + self.y().atan2(self.x()) + } + + /// Compute the angle between `self` and `other` + /// + /// The angle will be in [-PI, PI] + pub fn angle_to(self, other: Vector2F) -> f32 { + (self.dot(other) / (self.square_length() * other.square_length()).sqrt()).acos() * self.det(other).signum() + } } /// A convenience alias for `Vector2F::new()`. From 6b6a3b324df59650d3dff3cc1a61b43b24090dd9 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Wed, 17 Jun 2020 09:04:27 +0300 Subject: [PATCH 07/10] add Outline::{len, from_rect_rounded} --- content/src/outline.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/content/src/outline.rs b/content/src/outline.rs index 30b76f8d..cbd4c8aa 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -127,6 +127,18 @@ impl Outline { outline } + #[inline] + pub fn from_rect_rounded(rect: RectF, radius: Vector2F) -> Outline { + let mut outline = Outline::new(); + outline.push_contour(Contour::from_rect_rounded(rect, radius)); + outline + } + + #[inline] + pub fn len(&self) -> usize { + self.contours.len() + } + #[inline] pub fn bounds(&self) -> RectF { self.bounds From 496556f8732240577a2f7e5ef26e46bd74a58bac Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Tue, 7 Jul 2020 21:13:47 +0300 Subject: [PATCH 08/10] remove mirror_and_close --- content/src/outline.rs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/content/src/outline.rs b/content/src/outline.rs index cbd4c8aa..bbafb4ae 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -788,31 +788,6 @@ 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 { From 781504732907324728f88e80d8c2680c1763288d Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Tue, 7 Jul 2020 21:17:24 +0300 Subject: [PATCH 09/10] merge --- content/src/outline.rs | 13 ------------- geometry/src/vector.rs | 23 ----------------------- 2 files changed, 36 deletions(-) diff --git a/content/src/outline.rs b/content/src/outline.rs index 739bc20e..9be1ca74 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -72,14 +72,6 @@ impl Outline { } } - #[inline] - pub fn with_capacity(capacity: usize) -> Outline { - Outline { - contours: Vec::with_capacity(capacity), - bounds: RectF::default(), - } - } - #[inline] pub fn from_segments(segments: I) -> Outline where @@ -143,11 +135,6 @@ impl Outline { outline } - #[inline] - pub fn len(&self) -> usize { - self.contours.len() - } - #[inline] pub fn bounds(&self) -> RectF { self.bounds diff --git a/geometry/src/vector.rs b/geometry/src/vector.rs index a722dcd0..fa864f5d 100644 --- a/geometry/src/vector.rs +++ b/geometry/src/vector.rs @@ -178,29 +178,6 @@ impl Vector2F { pub fn to_i32(self) -> Vector2I { Vector2I(self.0.to_i32x2()) } - - /// Returns the inverse vector - /// - /// `v · v.inv() = (1.0, 1.0)` - #[inline] - pub fn inv(self) -> Vector2F { - Vector2F::new(1.0 / self.x(), 1.0 / self.y()) - } - - /// Returns the angle of the vector - /// - /// The angle will be in [-PI, PI] - #[inline] - pub fn phi(self) -> f32 { - self.y().atan2(self.x()) - } - - /// Compute the angle between `self` and `other` - /// - /// The angle will be in [-PI, PI] - pub fn angle_to(self, other: Vector2F) -> f32 { - (self.dot(other) / (self.square_length() * other.square_length()).sqrt()).acos() * self.det(other).signum() - } } /// A convenience alias for `Vector2F::new()`. From bff711131069e8908a3e1d63162203df4c2da864 Mon Sep 17 00:00:00 2001 From: Sebastian K Date: Tue, 7 Jul 2020 21:23:11 +0300 Subject: [PATCH 10/10] remove remainds --- canvas/src/lib.rs | 5 ----- content/src/outline.rs | 3 +-- geometry/src/util.rs | 16 ---------------- 3 files changed, 1 insertion(+), 23 deletions(-) diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index f4070c85..897b96f1 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -806,11 +806,6 @@ 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 9be1ca74..798e786b 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -21,7 +21,6 @@ use pathfinder_geometry::transform2d::{Transform2F, Matrix2x2F}; 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; @@ -603,7 +602,7 @@ impl Contour { if r.x().is_finite() & r.y().is_finite() { let r = r.abs(); - let r_inv = r.inv(); + let r_inv = r.recip(); let sign = match (large_arc, direction) { (false, ArcDirection::CW) | (true, ArcDirection::CCW) => 1.0, (false, ArcDirection::CCW) | (true, ArcDirection::CW) => -1.0 diff --git a/geometry/src/util.rs b/geometry/src/util.rs index ef5e43d8..1add973b 100644 --- a/geometry/src/util.rs +++ b/geometry/src/util.rs @@ -11,8 +11,6 @@ //! Various utilities. use std::f32; -use crate::transform2d::{Transform2F, Matrix2x2F}; -use crate::vector::Vector2F; pub const EPSILON: f32 = 0.001; @@ -39,17 +37,3 @@ 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 - } -}