From 2131724a2a7213e4ae7958f5da19a4a10924c8fe Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 21 May 2019 20:21:46 -0700 Subject: [PATCH] Implement round line caps --- geometry/src/outline.rs | 18 +++++++++++++++--- geometry/src/stroke.rs | 41 +++++++++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 72bcb5cb..75c1cb1f 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -19,10 +19,12 @@ use crate::clip::{self, ContourPolygonClipper, ContourRectClipper}; use crate::dilation::ContourDilator; use crate::orientation::Orientation; use crate::segment::{Segment, SegmentFlags, SegmentKind}; -use std::f32::consts::FRAC_PI_2; +use std::f32::consts::{FRAC_PI_2, PI}; use std::fmt::{self, Debug, Formatter}; use std::mem; +const TWO_PI: f32 = PI * 2.0; + #[derive(Clone)] pub struct Outline { pub(crate) contours: Vec, @@ -367,7 +369,17 @@ impl Contour { self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } - pub fn push_arc(&mut self, center: Point2DF32, radius: f32, start_angle: f32, end_angle: f32) { + pub fn push_arc(&mut self, + center: Point2DF32, + radius: f32, + mut start_angle: f32, + mut end_angle: f32) { + start_angle %= TWO_PI; + end_angle %= TWO_PI; + if end_angle < start_angle { + end_angle += TWO_PI; + } + let scale = Transform2DF32::from_scale(Point2DF32::splat(radius)); let translation = Transform2DF32::from_translation(center); @@ -375,7 +387,7 @@ impl Contour { while angle < end_angle { let sweep_angle = f32::min(FRAC_PI_2, end_angle - angle); let mut segment = Segment::arc(sweep_angle); - let rotation = Transform2DF32::from_rotation(angle); + let rotation = Transform2DF32::from_rotation(sweep_angle * 0.5 + angle); segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation)); debug!("angle={} start_angle={} end_angle={} sweep_angle={} segment={:?}", diff --git a/geometry/src/stroke.rs b/geometry/src/stroke.rs index 02d6a282..75e51019 100644 --- a/geometry/src/stroke.rs +++ b/geometry/src/stroke.rs @@ -15,6 +15,8 @@ use crate::basic::point::Point2DF32; use crate::basic::rect::RectF32; use crate::outline::{Contour, Outline}; use crate::segment::Segment; +use std::f32::consts::FRAC_PI_2; +use std::f32; use std::mem; const TOLERANCE: f32 = 0.01; @@ -35,6 +37,7 @@ pub struct StrokeStyle { pub enum LineCap { Butt, Square, + Round, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -70,9 +73,7 @@ impl OutlineStrokeToFill { } stroker.offset_backward(); - if closed { - // TODO(pcwalton): Line join. - } else { + if !closed { self.add_cap(&mut stroker.output); } @@ -108,15 +109,27 @@ impl OutlineStrokeToFill { let width = self.style.line_width; let (p0, p1) = (contour.position_of_last(2), contour.position_of_last(1)); let gradient = (p1 - p0).normalize(); - let offset = gradient.scale(width * 0.5); - let p2 = p1 + offset; - let p3 = p2 + gradient.yx().scale_xy(Point2DF32::new(width, -width)); - let p4 = p3 - offset; + match self.style.line_cap { + LineCap::Butt => unreachable!(), + LineCap::Square => { + let offset = gradient.scale(width * 0.5); - contour.push_endpoint(p2); - contour.push_endpoint(p3); - contour.push_endpoint(p4); + let p2 = p1 + offset; + let p3 = p2 + gradient.yx().scale_xy(Point2DF32::new(-width, width)); + let p4 = p3 - offset; + + contour.push_endpoint(p2); + contour.push_endpoint(p3); + contour.push_endpoint(p4); + } + LineCap::Round => { + // FIXME(pcwalton): Should we really be using angles here at all? + let offset = gradient.yx().scale_xy(Point2DF32::new(-width * 0.5, width * 0.5)); + let angle = f32::atan2(gradient.y(), gradient.x()); + contour.push_arc(p1 + offset, width * 0.5, angle - FRAC_PI_2, angle + FRAC_PI_2); + } + } } } @@ -135,8 +148,10 @@ impl ContourStrokeToFill { fn offset_forward(&mut self) { for (segment_index, segment) in self.input.iter().enumerate() { + // FIXME(pcwalton): We negate the radius here so that round end caps can be drawn + // clockwise. Of course, we should just implement anticlockwise arcs to begin with... let join = if segment_index == 0 { LineJoin::Bevel } else { self.join }; - segment.offset(self.radius, join, &mut self.output); + segment.offset(-self.radius, join, &mut self.output); } } @@ -148,8 +163,10 @@ impl ContourStrokeToFill { .collect(); segments.reverse(); for (segment_index, segment) in segments.iter().enumerate() { + // FIXME(pcwalton): We negate the radius here so that round end caps can be drawn + // clockwise. Of course, we should just implement anticlockwise arcs to begin with... let join = if segment_index == 0 { LineJoin::Bevel } else { self.join }; - segment.offset(self.radius, join, &mut self.output); + segment.offset(-self.radius, join, &mut self.output); } } }