Skip to main content

pathfinder_content/
stroke.rs

1// pathfinder/content/src/stroke.rs
2//
3// Copyright © 2019 The Pathfinder Project Developers.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! Utilities for converting path strokes to fills.
12
13use crate::outline::{ArcDirection, Contour, ContourIterFlags, Outline, PushSegmentFlags};
14use crate::segment::Segment;
15use pathfinder_geometry::line_segment::LineSegment2F;
16use pathfinder_geometry::rect::RectF;
17use pathfinder_geometry::transform2d::Transform2F;
18use pathfinder_geometry::util::EPSILON;
19use pathfinder_geometry::vector::{Vector2F, vec2f};
20use std::f32;
21
22const TOLERANCE: f32 = 0.01;
23
24pub struct OutlineStrokeToFill<'a> {
25    input: &'a Outline,
26    output: Outline,
27    style: StrokeStyle,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct StrokeStyle {
32    pub line_width: f32,
33    pub line_cap: LineCap,
34    pub line_join: LineJoin,
35}
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub enum LineCap {
39    Butt,
40    Square,
41    Round,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub enum LineJoin {
46    Miter(f32),
47    Bevel,
48    Round,
49}
50
51impl<'a> OutlineStrokeToFill<'a> {
52    #[inline]
53    pub fn new(input: &Outline, style: StrokeStyle) -> OutlineStrokeToFill {
54        OutlineStrokeToFill { input, output: Outline::new(), style }
55    }
56
57    pub fn offset(&mut self) {
58        let mut new_contours = vec![];
59        for input in &self.input.contours {
60            let closed = input.closed;
61            let mut stroker = ContourStrokeToFill::new(input,
62                                                       Contour::new(),
63                                                       self.style.line_width * 0.5,
64                                                       self.style.line_join);
65
66            stroker.offset_forward();
67            if closed {
68                self.push_stroked_contour(&mut new_contours, stroker, true);
69                stroker = ContourStrokeToFill::new(input,
70                                                   Contour::new(),
71                                                   self.style.line_width * 0.5,
72                                                   self.style.line_join);
73            } else {
74                self.add_cap(&mut stroker.output);
75            }
76
77            stroker.offset_backward();
78            if !closed {
79                self.add_cap(&mut stroker.output);
80            }
81
82            self.push_stroked_contour(&mut new_contours, stroker, closed);
83        }
84
85        let mut new_bounds = None;
86        new_contours.iter().for_each(|contour| contour.update_bounds(&mut new_bounds));
87
88        self.output.contours = new_contours;
89        self.output.bounds = new_bounds.unwrap_or_else(|| RectF::default());
90    }
91
92    #[inline]
93    pub fn into_outline(self) -> Outline {
94        self.output
95    }
96
97    fn push_stroked_contour(&mut self,
98                            new_contours: &mut Vec<Contour>,
99                            mut stroker: ContourStrokeToFill,
100                            closed: bool) {
101        // Add join if necessary.
102        if closed && stroker.output.might_need_join(self.style.line_join) {
103            let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0));
104            let final_segment = LineSegment2F::new(p1, p0);
105            stroker.output.add_join(self.style.line_width * 0.5,
106                                    self.style.line_join,
107                                    stroker.input.position_of(0),
108                                    final_segment);
109        }
110
111        stroker.output.closed = true;
112        new_contours.push(stroker.output);
113    }
114
115    fn add_cap(&mut self, contour: &mut Contour) {
116        if self.style.line_cap == LineCap::Butt || contour.len() < 2 {
117            return
118        }
119
120        let width = self.style.line_width;
121        let p1 = contour.position_of_last(1);
122
123        // Determine the ending gradient.
124        let mut p0;
125        let mut p0_index = contour.len() - 2;
126        loop {
127            p0 = contour.position_of(p0_index);
128            if (p1 - p0).square_length() > EPSILON {
129                break;
130            }
131            if p0_index == 0 {
132                return;
133            }
134            p0_index -= 1;
135        }
136        let gradient = (p1 - p0).normalize();
137
138        match self.style.line_cap {
139            LineCap::Butt => unreachable!(),
140
141            LineCap::Square => {
142                let offset = gradient * (width * 0.5);
143
144                let p2 = p1 + offset;
145                let p3 = p2 + gradient.yx() * vec2f(-width, width);
146                let p4 = p3 - offset;
147
148                contour.push_endpoint(p2);
149                contour.push_endpoint(p3);
150                contour.push_endpoint(p4);
151            }
152
153            LineCap::Round => {
154                let scale = width * 0.5;
155                let offset = gradient.yx() * vec2f(-1.0, 1.0);
156                let translation = p1 + offset * (width * 0.5);
157                let transform = Transform2F::from_scale(scale).translate(translation);
158                let chord = LineSegment2F::new(-offset, offset);
159                contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
160            }
161        }
162    }
163}
164
165struct ContourStrokeToFill<'a> {
166    input: &'a Contour,
167    output: Contour,
168    radius: f32,
169    join: LineJoin,
170}
171
172impl<'a> ContourStrokeToFill<'a> {
173    #[inline]
174    fn new(input: &Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
175        ContourStrokeToFill { input, output, radius, join }
176    }
177
178    fn offset_forward(&mut self) {
179        for (segment_index, segment) in self.input.iter(ContourIterFlags::empty()).enumerate() {
180            // FIXME(pcwalton): We negate the radius here so that round end caps can be drawn
181            // clockwise. Of course, we should just implement anticlockwise arcs to begin with...
182            let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
183            segment.offset(-self.radius, join, &mut self.output);
184        }
185    }
186
187    fn offset_backward(&mut self) {
188        let mut segments: Vec<_> = self
189            .input
190            .iter(ContourIterFlags::empty())
191            .map(|segment| segment.reversed())
192            .collect();
193        segments.reverse();
194        for (segment_index, segment) in segments.iter().enumerate() {
195            // FIXME(pcwalton): We negate the radius here so that round end caps can be drawn
196            // clockwise. Of course, we should just implement anticlockwise arcs to begin with...
197            let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
198            segment.offset(-self.radius, join, &mut self.output);
199        }
200    }
201}
202
203trait Offset {
204    fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour);
205    fn add_to_contour(&self,
206                      distance: f32,
207                      join: LineJoin,
208                      join_point: Vector2F,
209                      contour: &mut Contour);
210    fn offset_once(&self, distance: f32) -> Self;
211    fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
212}
213
214impl Offset for Segment {
215    fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) {
216        let join_point = self.baseline.from();
217        if self.baseline.square_length() < TOLERANCE * TOLERANCE {
218            self.add_to_contour(distance, join, join_point, contour);
219            return;
220        }
221
222        let candidate = self.offset_once(distance);
223        if self.error_is_within_tolerance(&candidate, distance) {
224            candidate.add_to_contour(distance, join, join_point, contour);
225            return;
226        }
227
228        debug!("--- SPLITTING ---");
229        debug!("... PRE-SPLIT: {:?}", self);
230        let (before, after) = self.split(0.5);
231        debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
232        before.offset(distance, join, contour);
233        after.offset(distance, join, contour);
234    }
235
236    fn add_to_contour(&self,
237                      distance: f32,
238                      join: LineJoin,
239                      join_point: Vector2F,
240                      contour: &mut Contour) {
241        // Add join if necessary.
242        if contour.might_need_join(join) {
243            let p3 = self.baseline.from();
244            let p4 = if self.is_line() {
245                self.baseline.to()
246            } else {
247                // NB: If you change the representation of quadratic curves, you will need to
248                // change this.
249                self.ctrl.from()
250            };
251
252            contour.add_join(distance, join, join_point, LineSegment2F::new(p4, p3));
253        }
254
255        // Push segment.
256        let flags = PushSegmentFlags::UPDATE_BOUNDS | PushSegmentFlags::INCLUDE_FROM_POINT;
257        contour.push_segment(self, flags);
258    }
259
260    fn offset_once(&self, distance: f32) -> Segment {
261        if self.is_line() {
262            return Segment::line(self.baseline.offset(distance));
263        }
264
265        if self.is_quadratic() {
266            let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
267            let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.baseline.to());
268            segment_0 = segment_0.offset(distance);
269            segment_1 = segment_1.offset(distance);
270            let ctrl = match segment_0.intersection_t(segment_1) {
271                Some(t) => segment_0.sample(t),
272                None => segment_0.to().lerp(segment_1.from(), 0.5),
273            };
274            let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
275            return Segment::quadratic(baseline, ctrl);
276        }
277
278        debug_assert!(self.is_cubic());
279
280        if self.baseline.from() == self.ctrl.from() {
281            let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.to());
282            let mut segment_1 = LineSegment2F::new(self.ctrl.to(), self.baseline.to());
283            segment_0 = segment_0.offset(distance);
284            segment_1 = segment_1.offset(distance);
285            let ctrl = match segment_0.intersection_t(segment_1) {
286                Some(t) => segment_0.sample(t),
287                None => segment_0.to().lerp(segment_1.from(), 0.5),
288            };
289            let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
290            let ctrl = LineSegment2F::new(segment_0.from(), ctrl);
291            return Segment::cubic(baseline, ctrl);
292        }
293
294        if self.ctrl.to() == self.baseline.to() {
295            let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
296            let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.baseline.to());
297            segment_0 = segment_0.offset(distance);
298            segment_1 = segment_1.offset(distance);
299            let ctrl = match segment_0.intersection_t(segment_1) {
300                Some(t) => segment_0.sample(t),
301                None => segment_0.to().lerp(segment_1.from(), 0.5),
302            };
303            let baseline = LineSegment2F::new(segment_0.from(), segment_1.to());
304            let ctrl = LineSegment2F::new(ctrl, segment_1.to());
305            return Segment::cubic(baseline, ctrl);
306        }
307
308        let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
309        let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.ctrl.to());
310        let mut segment_2 = LineSegment2F::new(self.ctrl.to(), self.baseline.to());
311        segment_0 = segment_0.offset(distance);
312        segment_1 = segment_1.offset(distance);
313        segment_2 = segment_2.offset(distance);
314        let (ctrl_0, ctrl_1) = match (
315            segment_0.intersection_t(segment_1),
316            segment_1.intersection_t(segment_2),
317        ) {
318            (Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)),
319            _ => (
320                segment_0.to().lerp(segment_1.from(), 0.5),
321                segment_1.to().lerp(segment_2.from(), 0.5),
322            ),
323        };
324        let baseline = LineSegment2F::new(segment_0.from(), segment_2.to());
325        let ctrl = LineSegment2F::new(ctrl_0, ctrl_1);
326        Segment::cubic(baseline, ctrl)
327    }
328
329    fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool {
330        let (mut min, mut max) = (
331            f32::abs(distance) - TOLERANCE,
332            f32::abs(distance) + TOLERANCE,
333        );
334        min = if min <= 0.0 { 0.0 } else { min * min };
335        max = if max <= 0.0 { 0.0 } else { max * max };
336
337        for t_num in 0..(SAMPLE_COUNT + 1) {
338            let t = t_num as f32 / SAMPLE_COUNT as f32;
339            // FIXME(pcwalton): Use signed distance!
340            let (this_p, other_p) = (self.sample(t), other.sample(t));
341            let vector = this_p - other_p;
342            let square_distance = vector.square_length();
343            debug!(
344                "this_p={:?} other_p={:?} vector={:?} sqdist={:?} min={:?} max={:?}",
345                this_p, other_p, vector, square_distance, min, max
346            );
347            if square_distance < min || square_distance > max {
348                return false;
349            }
350        }
351
352        return true;
353
354        const SAMPLE_COUNT: u32 = 16;
355    }
356}
357
358impl Contour {
359    fn might_need_join(&self, join: LineJoin) -> bool {
360        if self.len() < 2 {
361            false
362        } else {
363            match join {
364                LineJoin::Miter(_) | LineJoin::Round => true,
365                LineJoin::Bevel => false,
366            }
367        }
368    }
369
370    fn add_join(&mut self,
371                distance: f32,
372                join: LineJoin,
373                join_point: Vector2F,
374                next_tangent: LineSegment2F) {
375        let (p0, p1) = (self.position_of_last(2), self.position_of_last(1));
376        let prev_tangent = LineSegment2F::new(p0, p1);
377
378        if prev_tangent.square_length() < EPSILON || next_tangent.square_length() < EPSILON {
379            return;
380        }
381
382        match join {
383            LineJoin::Bevel => {}
384            LineJoin::Miter(miter_limit) => {
385                if let Some(prev_tangent_t) = prev_tangent.intersection_t(next_tangent) {
386                    if prev_tangent_t < -EPSILON {
387                        return;
388                    }
389                    let miter_endpoint = prev_tangent.sample(prev_tangent_t);
390                    let threshold = miter_limit * distance;
391                    if (miter_endpoint - join_point).square_length() > threshold * threshold {
392                        return;
393                    }
394                    self.push_endpoint(miter_endpoint);
395                }
396            }
397            LineJoin::Round => {
398                let scale = distance.abs();
399                let transform = Transform2F::from_scale(scale).translate(join_point);
400                let chord_from = (prev_tangent.to() - join_point).normalize();
401                let chord_to = (next_tangent.to() - join_point).normalize();
402                let chord = LineSegment2F::new(chord_from, chord_to);
403                self.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
404            }
405        }
406    }
407}
408
409impl Default for StrokeStyle {
410    #[inline]
411    fn default() -> StrokeStyle {
412        StrokeStyle {
413            line_width: 1.0,
414            line_cap: LineCap::default(),
415            line_join: LineJoin::default(),
416        }
417    }
418}
419
420impl Default for LineCap {
421    #[inline]
422    fn default() -> LineCap { LineCap::Butt }
423}
424
425impl Default for LineJoin {
426    #[inline]
427    fn default() -> LineJoin { LineJoin::Miter(10.0) }
428}