Skip to main content

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