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: Point = normal;
1297 parallel.rotate_cw();
1298
1299 let projected_center: Point = pivot + parallel;
1300
1301 path.conic_points_to(
1302 pt1:projected_center + normal,
1303 pt2:projected_center,
1304 SCALAR_ROOT_2_OVER_2,
1305 );
1306 path.conic_points_to(pt1:projected_center - normal, pt2: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(x:pivot.x - after.x, y: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: Point = after_unit_normal.scaled(scale: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(x:pivot.x + after.x, y: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 miter_clip: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 miter_clip: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(scale: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(scale:radius);
1725 true
1726}
1727
1728fn fn_ptr_eq(f1: CapProc, f2: CapProc) -> bool {
1729 core::ptr::eq(a:f1 as *const (), b: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: Point = line_end - line_start;
1861 let ab0: Point = pt - line_start;
1862 let numer: f32 = dxy.dot(ab0);
1863 let denom: f32 = dxy.dot(dxy);
1864 let t: f32 = numer / denom;
1865 if t >= 0.0 && t <= 1.0 {
1866 let hit: Point = Point::from_xy(
1867 x:line_start.x * (1.0 - t) + line_end.x * t,
1868 y:line_start.y * (1.0 - t) + line_end.y * t,
1869 );
1870 hit.distance_to_sqd(pt)
1871 } else {
1872 pt.distance_to_sqd(pt: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: Point = line[1] - line[0];
1883 let mut r: [f32; 3] = [0.0; 3];
1884 for n: usize 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: f32 = r[2];
1888 let mut b: f32 = r[1];
1889 let c: f32 = r[0];
1890 a += c - 2.0 * b; // A = a - 2*b + c
1891 b -= c; // B = -(b - c)
1892
1893 let len: usize = path_geometry::find_unit_quad_roots(a, b: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: Point = quad[1] - quad[0];
1903 let mut larger: Point = quad[1] - quad[2];
1904 let smaller_len: f32 = smaller.length_sqd();
1905 let mut larger_len: f32 = 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: f32 = 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: f32 = 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: f32 = 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: f32 = 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: f32 = 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}
2206