ressa_path/
stroker.rs

1// Copyright 2008 The Android Open Source Project
2// Copyright 2020 Yevhenii Reizner
3//
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE file.
6
7// Based on SkStroke.cpp
8
9use crate::{Path, Point, Transform};
10
11use crate::dash::StrokeDash;
12use crate::floating_point::{NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive};
13use crate::path::{PathSegment, PathSegmentsIter};
14use crate::path_builder::{PathBuilder, PathDirection};
15use crate::path_geometry;
16use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};
17
18#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
19use crate::NoStdFloat;
20
21struct SwappableBuilders<'a> {
22    inner: &'a mut PathBuilder,
23    outer: &'a mut PathBuilder,
24}
25
26impl<'a> SwappableBuilders<'a> {
27    fn swap(&mut self) {
28        // Skia swaps pointers to inner and outer builders during joining,
29        // but not builders itself. So a simple `core::mem::swap` will produce invalid results.
30        // And if we try to use use `core::mem::swap` on references, like below,
31        // borrow checker will be unhappy.
32        // That's why we need this wrapper. Maybe there is a better solution.
33        core::mem::swap(&mut self.inner, &mut self.outer);
34    }
35}
36
37/// Stroke properties.
38#[derive(Clone, PartialEq, Debug)]
39pub struct Stroke {
40    /// A stroke thickness.
41    ///
42    /// Must be >= 0.
43    ///
44    /// When set to 0, a hairline stroking will be used.
45    ///
46    /// Default: 1.0
47    pub width: f32,
48
49    /// The limit at which a sharp corner is drawn beveled.
50    ///
51    /// Default: 4.0
52    pub miter_limit: f32,
53
54    /// A stroke line cap.
55    ///
56    /// Default: Butt
57    pub line_cap: LineCap,
58
59    /// A stroke line join.
60    ///
61    /// Default: Miter
62    pub line_join: LineJoin,
63
64    /// A stroke dashing properties.
65    ///
66    /// Default: None
67    pub dash: Option<StrokeDash>,
68}
69
70impl Default for Stroke {
71    fn default() -> Self {
72        Stroke {
73            width: 1.0,
74            miter_limit: 4.0,
75            line_cap: LineCap::default(),
76            line_join: LineJoin::default(),
77            dash: None,
78        }
79    }
80}
81
82/// Draws at the beginning and end of an open path contour.
83#[derive(Copy, Clone, PartialEq, Debug)]
84pub enum LineCap {
85    /// No stroke extension.
86    Butt,
87    /// Adds circle.
88    Round,
89    /// Adds square.
90    Square,
91}
92
93impl Default for LineCap {
94    fn default() -> Self {
95        LineCap::Butt
96    }
97}
98
99/// Specifies how corners are drawn when a shape is stroked.
100///
101/// Join affects the four corners of a stroked rectangle, and the connected segments in a
102/// stroked path.
103///
104/// Choose miter join to draw sharp corners. Choose round join to draw a circle with a
105/// radius equal to the stroke width on top of the corner. Choose bevel join to minimally
106/// connect the thick strokes.
107///
108/// The fill path constructed to describe the stroked path respects the join setting but may
109/// not contain the actual join. For instance, a fill path constructed with round joins does
110/// not necessarily include circles at each connected segment.
111#[derive(Copy, Clone, PartialEq, Debug)]
112pub enum LineJoin {
113    /// Extends to miter limit.
114    Miter,
115    /// Adds circle.
116    Round,
117    /// Connects outside edges.
118    Bevel,
119}
120
121impl Default for LineJoin {
122    fn default() -> Self {
123        LineJoin::Miter
124    }
125}
126
127const QUAD_RECURSIVE_LIMIT: usize = 3;
128
129// quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure
130// largest seen for normal cubics: 5, 26
131// largest seen for normal quads: 11
132const RECURSIVE_LIMITS: [i32; 4] = [5 * 3, 26 * 3, 11 * 3, 11 * 3]; // 3x limits seen in practice
133
134type CapProc = fn(
135    pivot: Point,
136    normal: Point,
137    stop: Point,
138    other_path: Option<&PathBuilder>,
139    path: &mut PathBuilder,
140);
141
142type JoinProc = fn(
143    before_unit_normal: Point,
144    pivot: Point,
145    after_unit_normal: Point,
146    radius: f32,
147    inv_miter_limit: f32,
148    prev_is_line: bool,
149    curr_is_line: bool,
150    builders: SwappableBuilders,
151);
152
153#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
154enum ReductionType {
155    Point,       // all curve points are practically identical
156    Line,        // the control point is on the line between the ends
157    Quad,        // the control point is outside the line between the ends
158    Degenerate,  // the control point is on the line but outside the ends
159    Degenerate2, // two control points are on the line but outside ends (cubic)
160    Degenerate3, // three areas of max curvature found (for cubic)
161}
162
163#[derive(Copy, Clone, PartialEq, Debug)]
164enum StrokeType {
165    Outer = 1, // use sign-opposite values later to flip perpendicular axis
166    Inner = -1,
167}
168
169#[derive(Copy, Clone, PartialEq, Debug)]
170enum ResultType {
171    Split,      // the caller should split the quad stroke in two
172    Degenerate, // the caller should add a line
173    Quad,       // the caller should (continue to try to) add a quad stroke
174}
175
176#[derive(Copy, Clone, PartialEq, Debug)]
177enum IntersectRayType {
178    CtrlPt,
179    ResultType,
180}
181
182impl Path {
183    /// Returns a stoked path.
184    ///
185    /// `resolution_scale` can be obtained via
186    /// [`compute_resolution_scale`](PathStroker::compute_resolution_scale).
187    ///
188    /// If you plan stroking multiple paths, you can try using [`PathStroker`]
189    /// which will preserve temporary allocations required during stroking.
190    /// This might improve performance a bit.
191    pub fn stroke(&self, stroke: &Stroke, resolution_scale: f32) -> Option<Path> {
192        PathStroker::new().stroke(self, stroke, resolution_scale)
193    }
194}
195
196/// A path stroker.
197#[allow(missing_debug_implementations)]
198#[derive(Clone)]
199pub struct PathStroker {
200    radius: f32,
201    inv_miter_limit: f32,
202    res_scale: f32,
203    inv_res_scale: f32,
204    inv_res_scale_squared: f32,
205
206    first_normal: Point,
207    prev_normal: Point,
208    first_unit_normal: Point,
209    prev_unit_normal: Point,
210
211    // on original path
212    first_pt: Point,
213    prev_pt: Point,
214
215    first_outer_pt: Point,
216    first_outer_pt_index_in_contour: usize,
217    segment_count: i32,
218    prev_is_line: bool,
219
220    capper: CapProc,
221    joiner: JoinProc,
222
223    // outer is our working answer, inner is temp
224    inner: PathBuilder,
225    outer: PathBuilder,
226    cusper: PathBuilder,
227
228    stroke_type: StrokeType,
229
230    recursion_depth: i32, // track stack depth to abort if numerics run amok
231    found_tangents: bool, // do less work until tangents meet (cubic)
232    join_completed: bool, // previous join was not degenerate
233}
234
235impl Default for PathStroker {
236    fn default() -> Self {
237        PathStroker::new()
238    }
239}
240
241impl PathStroker {
242    /// Creates a new PathStroker.
243    pub fn new() -> Self {
244        PathStroker {
245            radius: 0.0,
246            inv_miter_limit: 0.0,
247            res_scale: 1.0,
248            inv_res_scale: 1.0,
249            inv_res_scale_squared: 1.0,
250
251            first_normal: Point::zero(),
252            prev_normal: Point::zero(),
253            first_unit_normal: Point::zero(),
254            prev_unit_normal: Point::zero(),
255
256            first_pt: Point::zero(),
257            prev_pt: Point::zero(),
258
259            first_outer_pt: Point::zero(),
260            first_outer_pt_index_in_contour: 0,
261            segment_count: -1,
262            prev_is_line: false,
263
264            capper: butt_capper,
265            joiner: miter_joiner,
266
267            inner: PathBuilder::new(),
268            outer: PathBuilder::new(),
269            cusper: PathBuilder::new(),
270
271            stroke_type: StrokeType::Outer,
272
273            recursion_depth: 0,
274            found_tangents: false,
275            join_completed: false,
276        }
277    }
278
279    /// Computes a resolution scale.
280    ///
281    /// Resolution scale is the "intended" resolution for the output. Default is 1.0.
282    ///
283    /// Larger values (res > 1) indicate that the result should be more precise, since it will
284    /// be zoomed up, and small errors will be magnified.
285    ///
286    /// Smaller values (0 < res < 1) indicate that the result can be less precise, since it will
287    /// be zoomed down, and small errors may be invisible.
288    pub fn compute_resolution_scale(ts: &Transform) -> f32 {
289        let sx = Point::from_xy(ts.sx, ts.kx).length();
290        let sy = Point::from_xy(ts.ky, ts.sy).length();
291        if sx.is_finite() && sy.is_finite() {
292            let scale = sx.max(sy);
293            if scale > 0.0 {
294                return scale;
295            }
296        }
297
298        1.0
299    }
300
301    /// Stokes the path.
302    ///
303    /// Can be called multiple times to reuse allocated buffers.
304    ///
305    /// `resolution_scale` can be obtained via
306    /// [`compute_resolution_scale`](Self::compute_resolution_scale).
307    pub fn stroke(&mut self, path: &Path, stroke: &Stroke, resolution_scale: f32) -> Option<Path> {
308        let width = NonZeroPositiveF32::new(stroke.width)?;
309        self.stroke_inner(
310            path,
311            width,
312            stroke.miter_limit,
313            stroke.line_cap,
314            stroke.line_join,
315            resolution_scale,
316        )
317    }
318
319    fn stroke_inner(
320        &mut self,
321        path: &Path,
322        width: NonZeroPositiveF32,
323        miter_limit: f32,
324        line_cap: LineCap,
325        mut line_join: LineJoin,
326        res_scale: f32,
327    ) -> Option<Path> {
328        // TODO: stroke_rect optimization
329
330        let mut inv_miter_limit = 0.0;
331
332        if line_join == LineJoin::Miter {
333            if miter_limit <= 1.0 {
334                line_join = LineJoin::Bevel;
335            } else {
336                inv_miter_limit = miter_limit.invert();
337            }
338        }
339
340        self.res_scale = res_scale;
341        // The '4' below matches the fill scan converter's error term.
342        self.inv_res_scale = (res_scale * 4.0).invert();
343        self.inv_res_scale_squared = self.inv_res_scale.sqr();
344
345        self.radius = width.get().half();
346        self.inv_miter_limit = inv_miter_limit;
347
348        self.first_normal = Point::zero();
349        self.prev_normal = Point::zero();
350        self.first_unit_normal = Point::zero();
351        self.prev_unit_normal = Point::zero();
352
353        self.first_pt = Point::zero();
354        self.prev_pt = Point::zero();
355
356        self.first_outer_pt = Point::zero();
357        self.first_outer_pt_index_in_contour = 0;
358        self.segment_count = -1;
359        self.prev_is_line = false;
360
361        self.capper = cap_factory(line_cap);
362        self.joiner = join_factory(line_join);
363
364        // Need some estimate of how large our final result (fOuter)
365        // and our per-contour temp (fInner) will be, so we don't spend
366        // extra time repeatedly growing these arrays.
367        //
368        // 1x for inner == 'wag' (worst contour length would be better guess)
369        self.inner.clear();
370        self.inner.reserve(path.verbs.len(), path.points.len());
371
372        // 3x for result == inner + outer + join (swag)
373        self.outer.clear();
374        self.outer
375            .reserve(path.verbs.len() * 3, path.points.len() * 3);
376
377        self.cusper.clear();
378
379        self.stroke_type = StrokeType::Outer;
380
381        self.recursion_depth = 0;
382        self.found_tangents = false;
383        self.join_completed = false;
384
385        let mut last_segment_is_line = false;
386        let mut iter = path.segments();
387        iter.set_auto_close(true);
388        while let Some(segment) = iter.next() {
389            match segment {
390                PathSegment::MoveTo(p) => {
391                    self.move_to(p);
392                }
393                PathSegment::LineTo(p) => {
394                    self.line_to(p, Some(&iter));
395                    last_segment_is_line = true;
396                }
397                PathSegment::QuadTo(p1, p2) => {
398                    self.quad_to(p1, p2);
399                    last_segment_is_line = false;
400                }
401                PathSegment::CubicTo(p1, p2, p3) => {
402                    self.cubic_to(p1, p2, p3);
403                    last_segment_is_line = false;
404                }
405                PathSegment::Close => {
406                    if line_cap != LineCap::Butt {
407                        // If the stroke consists of a moveTo followed by a close, treat it
408                        // as if it were followed by a zero-length line. Lines without length
409                        // can have square and round end caps.
410                        if self.has_only_move_to() {
411                            self.line_to(self.move_to_pt(), None);
412                            last_segment_is_line = true;
413                            continue;
414                        }
415
416                        // If the stroke consists of a moveTo followed by one or more zero-length
417                        // verbs, then followed by a close, treat is as if it were followed by a
418                        // zero-length line. Lines without length can have square & round end caps.
419                        if self.is_current_contour_empty() {
420                            last_segment_is_line = true;
421                            continue;
422                        }
423                    }
424
425                    self.close(last_segment_is_line);
426                }
427            }
428        }
429
430        self.finish(last_segment_is_line)
431    }
432
433    fn builders(&mut self) -> SwappableBuilders {
434        SwappableBuilders {
435            inner: &mut self.inner,
436            outer: &mut self.outer,
437        }
438    }
439
440    fn move_to_pt(&self) -> Point {
441        self.first_pt
442    }
443
444    fn move_to(&mut self, p: Point) {
445        if self.segment_count > 0 {
446            self.finish_contour(false, false);
447        }
448
449        self.segment_count = 0;
450        self.first_pt = p;
451        self.prev_pt = p;
452        self.join_completed = false;
453    }
454
455    fn line_to(&mut self, p: Point, iter: Option<&PathSegmentsIter>) {
456        let teeny_line = self
457            .prev_pt
458            .equals_within_tolerance(p, SCALAR_NEARLY_ZERO * self.inv_res_scale);
459        if fn_ptr_eq(self.capper, butt_capper) && teeny_line {
460            return;
461        }
462
463        if teeny_line && (self.join_completed || iter.map(|i| i.has_valid_tangent()) == Some(true))
464        {
465            return;
466        }
467
468        let mut normal = Point::zero();
469        let mut unit_normal = Point::zero();
470        if !self.pre_join_to(p, true, &mut normal, &mut unit_normal) {
471            return;
472        }
473
474        self.outer.line_to(p.x + normal.x, p.y + normal.y);
475        self.inner.line_to(p.x - normal.x, p.y - normal.y);
476
477        self.post_join_to(p, normal, unit_normal);
478    }
479
480    fn quad_to(&mut self, p1: Point, p2: Point) {
481        let quad = [self.prev_pt, p1, p2];
482        let (reduction, reduction_type) = check_quad_linear(&quad);
483        if reduction_type == ReductionType::Point {
484            // If the stroke consists of a moveTo followed by a degenerate curve, treat it
485            // as if it were followed by a zero-length line. Lines without length
486            // can have square and round end caps.
487            self.line_to(p2, None);
488            return;
489        }
490
491        if reduction_type == ReductionType::Line {
492            self.line_to(p2, None);
493            return;
494        }
495
496        if reduction_type == ReductionType::Degenerate {
497            self.line_to(reduction, None);
498            let save_joiner = self.joiner;
499            self.joiner = round_joiner;
500            self.line_to(p2, None);
501            self.joiner = save_joiner;
502            return;
503        }
504
505        debug_assert_eq!(reduction_type, ReductionType::Quad);
506
507        let mut normal_ab = Point::zero();
508        let mut unit_ab = Point::zero();
509        let mut normal_bc = Point::zero();
510        let mut unit_bc = Point::zero();
511        if !self.pre_join_to(p1, false, &mut normal_ab, &mut unit_ab) {
512            self.line_to(p2, None);
513            return;
514        }
515
516        let mut quad_points = QuadConstruct::default();
517        self.init_quad(
518            StrokeType::Outer,
519            NormalizedF32::ZERO,
520            NormalizedF32::ONE,
521            &mut quad_points,
522        );
523        self.quad_stroke(&quad, &mut quad_points);
524        self.init_quad(
525            StrokeType::Inner,
526            NormalizedF32::ZERO,
527            NormalizedF32::ONE,
528            &mut quad_points,
529        );
530        self.quad_stroke(&quad, &mut quad_points);
531
532        let ok = set_normal_unit_normal(
533            quad[1],
534            quad[2],
535            self.res_scale,
536            self.radius,
537            &mut normal_bc,
538            &mut unit_bc,
539        );
540        if !ok {
541            normal_bc = normal_ab;
542            unit_bc = unit_ab;
543        }
544
545        self.post_join_to(p2, normal_bc, unit_bc);
546    }
547
548    fn cubic_to(&mut self, pt1: Point, pt2: Point, pt3: Point) {
549        let cubic = [self.prev_pt, pt1, pt2, pt3];
550        let mut reduction = [Point::zero(); 3];
551        let mut tangent_pt = Point::zero();
552        let reduction_type = check_cubic_linear(&cubic, &mut reduction, Some(&mut tangent_pt));
553        if reduction_type == ReductionType::Point {
554            // If the stroke consists of a moveTo followed by a degenerate curve, treat it
555            // as if it were followed by a zero-length line. Lines without length
556            // can have square and round end caps.
557            self.line_to(pt3, None);
558            return;
559        }
560
561        if reduction_type == ReductionType::Line {
562            self.line_to(pt3, None);
563            return;
564        }
565
566        if ReductionType::Degenerate <= reduction_type
567            && ReductionType::Degenerate3 >= reduction_type
568        {
569            self.line_to(reduction[0], None);
570            let save_joiner = self.joiner;
571            self.joiner = round_joiner;
572            if ReductionType::Degenerate2 <= reduction_type {
573                self.line_to(reduction[1], None);
574            }
575
576            if ReductionType::Degenerate3 == reduction_type {
577                self.line_to(reduction[2], None);
578            }
579
580            self.line_to(pt3, None);
581            self.joiner = save_joiner;
582            return;
583        }
584
585        debug_assert_eq!(reduction_type, ReductionType::Quad);
586        let mut normal_ab = Point::zero();
587        let mut unit_ab = Point::zero();
588        let mut normal_cd = Point::zero();
589        let mut unit_cd = Point::zero();
590        if !self.pre_join_to(tangent_pt, false, &mut normal_ab, &mut unit_ab) {
591            self.line_to(pt3, None);
592            return;
593        }
594
595        let mut t_values = path_geometry::new_t_values();
596        let t_values = path_geometry::find_cubic_inflections(&cubic, &mut t_values);
597        let mut last_t = NormalizedF32::ZERO;
598        for index in 0..=t_values.len() {
599            let next_t = t_values
600                .get(index)
601                .cloned()
602                .map(|n| n.to_normalized())
603                .unwrap_or(NormalizedF32::ONE);
604
605            let mut quad_points = QuadConstruct::default();
606            self.init_quad(StrokeType::Outer, last_t, next_t, &mut quad_points);
607            self.cubic_stroke(&cubic, &mut quad_points);
608            self.init_quad(StrokeType::Inner, last_t, next_t, &mut quad_points);
609            self.cubic_stroke(&cubic, &mut quad_points);
610            last_t = next_t;
611        }
612
613        if let Some(cusp) = path_geometry::find_cubic_cusp(&cubic) {
614            let cusp_loc = path_geometry::eval_cubic_pos_at(&cubic, cusp.to_normalized());
615            self.cusper.push_circle(cusp_loc.x, cusp_loc.y, self.radius);
616        }
617
618        // emit the join even if one stroke succeeded but the last one failed
619        // this avoids reversing an inner stroke with a partial path followed by another moveto
620        self.set_cubic_end_normal(&cubic, normal_ab, unit_ab, &mut normal_cd, &mut unit_cd);
621
622        self.post_join_to(pt3, normal_cd, unit_cd);
623    }
624
625    fn cubic_stroke(&mut self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool {
626        if !self.found_tangents {
627            let result_type = self.tangents_meet(cubic, quad_points);
628            if result_type != ResultType::Quad {
629                let ok = points_within_dist(
630                    quad_points.quad[0],
631                    quad_points.quad[2],
632                    self.inv_res_scale,
633                );
634                if (result_type == ResultType::Degenerate || ok)
635                    && self.cubic_mid_on_line(cubic, quad_points)
636                {
637                    self.add_degenerate_line(quad_points);
638                    return true;
639                }
640            } else {
641                self.found_tangents = true;
642            }
643        }
644
645        if self.found_tangents {
646            let result_type = self.compare_quad_cubic(cubic, quad_points);
647            if result_type == ResultType::Quad {
648                let stroke = &quad_points.quad;
649                if self.stroke_type == StrokeType::Outer {
650                    self.outer
651                        .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y);
652                } else {
653                    self.inner
654                        .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y);
655                }
656
657                return true;
658            }
659
660            if result_type == ResultType::Degenerate {
661                if !quad_points.opposite_tangents {
662                    self.add_degenerate_line(quad_points);
663                    return true;
664                }
665            }
666        }
667
668        if !quad_points.quad[2].x.is_finite() || !quad_points.quad[2].x.is_finite() {
669            return false; // just abort if projected quad isn't representable
670        }
671
672        self.recursion_depth += 1;
673        if self.recursion_depth > RECURSIVE_LIMITS[self.found_tangents as usize] {
674            return false; // just abort if projected quad isn't representable
675        }
676
677        let mut half = QuadConstruct::default();
678        if !half.init_with_start(quad_points) {
679            self.add_degenerate_line(quad_points);
680            self.recursion_depth -= 1;
681            return true;
682        }
683
684        if !self.cubic_stroke(cubic, &mut half) {
685            return false;
686        }
687
688        if !half.init_with_end(quad_points) {
689            self.add_degenerate_line(quad_points);
690            self.recursion_depth -= 1;
691            return true;
692        }
693
694        if !self.cubic_stroke(cubic, &mut half) {
695            return false;
696        }
697
698        self.recursion_depth -= 1;
699        true
700    }
701
702    fn cubic_mid_on_line(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool {
703        let mut stroke_mid = Point::zero();
704        self.cubic_quad_mid(cubic, quad_points, &mut stroke_mid);
705        let dist = pt_to_line(stroke_mid, quad_points.quad[0], quad_points.quad[2]);
706        dist < self.inv_res_scale_squared
707    }
708
709    fn cubic_quad_mid(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct, mid: &mut Point) {
710        let mut cubic_mid_pt = Point::zero();
711        self.cubic_perp_ray(cubic, quad_points.mid_t, &mut cubic_mid_pt, mid, None);
712    }
713
714    // Given a cubic and t, return the point on curve,
715    // its perpendicular, and the perpendicular tangent.
716    fn cubic_perp_ray(
717        &self,
718        cubic: &[Point; 4],
719        t: NormalizedF32,
720        t_pt: &mut Point,
721        on_pt: &mut Point,
722        tangent: Option<&mut Point>,
723    ) {
724        *t_pt = path_geometry::eval_cubic_pos_at(cubic, t);
725        let mut dxy = path_geometry::eval_cubic_tangent_at(cubic, t);
726
727        let mut chopped = [Point::zero(); 7];
728        if dxy.x == 0.0 && dxy.y == 0.0 {
729            let mut c_points: &[Point] = cubic;
730            if t.get().is_nearly_zero() {
731                dxy = cubic[2] - cubic[0];
732            } else if (1.0 - t.get()).is_nearly_zero() {
733                dxy = cubic[3] - cubic[1];
734            } else {
735                // If the cubic inflection falls on the cusp, subdivide the cubic
736                // to find the tangent at that point.
737                //
738                // Unwrap never fails, because we already checked that `t` is not 0/1,
739                let t = NormalizedF32Exclusive::new(t.get()).unwrap();
740                path_geometry::chop_cubic_at2(cubic, t, &mut chopped);
741                dxy = chopped[3] - chopped[2];
742                if dxy.x == 0.0 && dxy.y == 0.0 {
743                    dxy = chopped[3] - chopped[1];
744                    c_points = &chopped;
745                }
746            }
747
748            if dxy.x == 0.0 && dxy.y == 0.0 {
749                dxy = c_points[3] - c_points[0];
750            }
751        }
752
753        self.set_ray_points(*t_pt, &mut dxy, on_pt, tangent);
754    }
755
756    fn set_cubic_end_normal(
757        &mut self,
758        cubic: &[Point; 4],
759        normal_ab: Point,
760        unit_normal_ab: Point,
761        normal_cd: &mut Point,
762        unit_normal_cd: &mut Point,
763    ) {
764        let mut ab = cubic[1] - cubic[0];
765        let mut cd = cubic[3] - cubic[2];
766
767        let mut degenerate_ab = degenerate_vector(ab);
768        let mut degenerate_cb = degenerate_vector(cd);
769
770        if degenerate_ab && degenerate_cb {
771            *normal_cd = normal_ab;
772            *unit_normal_cd = unit_normal_ab;
773            return;
774        }
775
776        if degenerate_ab {
777            ab = cubic[2] - cubic[0];
778            degenerate_ab = degenerate_vector(ab);
779        }
780
781        if degenerate_cb {
782            cd = cubic[3] - cubic[1];
783            degenerate_cb = degenerate_vector(cd);
784        }
785
786        if degenerate_ab || degenerate_cb {
787            *normal_cd = normal_ab;
788            *unit_normal_cd = unit_normal_ab;
789            return;
790        }
791
792        let res = set_normal_unit_normal2(cd, self.radius, normal_cd, unit_normal_cd);
793        debug_assert!(res);
794    }
795
796    fn compare_quad_cubic(
797        &self,
798        cubic: &[Point; 4],
799        quad_points: &mut QuadConstruct,
800    ) -> ResultType {
801        // get the quadratic approximation of the stroke
802        self.cubic_quad_ends(cubic, quad_points);
803        let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points);
804        if result_type != ResultType::Quad {
805            return result_type;
806        }
807
808        // project a ray from the curve to the stroke
809        // points near midpoint on quad, midpoint on cubic
810        let mut ray0 = Point::zero();
811        let mut ray1 = Point::zero();
812        self.cubic_perp_ray(cubic, quad_points.mid_t, &mut ray1, &mut ray0, None);
813        self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points)
814    }
815
816    // Given a cubic and a t range, find the start and end if they haven't been found already.
817    fn cubic_quad_ends(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) {
818        if !quad_points.start_set {
819            let mut cubic_start_pt = Point::zero();
820            self.cubic_perp_ray(
821                cubic,
822                quad_points.start_t,
823                &mut cubic_start_pt,
824                &mut quad_points.quad[0],
825                Some(&mut quad_points.tangent_start),
826            );
827            quad_points.start_set = true;
828        }
829
830        if !quad_points.end_set {
831            let mut cubic_end_pt = Point::zero();
832            self.cubic_perp_ray(
833                cubic,
834                quad_points.end_t,
835                &mut cubic_end_pt,
836                &mut quad_points.quad[2],
837                Some(&mut quad_points.tangent_end),
838            );
839            quad_points.end_set = true;
840        }
841    }
842
843    fn close(&mut self, is_line: bool) {
844        self.finish_contour(true, is_line);
845    }
846
847    fn finish_contour(&mut self, close: bool, curr_is_line: bool) {
848        if self.segment_count > 0 {
849            if close {
850                (self.joiner)(
851                    self.prev_unit_normal,
852                    self.prev_pt,
853                    self.first_unit_normal,
854                    self.radius,
855                    self.inv_miter_limit,
856                    self.prev_is_line,
857                    curr_is_line,
858                    self.builders(),
859                );
860                self.outer.close();
861
862                // now add inner as its own contour
863                let pt = self.inner.last_point().unwrap_or_default();
864                self.outer.move_to(pt.x, pt.y);
865                self.outer.reverse_path_to(&self.inner);
866                self.outer.close();
867            } else {
868                // add caps to start and end
869
870                // cap the end
871                let pt = self.inner.last_point().unwrap_or_default();
872                let other_path = if curr_is_line {
873                    Some(&self.inner)
874                } else {
875                    None
876                };
877                (self.capper)(
878                    self.prev_pt,
879                    self.prev_normal,
880                    pt,
881                    other_path,
882                    &mut self.outer,
883                );
884                self.outer.reverse_path_to(&self.inner);
885
886                // cap the start
887                let other_path = if self.prev_is_line {
888                    Some(&self.inner)
889                } else {
890                    None
891                };
892                (self.capper)(
893                    self.first_pt,
894                    -self.first_normal,
895                    self.first_outer_pt,
896                    other_path,
897                    &mut self.outer,
898                );
899                self.outer.close();
900            }
901
902            if !self.cusper.is_empty() {
903                self.outer.push_path(&self.cusper);
904                self.cusper.clear();
905            }
906        }
907
908        // since we may re-use `inner`, we rewind instead of reset, to save on
909        // reallocating its internal storage.
910        self.inner.clear();
911        self.segment_count = -1;
912        self.first_outer_pt_index_in_contour = self.outer.points.len();
913    }
914
915    fn pre_join_to(
916        &mut self,
917        p: Point,
918        curr_is_line: bool,
919        normal: &mut Point,
920        unit_normal: &mut Point,
921    ) -> bool {
922        debug_assert!(self.segment_count >= 0);
923
924        let prev_x = self.prev_pt.x;
925        let prev_y = self.prev_pt.y;
926
927        let normal_set = set_normal_unit_normal(
928            self.prev_pt,
929            p,
930            self.res_scale,
931            self.radius,
932            normal,
933            unit_normal,
934        );
935        if !normal_set {
936            if fn_ptr_eq(self.capper, butt_capper) {
937                return false;
938            }
939
940            // Square caps and round caps draw even if the segment length is zero.
941            // Since the zero length segment has no direction, set the orientation
942            // to upright as the default orientation.
943            *normal = Point::from_xy(self.radius, 0.0);
944            *unit_normal = Point::from_xy(1.0, 0.0);
945        }
946
947        if self.segment_count == 0 {
948            self.first_normal = *normal;
949            self.first_unit_normal = *unit_normal;
950            self.first_outer_pt = Point::from_xy(prev_x + normal.x, prev_y + normal.y);
951
952            self.outer
953                .move_to(self.first_outer_pt.x, self.first_outer_pt.y);
954            self.inner.move_to(prev_x - normal.x, prev_y - normal.y);
955        } else {
956            // we have a previous segment
957            (self.joiner)(
958                self.prev_unit_normal,
959                self.prev_pt,
960                *unit_normal,
961                self.radius,
962                self.inv_miter_limit,
963                self.prev_is_line,
964                curr_is_line,
965                self.builders(),
966            );
967        }
968        self.prev_is_line = curr_is_line;
969        true
970    }
971
972    fn post_join_to(&mut self, p: Point, normal: Point, unit_normal: Point) {
973        self.join_completed = true;
974        self.prev_pt = p;
975        self.prev_unit_normal = unit_normal;
976        self.prev_normal = normal;
977        self.segment_count += 1;
978    }
979
980    fn init_quad(
981        &mut self,
982        stroke_type: StrokeType,
983        start: NormalizedF32,
984        end: NormalizedF32,
985        quad_points: &mut QuadConstruct,
986    ) {
987        self.stroke_type = stroke_type;
988        self.found_tangents = false;
989        quad_points.init(start, end);
990    }
991
992    fn quad_stroke(&mut self, quad: &[Point; 3], quad_points: &mut QuadConstruct) -> bool {
993        let result_type = self.compare_quad_quad(quad, quad_points);
994        if result_type == ResultType::Quad {
995            let path = if self.stroke_type == StrokeType::Outer {
996                &mut self.outer
997            } else {
998                &mut self.inner
999            };
1000
1001            path.quad_to(
1002                quad_points.quad[1].x,
1003                quad_points.quad[1].y,
1004                quad_points.quad[2].x,
1005                quad_points.quad[2].y,
1006            );
1007
1008            return true;
1009        }
1010
1011        if result_type == ResultType::Degenerate {
1012            self.add_degenerate_line(quad_points);
1013            return true;
1014        }
1015
1016        self.recursion_depth += 1;
1017        if self.recursion_depth > RECURSIVE_LIMITS[QUAD_RECURSIVE_LIMIT] {
1018            return false; // just abort if projected quad isn't representable
1019        }
1020
1021        let mut half = QuadConstruct::default();
1022        half.init_with_start(quad_points);
1023        if !self.quad_stroke(quad, &mut half) {
1024            return false;
1025        }
1026
1027        half.init_with_end(quad_points);
1028        if !self.quad_stroke(quad, &mut half) {
1029            return false;
1030        }
1031
1032        self.recursion_depth -= 1;
1033        true
1034    }
1035
1036    fn compare_quad_quad(
1037        &mut self,
1038        quad: &[Point; 3],
1039        quad_points: &mut QuadConstruct,
1040    ) -> ResultType {
1041        // get the quadratic approximation of the stroke
1042        if !quad_points.start_set {
1043            let mut quad_start_pt = Point::zero();
1044            self.quad_perp_ray(
1045                quad,
1046                quad_points.start_t,
1047                &mut quad_start_pt,
1048                &mut quad_points.quad[0],
1049                Some(&mut quad_points.tangent_start),
1050            );
1051            quad_points.start_set = true;
1052        }
1053
1054        if !quad_points.end_set {
1055            let mut quad_end_pt = Point::zero();
1056            self.quad_perp_ray(
1057                quad,
1058                quad_points.end_t,
1059                &mut quad_end_pt,
1060                &mut quad_points.quad[2],
1061                Some(&mut quad_points.tangent_end),
1062            );
1063            quad_points.end_set = true;
1064        }
1065
1066        let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points);
1067        if result_type != ResultType::Quad {
1068            return result_type;
1069        }
1070
1071        // project a ray from the curve to the stroke
1072        let mut ray0 = Point::zero();
1073        let mut ray1 = Point::zero();
1074        self.quad_perp_ray(quad, quad_points.mid_t, &mut ray1, &mut ray0, None);
1075        self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points)
1076    }
1077
1078    // Given a point on the curve and its derivative, scale the derivative by the radius, and
1079    // compute the perpendicular point and its tangent.
1080    fn set_ray_points(
1081        &self,
1082        tp: Point,
1083        dxy: &mut Point,
1084        on_p: &mut Point,
1085        mut tangent: Option<&mut Point>,
1086    ) {
1087        if !dxy.set_length(self.radius) {
1088            *dxy = Point::from_xy(self.radius, 0.0);
1089        }
1090
1091        let axis_flip = self.stroke_type as i32 as f32; // go opposite ways for outer, inner
1092        on_p.x = tp.x + axis_flip * dxy.y;
1093        on_p.y = tp.y - axis_flip * dxy.x;
1094
1095        if let Some(ref mut tangent) = tangent {
1096            tangent.x = on_p.x + dxy.x;
1097            tangent.y = on_p.y + dxy.y;
1098        }
1099    }
1100
1101    // Given a quad and t, return the point on curve,
1102    // its perpendicular, and the perpendicular tangent.
1103    fn quad_perp_ray(
1104        &self,
1105        quad: &[Point; 3],
1106        t: NormalizedF32,
1107        tp: &mut Point,
1108        on_p: &mut Point,
1109        tangent: Option<&mut Point>,
1110    ) {
1111        *tp = path_geometry::eval_quad_at(quad, t);
1112        let mut dxy = path_geometry::eval_quad_tangent_at(quad, t);
1113
1114        if dxy.is_zero() {
1115            dxy = quad[2] - quad[0];
1116        }
1117
1118        self.set_ray_points(*tp, &mut dxy, on_p, tangent);
1119    }
1120
1121    fn add_degenerate_line(&mut self, quad_points: &QuadConstruct) {
1122        if self.stroke_type == StrokeType::Outer {
1123            self.outer
1124                .line_to(quad_points.quad[2].x, quad_points.quad[2].y);
1125        } else {
1126            self.inner
1127                .line_to(quad_points.quad[2].x, quad_points.quad[2].y);
1128        }
1129    }
1130
1131    fn stroke_close_enough(
1132        &self,
1133        stroke: &[Point; 3],
1134        ray: &[Point; 2],
1135        quad_points: &mut QuadConstruct,
1136    ) -> ResultType {
1137        let half = NormalizedF32::new_clamped(0.5);
1138        let stroke_mid = path_geometry::eval_quad_at(stroke, half);
1139        // measure the distance from the curve to the quad-stroke midpoint, compare to radius
1140        if points_within_dist(ray[0], stroke_mid, self.inv_res_scale) {
1141            // if the difference is small
1142            if sharp_angle(&quad_points.quad) {
1143                return ResultType::Split;
1144            }
1145
1146            return ResultType::Quad;
1147        }
1148
1149        // measure the distance to quad's bounds (quick reject)
1150        // an alternative : look for point in triangle
1151        if !pt_in_quad_bounds(stroke, ray[0], self.inv_res_scale) {
1152            // if far, subdivide
1153            return ResultType::Split;
1154        }
1155
1156        // measure the curve ray distance to the quad-stroke
1157        let mut roots = path_geometry::new_t_values();
1158        let roots = intersect_quad_ray(ray, stroke, &mut roots);
1159        if roots.len() != 1 {
1160            return ResultType::Split;
1161        }
1162
1163        let quad_pt = path_geometry::eval_quad_at(stroke, roots[0].to_normalized());
1164        let error = self.inv_res_scale * (1.0 - (roots[0].get() - 0.5).abs() * 2.0);
1165        if points_within_dist(ray[0], quad_pt, error) {
1166            // if the difference is small, we're done
1167            if sharp_angle(&quad_points.quad) {
1168                return ResultType::Split;
1169            }
1170
1171            return ResultType::Quad;
1172        }
1173
1174        // otherwise, subdivide
1175        ResultType::Split
1176    }
1177
1178    // Find the intersection of the stroke tangents to construct a stroke quad.
1179    // Return whether the stroke is a degenerate (a line), a quad, or must be split.
1180    // Optionally compute the quad's control point.
1181    fn intersect_ray(
1182        &self,
1183        intersect_ray_type: IntersectRayType,
1184        quad_points: &mut QuadConstruct,
1185    ) -> ResultType {
1186        let start = quad_points.quad[0];
1187        let end = quad_points.quad[2];
1188        let a_len = quad_points.tangent_start - start;
1189        let b_len = quad_points.tangent_end - end;
1190
1191        // Slopes match when denom goes to zero:
1192        //                   axLen / ayLen ==                   bxLen / byLen
1193        // (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen
1194        //          byLen  * axLen         ==  ayLen          * bxLen
1195        //          byLen  * axLen         -   ayLen          * bxLen         ( == denom )
1196        let denom = a_len.cross(b_len);
1197        if denom == 0.0 || !denom.is_finite() {
1198            quad_points.opposite_tangents = a_len.dot(b_len) < 0.0;
1199            return ResultType::Degenerate;
1200        }
1201
1202        quad_points.opposite_tangents = false;
1203        let ab0 = start - end;
1204        let mut numer_a = b_len.cross(ab0);
1205        let numer_b = a_len.cross(ab0);
1206        if (numer_a >= 0.0) == (numer_b >= 0.0) {
1207            // if the control point is outside the quad ends
1208
1209            // if the perpendicular distances from the quad points to the opposite tangent line
1210            // are small, a straight line is good enough
1211            let dist1 = pt_to_line(start, end, quad_points.tangent_end);
1212            let dist2 = pt_to_line(end, start, quad_points.tangent_start);
1213            if dist1.max(dist2) <= self.inv_res_scale_squared {
1214                return ResultType::Degenerate;
1215            }
1216
1217            return ResultType::Split;
1218        }
1219
1220        // check to see if the denominator is teeny relative to the numerator
1221        // if the offset by one will be lost, the ratio is too large
1222        numer_a /= denom;
1223        let valid_divide = numer_a > numer_a - 1.0;
1224        if valid_divide {
1225            if intersect_ray_type == IntersectRayType::CtrlPt {
1226                // the intersection of the tangents need not be on the tangent segment
1227                // so 0 <= numerA <= 1 is not necessarily true
1228                quad_points.quad[1].x =
1229                    start.x * (1.0 - numer_a) + quad_points.tangent_start.x * numer_a;
1230                quad_points.quad[1].y =
1231                    start.y * (1.0 - numer_a) + quad_points.tangent_start.y * numer_a;
1232            }
1233
1234            return ResultType::Quad;
1235        }
1236
1237        quad_points.opposite_tangents = a_len.dot(b_len) < 0.0;
1238
1239        // if the lines are parallel, straight line is good enough
1240        ResultType::Degenerate
1241    }
1242
1243    // Given a cubic and a t-range, determine if the stroke can be described by a quadratic.
1244    fn tangents_meet(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> ResultType {
1245        self.cubic_quad_ends(cubic, quad_points);
1246        self.intersect_ray(IntersectRayType::ResultType, quad_points)
1247    }
1248
1249    fn finish(&mut self, is_line: bool) -> Option<Path> {
1250        self.finish_contour(false, is_line);
1251
1252        // Swap out the outer builder.
1253        let mut buf = PathBuilder::new();
1254        core::mem::swap(&mut self.outer, &mut buf);
1255
1256        buf.finish()
1257    }
1258
1259    fn has_only_move_to(&self) -> bool {
1260        self.segment_count == 0
1261    }
1262
1263    fn is_current_contour_empty(&self) -> bool {
1264        self.inner.is_zero_length_since_point(0)
1265            && self
1266                .outer
1267                .is_zero_length_since_point(self.first_outer_pt_index_in_contour)
1268    }
1269}
1270
1271fn cap_factory(cap: LineCap) -> CapProc {
1272    match cap {
1273        LineCap::Butt => butt_capper,
1274        LineCap::Round => round_capper,
1275        LineCap::Square => square_capper,
1276    }
1277}
1278
1279fn butt_capper(_: Point, _: Point, stop: Point, _: Option<&PathBuilder>, path: &mut PathBuilder) {
1280    path.line_to(stop.x, stop.y);
1281}
1282
1283fn round_capper(
1284    pivot: Point,
1285    normal: Point,
1286    stop: Point,
1287    _: Option<&PathBuilder>,
1288    path: &mut PathBuilder,
1289) {
1290    let mut parallel = normal;
1291    parallel.rotate_cw();
1292
1293    let projected_center = pivot + parallel;
1294
1295    path.conic_points_to(
1296        projected_center + normal,
1297        projected_center,
1298        SCALAR_ROOT_2_OVER_2,
1299    );
1300    path.conic_points_to(projected_center - normal, stop, SCALAR_ROOT_2_OVER_2);
1301}
1302
1303fn square_capper(
1304    pivot: Point,
1305    normal: Point,
1306    stop: Point,
1307    other_path: Option<&PathBuilder>,
1308    path: &mut PathBuilder,
1309) {
1310    let mut parallel = normal;
1311    parallel.rotate_cw();
1312
1313    if other_path.is_some() {
1314        path.set_last_point(Point::from_xy(
1315            pivot.x + normal.x + parallel.x,
1316            pivot.y + normal.y + parallel.y,
1317        ));
1318        path.line_to(
1319            pivot.x - normal.x + parallel.x,
1320            pivot.y - normal.y + parallel.y,
1321        );
1322    } else {
1323        path.line_to(
1324            pivot.x + normal.x + parallel.x,
1325            pivot.y + normal.y + parallel.y,
1326        );
1327        path.line_to(
1328            pivot.x - normal.x + parallel.x,
1329            pivot.y - normal.y + parallel.y,
1330        );
1331        path.line_to(stop.x, stop.y);
1332    }
1333}
1334
1335fn join_factory(join: LineJoin) -> JoinProc {
1336    match join {
1337        LineJoin::Miter => miter_joiner,
1338        LineJoin::Round => round_joiner,
1339        LineJoin::Bevel => bevel_joiner,
1340    }
1341}
1342
1343fn is_clockwise(before: Point, after: Point) -> bool {
1344    before.x * after.y > before.y * after.x
1345}
1346
1347#[derive(Copy, Clone, PartialEq, Debug)]
1348enum AngleType {
1349    Nearly180,
1350    Sharp,
1351    Shallow,
1352    NearlyLine,
1353}
1354
1355fn dot_to_angle_type(dot: f32) -> AngleType {
1356    if dot >= 0.0 {
1357        // shallow or line
1358        if (1.0 - dot).is_nearly_zero() {
1359            AngleType::NearlyLine
1360        } else {
1361            AngleType::Shallow
1362        }
1363    } else {
1364        // sharp or 180
1365        if (1.0 + dot).is_nearly_zero() {
1366            AngleType::Nearly180
1367        } else {
1368            AngleType::Sharp
1369        }
1370    }
1371}
1372
1373fn handle_inner_join(pivot: Point, after: Point, inner: &mut PathBuilder) {
1374    // In the degenerate case that the stroke radius is larger than our segments
1375    // just connecting the two inner segments may "show through" as a funny
1376    // diagonal. To pseudo-fix this, we go through the pivot point. This adds
1377    // an extra point/edge, but I can't see a cheap way to know when this is
1378    // not needed :(
1379    inner.line_to(pivot.x, pivot.y);
1380
1381    inner.line_to(pivot.x - after.x, pivot.y - after.y);
1382}
1383
1384fn bevel_joiner(
1385    before_unit_normal: Point,
1386    pivot: Point,
1387    after_unit_normal: Point,
1388    radius: f32,
1389    _: f32,
1390    _: bool,
1391    _: bool,
1392    mut builders: SwappableBuilders,
1393) {
1394    let mut after = after_unit_normal.scaled(radius);
1395
1396    if !is_clockwise(before_unit_normal, after_unit_normal) {
1397        builders.swap();
1398        after = -after;
1399    }
1400
1401    builders.outer.line_to(pivot.x + after.x, pivot.y + after.y);
1402    handle_inner_join(pivot, after, builders.inner);
1403}
1404
1405fn round_joiner(
1406    before_unit_normal: Point,
1407    pivot: Point,
1408    after_unit_normal: Point,
1409    radius: f32,
1410    _: f32,
1411    _: bool,
1412    _: bool,
1413    mut builders: SwappableBuilders,
1414) {
1415    let dot_prod = before_unit_normal.dot(after_unit_normal);
1416    let angle_type = dot_to_angle_type(dot_prod);
1417
1418    if angle_type == AngleType::NearlyLine {
1419        return;
1420    }
1421
1422    let mut before = before_unit_normal;
1423    let mut after = after_unit_normal;
1424    let mut dir = PathDirection::CW;
1425
1426    if !is_clockwise(before, after) {
1427        builders.swap();
1428        before = -before;
1429        after = -after;
1430        dir = PathDirection::CCW;
1431    }
1432
1433    let ts = Transform::from_row(radius, 0.0, 0.0, radius, pivot.x, pivot.y);
1434
1435    let mut conics = [path_geometry::Conic::default(); 5];
1436    let conics = path_geometry::Conic::build_unit_arc(before, after, dir, ts, &mut conics);
1437    if let Some(conics) = conics {
1438        for conic in conics {
1439            builders
1440                .outer
1441                .conic_points_to(conic.points[1], conic.points[2], conic.weight);
1442        }
1443
1444        after.scale(radius);
1445        handle_inner_join(pivot, after, builders.inner);
1446    }
1447}
1448
1449fn miter_joiner(
1450    before_unit_normal: Point,
1451    pivot: Point,
1452    after_unit_normal: Point,
1453    radius: f32,
1454    inv_miter_limit: f32,
1455    prev_is_line: bool,
1456    mut curr_is_line: bool,
1457    mut builders: SwappableBuilders,
1458) {
1459    fn do_blunt(
1460        builders: SwappableBuilders,
1461        pivot: Point,
1462        radius: f32,
1463        curr_is_line: bool,
1464        mut after: Point,
1465    ) {
1466        after.scale(radius);
1467        if !curr_is_line {
1468            builders.outer.line_to(pivot.x + after.x, pivot.y + after.y);
1469        }
1470
1471        handle_inner_join(pivot, after, builders.inner);
1472    }
1473
1474    fn do_miter(
1475        builders: SwappableBuilders,
1476        pivot: Point,
1477        radius: f32,
1478        prev_is_line: bool,
1479        curr_is_line: bool,
1480        mid: Point,
1481        after: Point,
1482    ) {
1483        if prev_is_line {
1484            builders
1485                .outer
1486                .set_last_point(Point::from_xy(pivot.x + mid.x, pivot.y + mid.y));
1487        } else {
1488            builders.outer.line_to(pivot.x + mid.x, pivot.y + mid.y);
1489        }
1490
1491        do_blunt(builders, pivot, radius, curr_is_line, after);
1492    }
1493
1494    // negate the dot since we're using normals instead of tangents
1495    let dot_prod = before_unit_normal.dot(after_unit_normal);
1496    let angle_type = dot_to_angle_type(dot_prod);
1497    let mut before = before_unit_normal;
1498    let mut after = after_unit_normal;
1499    let mut mid;
1500
1501    if angle_type == AngleType::NearlyLine {
1502        return;
1503    }
1504
1505    if angle_type == AngleType::Nearly180 {
1506        curr_is_line = false;
1507        do_blunt(builders, pivot, radius, curr_is_line, after);
1508        return;
1509    }
1510
1511    let ccw = !is_clockwise(before, after);
1512    if ccw {
1513        builders.swap();
1514        before = -before;
1515        after = -after;
1516    }
1517
1518    // Before we enter the world of square-roots and divides,
1519    // check if we're trying to join an upright right angle
1520    // (common case for stroking rectangles). If so, special case
1521    // that (for speed an accuracy).
1522    // Note: we only need to check one normal if dot==0
1523    if dot_prod == 0.0 && inv_miter_limit <= SCALAR_ROOT_2_OVER_2 {
1524        mid = (before + after).scaled(radius);
1525        do_miter(
1526            builders,
1527            pivot,
1528            radius,
1529            prev_is_line,
1530            curr_is_line,
1531            mid,
1532            after,
1533        );
1534        return;
1535    }
1536
1537    // midLength = radius / sinHalfAngle
1538    // if (midLength > miterLimit * radius) abort
1539    // if (radius / sinHalf > miterLimit * radius) abort
1540    // if (1 / sinHalf > miterLimit) abort
1541    // if (1 / miterLimit > sinHalf) abort
1542    // My dotProd is opposite sign, since it is built from normals and not tangents
1543    // hence 1 + dot instead of 1 - dot in the formula
1544    let sin_half_angle = (1.0 + dot_prod).half().sqrt();
1545    if sin_half_angle < inv_miter_limit {
1546        curr_is_line = false;
1547        do_blunt(builders, pivot, radius, curr_is_line, after);
1548        return;
1549    }
1550
1551    // choose the most accurate way to form the initial mid-vector
1552    if angle_type == AngleType::Sharp {
1553        mid = Point::from_xy(after.y - before.y, before.x - after.x);
1554        if ccw {
1555            mid = -mid;
1556        }
1557    } else {
1558        mid = Point::from_xy(before.x + after.x, before.y + after.y);
1559    }
1560
1561    mid.set_length(radius / sin_half_angle);
1562    do_miter(
1563        builders,
1564        pivot,
1565        radius,
1566        prev_is_line,
1567        curr_is_line,
1568        mid,
1569        after,
1570    );
1571}
1572
1573fn set_normal_unit_normal(
1574    before: Point,
1575    after: Point,
1576    scale: f32,
1577    radius: f32,
1578    normal: &mut Point,
1579    unit_normal: &mut Point,
1580) -> bool {
1581    if !unit_normal.set_normalize((after.x - before.x) * scale, (after.y - before.y) * scale) {
1582        return false;
1583    }
1584
1585    unit_normal.rotate_ccw();
1586    *normal = unit_normal.scaled(radius);
1587    true
1588}
1589
1590fn set_normal_unit_normal2(
1591    vec: Point,
1592    radius: f32,
1593    normal: &mut Point,
1594    unit_normal: &mut Point,
1595) -> bool {
1596    if !unit_normal.set_normalize(vec.x, vec.y) {
1597        return false;
1598    }
1599
1600    unit_normal.rotate_ccw();
1601    *normal = unit_normal.scaled(radius);
1602    true
1603}
1604
1605fn fn_ptr_eq(f1: CapProc, f2: CapProc) -> bool {
1606    core::ptr::eq(f1 as *const (), f2 as *const ())
1607}
1608
1609#[derive(Debug)]
1610struct QuadConstruct {
1611    // The state of the quad stroke under construction.
1612    quad: [Point; 3],       // the stroked quad parallel to the original curve
1613    tangent_start: Point,   // a point tangent to quad[0]
1614    tangent_end: Point,     // a point tangent to quad[2]
1615    start_t: NormalizedF32, // a segment of the original curve
1616    mid_t: NormalizedF32,
1617    end_t: NormalizedF32,
1618    start_set: bool, // state to share common points across structs
1619    end_set: bool,
1620    opposite_tangents: bool, // set if coincident tangents have opposite directions
1621}
1622
1623impl Default for QuadConstruct {
1624    fn default() -> Self {
1625        Self {
1626            quad: Default::default(),
1627            tangent_start: Point::default(),
1628            tangent_end: Point::default(),
1629            start_t: NormalizedF32::ZERO,
1630            mid_t: NormalizedF32::ZERO,
1631            end_t: NormalizedF32::ZERO,
1632            start_set: false,
1633            end_set: false,
1634            opposite_tangents: false,
1635        }
1636    }
1637}
1638
1639impl QuadConstruct {
1640    // return false if start and end are too close to have a unique middle
1641    fn init(&mut self, start: NormalizedF32, end: NormalizedF32) -> bool {
1642        self.start_t = start;
1643        self.mid_t = NormalizedF32::new_clamped((start.get() + end.get()).half());
1644        self.end_t = end;
1645        self.start_set = false;
1646        self.end_set = false;
1647        self.start_t < self.mid_t && self.mid_t < self.end_t
1648    }
1649
1650    fn init_with_start(&mut self, parent: &Self) -> bool {
1651        if !self.init(parent.start_t, parent.mid_t) {
1652            return false;
1653        }
1654
1655        self.quad[0] = parent.quad[0];
1656        self.tangent_start = parent.tangent_start;
1657        self.start_set = true;
1658        true
1659    }
1660
1661    fn init_with_end(&mut self, parent: &Self) -> bool {
1662        if !self.init(parent.mid_t, parent.end_t) {
1663            return false;
1664        }
1665
1666        self.quad[2] = parent.quad[2];
1667        self.tangent_end = parent.tangent_end;
1668        self.end_set = true;
1669        true
1670    }
1671}
1672
1673fn check_quad_linear(quad: &[Point; 3]) -> (Point, ReductionType) {
1674    let degenerate_ab = degenerate_vector(quad[1] - quad[0]);
1675    let degenerate_bc = degenerate_vector(quad[2] - quad[1]);
1676    if degenerate_ab & degenerate_bc {
1677        return (Point::zero(), ReductionType::Point);
1678    }
1679
1680    if degenerate_ab | degenerate_bc {
1681        return (Point::zero(), ReductionType::Line);
1682    }
1683
1684    if !quad_in_line(quad) {
1685        return (Point::zero(), ReductionType::Quad);
1686    }
1687
1688    let t = path_geometry::find_quad_max_curvature(quad);
1689    if t == NormalizedF32::ZERO || t == NormalizedF32::ONE {
1690        return (Point::zero(), ReductionType::Line);
1691    }
1692
1693    (
1694        path_geometry::eval_quad_at(quad, t),
1695        ReductionType::Degenerate,
1696    )
1697}
1698
1699fn degenerate_vector(v: Point) -> bool {
1700    !v.can_normalize()
1701}
1702
1703/// Given quad, see if all there points are in a line.
1704/// Return true if the inside point is close to a line connecting the outermost points.
1705///
1706/// Find the outermost point by looking for the largest difference in X or Y.
1707/// Since the XOR of the indices is 3  (0 ^ 1 ^ 2)
1708/// the missing index equals: outer_1 ^ outer_2 ^ 3.
1709fn quad_in_line(quad: &[Point; 3]) -> bool {
1710    let mut pt_max = -1.0;
1711    let mut outer1 = 0;
1712    let mut outer2 = 0;
1713    for index in 0..2 {
1714        for inner in index + 1..3 {
1715            let test_diff = quad[inner] - quad[index];
1716            let test_max = test_diff.x.abs().max(test_diff.y.abs());
1717            if pt_max < test_max {
1718                outer1 = index;
1719                outer2 = inner;
1720                pt_max = test_max;
1721            }
1722        }
1723    }
1724
1725    debug_assert!(outer1 <= 1);
1726    debug_assert!(outer2 >= 1 && outer2 <= 2);
1727    debug_assert!(outer1 < outer2);
1728
1729    let mid = outer1 ^ outer2 ^ 3;
1730    const CURVATURE_SLOP: f32 = 0.000005; // this multiplier is pulled out of the air
1731    let line_slop = pt_max * pt_max * CURVATURE_SLOP;
1732    pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= line_slop
1733}
1734
1735// returns the distance squared from the point to the line
1736fn pt_to_line(pt: Point, line_start: Point, line_end: Point) -> f32 {
1737    let dxy = line_end - line_start;
1738    let ab0 = pt - line_start;
1739    let numer = dxy.dot(ab0);
1740    let denom = dxy.dot(dxy);
1741    let t = numer / denom;
1742    if t >= 0.0 && t <= 1.0 {
1743        let hit = Point::from_xy(
1744            line_start.x * (1.0 - t) + line_end.x * t,
1745            line_start.y * (1.0 - t) + line_end.y * t,
1746        );
1747        hit.distance_to_sqd(pt)
1748    } else {
1749        pt.distance_to_sqd(line_start)
1750    }
1751}
1752
1753// Intersect the line with the quad and return the t values on the quad where the line crosses.
1754fn intersect_quad_ray<'a>(
1755    line: &[Point; 2],
1756    quad: &[Point; 3],
1757    roots: &'a mut [NormalizedF32Exclusive; 3],
1758) -> &'a [NormalizedF32Exclusive] {
1759    let vec = line[1] - line[0];
1760    let mut r = [0.0; 3];
1761    for n in 0..3 {
1762        r[n] = (quad[n].y - line[0].y) * vec.x - (quad[n].x - line[0].x) * vec.y;
1763    }
1764    let mut a = r[2];
1765    let mut b = r[1];
1766    let c = r[0];
1767    a += c - 2.0 * b; // A = a - 2*b + c
1768    b -= c; // B = -(b - c)
1769
1770    let len = path_geometry::find_unit_quad_roots(a, 2.0 * b, c, roots);
1771    &roots[0..len]
1772}
1773
1774fn points_within_dist(near_pt: Point, far_pt: Point, limit: f32) -> bool {
1775    near_pt.distance_to_sqd(far_pt) <= limit * limit
1776}
1777
1778fn sharp_angle(quad: &[Point; 3]) -> bool {
1779    let mut smaller = quad[1] - quad[0];
1780    let mut larger = quad[1] - quad[2];
1781    let smaller_len = smaller.length_sqd();
1782    let mut larger_len = larger.length_sqd();
1783    if smaller_len > larger_len {
1784        core::mem::swap(&mut smaller, &mut larger);
1785        larger_len = smaller_len;
1786    }
1787
1788    if !smaller.set_length(larger_len) {
1789        return false;
1790    }
1791
1792    let dot = smaller.dot(larger);
1793    dot > 0.0
1794}
1795
1796// Return true if the point is close to the bounds of the quad. This is used as a quick reject.
1797fn pt_in_quad_bounds(quad: &[Point; 3], pt: Point, inv_res_scale: f32) -> bool {
1798    let x_min = quad[0].x.min(quad[1].x).min(quad[2].x);
1799    if pt.x + inv_res_scale < x_min {
1800        return false;
1801    }
1802
1803    let x_max = quad[0].x.max(quad[1].x).max(quad[2].x);
1804    if pt.x - inv_res_scale > x_max {
1805        return false;
1806    }
1807
1808    let y_min = quad[0].y.min(quad[1].y).min(quad[2].y);
1809    if pt.y + inv_res_scale < y_min {
1810        return false;
1811    }
1812
1813    let y_max = quad[0].y.max(quad[1].y).max(quad[2].y);
1814    if pt.y - inv_res_scale > y_max {
1815        return false;
1816    }
1817
1818    true
1819}
1820
1821fn check_cubic_linear(
1822    cubic: &[Point; 4],
1823    reduction: &mut [Point; 3],
1824    tangent_pt: Option<&mut Point>,
1825) -> ReductionType {
1826    let degenerate_ab = degenerate_vector(cubic[1] - cubic[0]);
1827    let degenerate_bc = degenerate_vector(cubic[2] - cubic[1]);
1828    let degenerate_cd = degenerate_vector(cubic[3] - cubic[2]);
1829    if degenerate_ab & degenerate_bc & degenerate_cd {
1830        return ReductionType::Point;
1831    }
1832
1833    if degenerate_ab as i32 + degenerate_bc as i32 + degenerate_cd as i32 == 2 {
1834        return ReductionType::Line;
1835    }
1836
1837    if !cubic_in_line(cubic) {
1838        if let Some(tangent_pt) = tangent_pt {
1839            *tangent_pt = if degenerate_ab { cubic[2] } else { cubic[1] };
1840        }
1841
1842        return ReductionType::Quad;
1843    }
1844
1845    let mut t_values = [NormalizedF32::ZERO; 3];
1846    let t_values = path_geometry::find_cubic_max_curvature(cubic, &mut t_values);
1847    let mut r_count = 0;
1848    // Now loop over the t-values, and reject any that evaluate to either end-point
1849    for t in t_values {
1850        if 0.0 >= t.get() || t.get() >= 1.0 {
1851            continue;
1852        }
1853
1854        reduction[r_count] = path_geometry::eval_cubic_pos_at(cubic, *t);
1855        if reduction[r_count] != cubic[0] && reduction[r_count] != cubic[3] {
1856            r_count += 1;
1857        }
1858    }
1859
1860    match r_count {
1861        0 => ReductionType::Line,
1862        1 => ReductionType::Degenerate,
1863        2 => ReductionType::Degenerate2,
1864        3 => ReductionType::Degenerate3,
1865        _ => unreachable!(),
1866    }
1867}
1868
1869/// Given a cubic, determine if all four points are in a line.
1870///
1871/// Return true if the inner points is close to a line connecting the outermost points.
1872///
1873/// Find the outermost point by looking for the largest difference in X or Y.
1874/// Given the indices of the outermost points, and that outer_1 is greater than outer_2,
1875/// this table shows the index of the smaller of the remaining points:
1876///
1877/// ```text
1878///                   outer_2
1879///               0    1    2    3
1880///   outer_1     ----------------
1881///      0     |  -    2    1    1
1882///      1     |  -    -    0    0
1883///      2     |  -    -    -    0
1884///      3     |  -    -    -    -
1885/// ```
1886///
1887/// If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2.
1888///
1889/// This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1
1890///
1891/// Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is:
1892///
1893/// ```text
1894/// mid_2 == (outer_1 ^ outer_2 ^ mid_1)
1895/// ```
1896fn cubic_in_line(cubic: &[Point; 4]) -> bool {
1897    let mut pt_max = -1.0;
1898    let mut outer1 = 0;
1899    let mut outer2 = 0;
1900    for index in 0..3 {
1901        for inner in index + 1..4 {
1902            let test_diff = cubic[inner] - cubic[index];
1903            let test_max = test_diff.x.abs().max(test_diff.y.abs());
1904            if pt_max < test_max {
1905                outer1 = index;
1906                outer2 = inner;
1907                pt_max = test_max;
1908            }
1909        }
1910    }
1911    debug_assert!(outer1 <= 2);
1912    debug_assert!(outer2 >= 1 && outer2 <= 3);
1913    debug_assert!(outer1 < outer2);
1914    let mid1 = (1 + (2 >> outer2)) >> outer1;
1915    debug_assert!(mid1 <= 2);
1916    debug_assert!(outer1 != mid1 && outer2 != mid1);
1917    let mid2 = outer1 ^ outer2 ^ mid1;
1918    debug_assert!(mid2 >= 1 && mid2 <= 3);
1919    debug_assert!(mid2 != outer1 && mid2 != outer2 && mid2 != mid1);
1920    debug_assert!(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f);
1921    let line_slop = pt_max * pt_max * 0.00001; // this multiplier is pulled out of the air
1922
1923    pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= line_slop
1924        && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= line_slop
1925}
1926
1927#[rustfmt::skip]
1928#[cfg(test)]
1929mod tests {
1930    use super::*;
1931
1932    impl PathSegment {
1933        fn new_move_to(x: f32, y: f32) -> Self {
1934            PathSegment::MoveTo(Point::from_xy(x, y))
1935        }
1936
1937        fn new_line_to(x: f32, y: f32) -> Self {
1938            PathSegment::LineTo(Point::from_xy(x, y))
1939        }
1940
1941        // fn new_quad_to(x1: f32, y1: f32, x: f32, y: f32) -> Self {
1942        //     PathSegment::QuadTo(Point::from_xy(x1, y1), Point::from_xy(x, y))
1943        // }
1944
1945        // fn new_cubic_to(x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) -> Self {
1946        //     PathSegment::CubicTo(Point::from_xy(x1, y1), Point::from_xy(x2, y2), Point::from_xy(x, y))
1947        // }
1948
1949        fn new_close() -> Self {
1950            PathSegment::Close
1951        }
1952    }
1953
1954    // Make sure that subpath auto-closing is enabled.
1955    #[test]
1956    fn auto_close() {
1957        // A triangle.
1958        let mut pb = PathBuilder::new();
1959        pb.move_to(10.0, 10.0);
1960        pb.line_to(20.0, 50.0);
1961        pb.line_to(30.0, 10.0);
1962        pb.close();
1963        let path = pb.finish().unwrap();
1964
1965        let stroke = Stroke::default();
1966        let stroke_path = PathStroker::new().stroke(&path, &stroke, 1.0).unwrap();
1967
1968        let mut iter = stroke_path.segments();
1969        iter.set_auto_close(true);
1970
1971        assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(10.485071, 9.878732));
1972        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 49.878731));
1973        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.0, 50.0));
1974        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 49.878731));
1975        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(29.514929, 9.878732));
1976        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.0));
1977        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.5));
1978        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.5));
1979        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.0));
1980        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.485071, 9.878732));
1981        assert_eq!(iter.next().unwrap(), PathSegment::new_close());
1982        assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(9.3596115, 9.5));
1983        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.640388, 9.5));
1984        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 50.121269));
1985        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 50.121269));
1986        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.514929, 10.121268));
1987        assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.3596115, 9.5));
1988        assert_eq!(iter.next().unwrap(), PathSegment::new_close());
1989    }
1990
1991    // From skia/tests/StrokeTest.cpp
1992    #[test]
1993    fn cubic_1() {
1994        let mut pb = PathBuilder::new();
1995        pb.move_to(51.0161362, 1511.52478);
1996        pb.cubic_to(
1997            51.0161362, 1511.52478,
1998            51.0161362, 1511.52478,
1999            51.0161362, 1511.52478,
2000        );
2001        let path = pb.finish().unwrap();
2002
2003        let mut stroke = Stroke::default();
2004        stroke.width = 0.394537568;
2005
2006        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_none());
2007    }
2008
2009    // From skia/tests/StrokeTest.cpp
2010    #[test]
2011    fn cubic_2() {
2012        let mut pb = PathBuilder::new();
2013        pb.move_to(f32::from_bits(0x424c1086), f32::from_bits(0x44bcf0cb)); // 51.0161362, 1511.52478
2014        pb.cubic_to(
2015            f32::from_bits(0x424c107c), f32::from_bits(0x44bcf0cb), // 51.0160980, 1511.52478
2016            f32::from_bits(0x424c10c2), f32::from_bits(0x44bcf0cb), // 51.0163651, 1511.52478
2017            f32::from_bits(0x424c1119), f32::from_bits(0x44bcf0ca), // 51.0166969, 1511.52466
2018        );
2019        let path = pb.finish().unwrap();
2020
2021        let mut stroke = Stroke::default();
2022        stroke.width = 0.394537568;
2023
2024        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
2025    }
2026
2027    // From skia/tests/StrokeTest.cpp
2028    // From skbug.com/6491. The large stroke width can cause numerical instabilities.
2029    #[test]
2030    fn big() {
2031        // Skia uses `kStrokeAndFill_Style` here, but we do not support it.
2032
2033        let mut pb = PathBuilder::new();
2034        pb.move_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776
2035        pb.line_to(f32::from_bits(0x46a00000), f32::from_bits(0xc6a00000)); // 20480, -20480
2036        pb.line_to(f32::from_bits(0x468c0000), f32::from_bits(0xc68c0000)); // 17920, -17920
2037        pb.line_to(f32::from_bits(0x46100000), f32::from_bits(0xc6100000)); // 9216, -9216
2038        pb.line_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776
2039        pb.close();
2040        let path = pb.finish().unwrap();
2041
2042        let mut stroke = Stroke::default();
2043        stroke.width = 1.49679073e+10;
2044
2045        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
2046    }
2047
2048    // From skia/tests/StrokerTest.cpp
2049    #[test]
2050    fn quad_stroker_one_off() {
2051        let mut pb = PathBuilder::new();
2052        pb.move_to(f32::from_bits(0x43c99223), f32::from_bits(0x42b7417e));
2053        pb.quad_to(
2054            f32::from_bits(0x4285d839), f32::from_bits(0x43ed6645),
2055            f32::from_bits(0x43c941c8), f32::from_bits(0x42b3ace3),
2056        );
2057        let path = pb.finish().unwrap();
2058
2059        let mut stroke = Stroke::default();
2060        stroke.width = 164.683548;
2061
2062        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
2063    }
2064
2065    // From skia/tests/StrokerTest.cpp
2066    #[test]
2067    fn cubic_stroker_one_off() {
2068        let mut pb = PathBuilder::new();
2069        pb.move_to(f32::from_bits(0x433f5370), f32::from_bits(0x43d1f4b3));
2070        pb.cubic_to(
2071            f32::from_bits(0x4331cb76), f32::from_bits(0x43ea3340),
2072            f32::from_bits(0x4388f498), f32::from_bits(0x42f7f08d),
2073            f32::from_bits(0x43f1cd32), f32::from_bits(0x42802ec1),
2074        );
2075        let path = pb.finish().unwrap();
2076
2077        let mut stroke = Stroke::default();
2078        stroke.width = 42.835968;
2079
2080        assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some());
2081    }
2082}