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 | |
9 | use crate::{Path, Point, Transform}; |
10 | |
11 | use crate::dash::StrokeDash; |
12 | use crate::floating_point::{NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive}; |
13 | use crate::path::{PathSegment, PathSegmentsIter}; |
14 | use crate::path_builder::{PathBuilder, PathDirection}; |
15 | use crate::path_geometry; |
16 | use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2}; |
17 | |
18 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
19 | use crate::NoStdFloat; |
20 | |
21 | struct SwappableBuilders<'a> { |
22 | inner: &'a mut PathBuilder, |
23 | outer: &'a mut PathBuilder, |
24 | } |
25 | |
26 | impl<'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)] |
39 | pub 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 | |
70 | impl 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)] |
84 | pub enum LineCap { |
85 | /// No stroke extension. |
86 | Butt, |
87 | /// Adds circle. |
88 | Round, |
89 | /// Adds square. |
90 | Square, |
91 | } |
92 | |
93 | impl 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)] |
112 | pub 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 | |
123 | impl Default for LineJoin { |
124 | fn default() -> Self { |
125 | LineJoin::Miter |
126 | } |
127 | } |
128 | |
129 | const 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 |
134 | const RECURSIVE_LIMITS: [i32; 4] = [5 * 3, 26 * 3, 11 * 3, 11 * 3]; // 3x limits seen in practice |
135 | |
136 | type CapProc = fn( |
137 | pivot: Point, |
138 | normal: Point, |
139 | stop: Point, |
140 | other_path: Option<&PathBuilder>, |
141 | path: &mut PathBuilder, |
142 | ); |
143 | |
144 | type 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)] |
156 | enum 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)] |
166 | enum StrokeType { |
167 | Outer = 1, // use sign-opposite values later to flip perpendicular axis |
168 | Inner = -1, |
169 | } |
170 | |
171 | #[derive (Copy, Clone, PartialEq, Debug)] |
172 | enum 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)] |
179 | enum IntersectRayType { |
180 | CtrlPt, |
181 | ResultType, |
182 | } |
183 | |
184 | impl 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)] |
201 | pub 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 | |
237 | impl Default for PathStroker { |
238 | fn default() -> Self { |
239 | PathStroker::new() |
240 | } |
241 | } |
242 | |
243 | impl 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 | |
1277 | fn 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 | |
1285 | fn butt_capper(_: Point, _: Point, stop: Point, _: Option<&PathBuilder>, path: &mut PathBuilder) { |
1286 | path.line_to(stop.x, stop.y); |
1287 | } |
1288 | |
1289 | fn 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 | |
1309 | fn 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 | |
1341 | fn 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 | |
1350 | fn is_clockwise(before: Point, after: Point) -> bool { |
1351 | before.x * after.y > before.y * after.x |
1352 | } |
1353 | |
1354 | #[derive (Copy, Clone, PartialEq, Debug)] |
1355 | enum AngleType { |
1356 | Nearly180, |
1357 | Sharp, |
1358 | Shallow, |
1359 | NearlyLine, |
1360 | } |
1361 | |
1362 | fn 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 | |
1380 | fn 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 | |
1391 | fn 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 | |
1412 | fn 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 ] |
1457 | fn 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 ] |
1481 | fn 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 | |
1504 | fn 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 | |
1696 | fn 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 | |
1713 | fn 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 | |
1728 | fn 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)] |
1733 | struct 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 | |
1746 | impl 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 | |
1762 | impl 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 | |
1796 | fn 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 | |
1822 | fn 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. |
1832 | fn 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 |
1859 | fn 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. |
1877 | fn 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 | |
1897 | fn points_within_dist(near_pt: Point, far_pt: Point, limit: f32) -> bool { |
1898 | near_pt.distance_to_sqd(far_pt) <= limit * limit |
1899 | } |
1900 | |
1901 | fn 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. |
1920 | fn 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 | |
1944 | fn 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 | /// ``` |
2019 | fn 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)] |
2052 | mod 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 | |