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