diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 674bc2d6..2b50c296 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -19,7 +19,7 @@ use pathfinder_geometry::basic::point::Point2DF; use pathfinder_geometry::basic::rect::RectF; use pathfinder_geometry::basic::transform2d::Transform2DF; use pathfinder_geometry::color::ColorU; -use pathfinder_geometry::outline::{Contour, Outline}; +use pathfinder_geometry::outline::{ArcDirection, Contour, Outline}; use pathfinder_geometry::stroke::{LineCap, LineJoin as StrokeLineJoin}; use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle}; use pathfinder_renderer::paint::{Paint, PaintId}; @@ -246,7 +246,7 @@ impl CanvasRenderingContext2D { } #[derive(Clone)] -pub struct State { +struct State { transform: Transform2DF, font_collection: Arc, font_size: f32, @@ -336,10 +336,15 @@ impl Path2D { } #[inline] - pub fn arc(&mut self, center: Point2DF, radius: f32, start_angle: f32, end_angle: f32) { + pub fn arc(&mut self, + center: Point2DF, + radius: f32, + start_angle: f32, + end_angle: f32, + direction: ArcDirection) { let mut transform = Transform2DF::from_scale(Point2DF::splat(radius)); transform = transform.post_mul(&Transform2DF::from_translation(center)); - self.current_contour.push_arc(&transform, start_angle, end_angle); + self.current_contour.push_arc(&transform, start_angle, end_angle, direction); } #[inline] @@ -357,7 +362,9 @@ impl Path2D { let chord = LineSegmentF::new(vu0.yx().scale_xy(Point2DF::new(-1.0, 1.0)), vu1.yx().scale_xy(Point2DF::new(1.0, -1.0))); - self.current_contour.push_arc_from_unit_chord(&transform, chord); + + // FIXME(pcwalton): Is clockwise direction correct? + self.current_contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW); } pub fn rect(&mut self, rect: RectF) { @@ -380,7 +387,7 @@ impl Path2D { let mut transform = Transform2DF::from_rotation(rotation); transform = transform.post_mul(&Transform2DF::from_scale(axes)); transform = transform.post_mul(&Transform2DF::from_translation(center)); - self.current_contour.push_arc(&transform, start_angle, end_angle); + self.current_contour.push_arc(&transform, start_angle, end_angle, ArcDirection::CW); if end_angle - start_angle >= 2.0 * PI { self.current_contour.close(); diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index 77de5648..f22928aa 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -97,6 +97,11 @@ impl LineSegmentF { LineSegmentF(self.0 * F32x4::splat(factor)) } + #[inline] + pub fn scale_xy(&self, factors: Point2DF) -> LineSegmentF { + LineSegmentF(self.0 * factors.0.xyxy()) + } + #[inline] pub fn split(&self, t: f32) -> (LineSegmentF, LineSegmentF) { debug_assert!(t >= 0.0 && t <= 1.0); diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 50bc13fb..c66d0868 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -352,17 +352,30 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } - pub fn push_arc(&mut self, transform: &Transform2DF, start_angle: f32, end_angle: f32) { + pub fn push_arc(&mut self, + transform: &Transform2DF, + start_angle: f32, + end_angle: f32, + direction: ArcDirection) { if end_angle - start_angle >= PI * 2.0 { self.push_ellipse(transform); } else { let start = Point2DF::new(f32::cos(start_angle), f32::sin(start_angle)); let end = Point2DF::new(f32::cos(end_angle), f32::sin(end_angle)); - self.push_arc_from_unit_chord(transform, LineSegmentF::new(start, end)); + self.push_arc_from_unit_chord(transform, LineSegmentF::new(start, end), direction); } } - pub fn push_arc_from_unit_chord(&mut self, transform: &Transform2DF, chord: LineSegmentF) { + pub fn push_arc_from_unit_chord(&mut self, + transform: &Transform2DF, + mut chord: LineSegmentF, + direction: ArcDirection) { + let mut direction_transform = Transform2DF::default(); + if direction == ArcDirection::CCW { + chord = chord.scale_xy(Point2DF::new(-1.0, 1.0)); + direction_transform = Transform2DF::from_scale(Point2DF::new(-1.0, 1.0)); + } + let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to())); let mut first_segment = true; @@ -378,9 +391,10 @@ impl Contour { segment = Segment::arc_from_cos(sweep_vector.0.x()); } - let rotation = - Transform2DF::from_rotation_vector(sweep_vector.halve_angle().rotate_by(vector)); - segment = segment.transform(&rotation.post_mul(&transform)); + let half_sweep_vector = sweep_vector.halve_angle(); + let rotation = Transform2DF::from_rotation_vector(half_sweep_vector.rotate_by(vector)); + segment = segment.transform(&direction_transform.post_mul(&rotation) + .post_mul(&transform)); let mut push_segment_flags = PushSegmentFlags::UPDATE_BOUNDS; if first_segment { @@ -816,6 +830,12 @@ impl<'a> Iterator for ContourIter<'a> { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ArcDirection { + CW, + CCW, +} + #[inline] pub(crate) fn union_rect(bounds: &mut RectF, new_point: Point2DF, first: bool) { if first { diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 579cdf78..fe80cef0 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -14,7 +14,7 @@ use crate::basic::line_segment::LineSegmentF; use crate::basic::point::Point2DF; use crate::basic::rect::RectF; use crate::basic::transform2d::Transform2DF; -use crate::outline::{Contour, Outline, PushSegmentFlags}; +use crate::outline::{ArcDirection, Contour, Outline, PushSegmentFlags}; use crate::segment::Segment; use std::f32; @@ -141,7 +141,8 @@ impl<'a> OutlineStrokeToFill<'a> { let mut transform = Transform2DF::from_scale(scale); let translation = p1 + offset.scale(width * 0.5); transform = transform.post_mul(&Transform2DF::from_translation(translation)); - contour.push_arc_from_unit_chord(&transform, LineSegmentF::new(-offset, offset)); + let chord = LineSegmentF::new(-offset, offset); + contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW); } } } @@ -377,7 +378,8 @@ impl Contour { transform = transform.post_mul(&Transform2DF::from_translation(join_point)); let chord_from = (prev_tangent.to() - join_point).normalize(); let chord_to = (next_tangent.to() - join_point).normalize(); - self.push_arc_from_unit_chord(&transform, LineSegmentF::new(chord_from, chord_to)); + let chord = LineSegmentF::new(chord_from, chord_to); + self.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW); } } }