1 | // Copyright 2009 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 | use arrayvec::ArrayVec; |
8 | |
9 | use tiny_skia_path::{NormalizedF32Exclusive, SCALAR_MAX}; |
10 | |
11 | use crate::{Path, Point, Rect}; |
12 | |
13 | use crate::edge_builder::{edge_iter, PathEdge, PathEdgeIter}; |
14 | use crate::line_clipper; |
15 | use crate::path_geometry; |
16 | |
17 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
18 | use tiny_skia_path::NoStdFloat; |
19 | |
20 | // This is a fail-safe `arr[n..n+3].try_into().unwrap()` alternative. |
21 | // Everything is checked at compile-time so there is no bound checking and panics. |
22 | macro_rules! copy_3_points { |
23 | ($arr:expr, $i:expr) => { |
24 | [$arr[$i], $arr[$i + 1], $arr[$i + 2]] |
25 | }; |
26 | } |
27 | |
28 | macro_rules! copy_4_points { |
29 | ($arr:expr, $i:expr) => { |
30 | [$arr[$i], $arr[$i + 1], $arr[$i + 2], $arr[$i + 3]] |
31 | }; |
32 | } |
33 | |
34 | /// Max curvature in X and Y split cubic into 9 pieces, * (line + cubic). |
35 | const MAX_VERBS: usize = 18; |
36 | |
37 | pub type ClippedEdges = ArrayVec<PathEdge, MAX_VERBS>; |
38 | |
39 | pub struct EdgeClipper { |
40 | clip: Rect, |
41 | can_cull_to_the_right: bool, |
42 | edges: ClippedEdges, |
43 | } |
44 | |
45 | impl EdgeClipper { |
46 | fn new(clip: Rect, can_cull_to_the_right: bool) -> Self { |
47 | EdgeClipper { |
48 | clip, |
49 | can_cull_to_the_right, |
50 | edges: ArrayVec::new(), |
51 | } |
52 | } |
53 | |
54 | fn clip_line(mut self, p0: Point, p1: Point) -> Option<ClippedEdges> { |
55 | let mut points = [Point::zero(); line_clipper::MAX_POINTS]; |
56 | let points = line_clipper::clip( |
57 | &[p0, p1], |
58 | &self.clip, |
59 | self.can_cull_to_the_right, |
60 | &mut points, |
61 | ); |
62 | if !points.is_empty() { |
63 | for i in 0..points.len() - 1 { |
64 | self.push_line(points[i], points[i + 1]); |
65 | } |
66 | } |
67 | |
68 | if self.edges.is_empty() { |
69 | None |
70 | } else { |
71 | Some(self.edges) |
72 | } |
73 | } |
74 | |
75 | fn push_line(&mut self, p0: Point, p1: Point) { |
76 | self.edges.push(PathEdge::LineTo(p0, p1)); |
77 | } |
78 | |
79 | fn push_vline(&mut self, x: f32, mut y0: f32, mut y1: f32, reverse: bool) { |
80 | if reverse { |
81 | core::mem::swap(&mut y0, &mut y1); |
82 | } |
83 | |
84 | self.edges.push(PathEdge::LineTo( |
85 | Point::from_xy(x, y0), |
86 | Point::from_xy(x, y1), |
87 | )); |
88 | } |
89 | |
90 | fn clip_quad(mut self, p0: Point, p1: Point, p2: Point) -> Option<ClippedEdges> { |
91 | let pts = [p0, p1, p2]; |
92 | let bounds = Rect::from_points(&pts)?; |
93 | |
94 | if !quick_reject(&bounds, &self.clip) { |
95 | let mut mono_y = [Point::zero(); 5]; |
96 | let count_y = path_geometry::chop_quad_at_y_extrema(&pts, &mut mono_y); |
97 | for y in 0..=count_y { |
98 | let mut mono_x = [Point::zero(); 5]; |
99 | let y_points: [Point; 3] = copy_3_points!(mono_y, y * 2); |
100 | let count_x = path_geometry::chop_quad_at_x_extrema(&y_points, &mut mono_x); |
101 | for x in 0..=count_x { |
102 | let x_points: [Point; 3] = copy_3_points!(mono_x, x * 2); |
103 | self.clip_mono_quad(&x_points); |
104 | } |
105 | } |
106 | } |
107 | |
108 | if self.edges.is_empty() { |
109 | None |
110 | } else { |
111 | Some(self.edges) |
112 | } |
113 | } |
114 | |
115 | // src[] must be monotonic in X and Y |
116 | fn clip_mono_quad(&mut self, src: &[Point; 3]) { |
117 | let mut pts = [Point::zero(); 3]; |
118 | let mut reverse = sort_increasing_y(src, &mut pts); |
119 | |
120 | // are we completely above or below |
121 | if pts[2].y <= self.clip.top() || pts[0].y >= self.clip.bottom() { |
122 | return; |
123 | } |
124 | |
125 | // Now chop so that pts is contained within clip in Y |
126 | chop_quad_in_y(&self.clip, &mut pts); |
127 | |
128 | if pts[0].x > pts[2].x { |
129 | pts.swap(0, 2); |
130 | reverse = !reverse; |
131 | } |
132 | debug_assert!(pts[0].x <= pts[1].x); |
133 | debug_assert!(pts[1].x <= pts[2].x); |
134 | |
135 | // Now chop in X has needed, and record the segments |
136 | |
137 | if pts[2].x <= self.clip.left() { |
138 | // wholly to the left |
139 | self.push_vline(self.clip.left(), pts[0].y, pts[2].y, reverse); |
140 | return; |
141 | } |
142 | |
143 | if pts[0].x >= self.clip.right() { |
144 | // wholly to the right |
145 | if !self.can_cull_to_the_right { |
146 | self.push_vline(self.clip.right(), pts[0].y, pts[2].y, reverse); |
147 | } |
148 | |
149 | return; |
150 | } |
151 | |
152 | let mut t = NormalizedF32Exclusive::ANY; |
153 | let mut tmp = [Point::zero(); 5]; |
154 | |
155 | // are we partially to the left |
156 | if pts[0].x < self.clip.left() { |
157 | if chop_mono_quad_at_x(&pts, self.clip.left(), &mut t) { |
158 | path_geometry::chop_quad_at(&pts, t, &mut tmp); |
159 | self.push_vline(self.clip.left(), tmp[0].y, tmp[2].y, reverse); |
160 | // clamp to clean up imprecise numerics in the chop |
161 | tmp[2].x = self.clip.left(); |
162 | tmp[3].x = tmp[3].x.max(self.clip.left()); |
163 | |
164 | pts[0] = tmp[2]; |
165 | pts[1] = tmp[3]; |
166 | } else { |
167 | // if chopMonoQuadAtY failed, then we may have hit inexact numerics |
168 | // so we just clamp against the left |
169 | self.push_vline(self.clip.left(), pts[0].y, pts[2].y, reverse); |
170 | return; |
171 | } |
172 | } |
173 | |
174 | // are we partially to the right |
175 | if pts[2].x > self.clip.right() { |
176 | if chop_mono_quad_at_x(&pts, self.clip.right(), &mut t) { |
177 | path_geometry::chop_quad_at(&pts, t, &mut tmp); |
178 | // clamp to clean up imprecise numerics in the chop |
179 | tmp[1].x = tmp[1].x.min(self.clip.right()); |
180 | tmp[2].x = self.clip.right(); |
181 | |
182 | self.push_quad(©_3_points!(tmp, 0), reverse); |
183 | self.push_vline(self.clip.right(), tmp[2].y, tmp[4].y, reverse); |
184 | } else { |
185 | // if chopMonoQuadAtY failed, then we may have hit inexact numerics |
186 | // so we just clamp against the right |
187 | pts[1].x = pts[1].x.min(self.clip.right()); |
188 | pts[2].x = pts[2].x.min(self.clip.right()); |
189 | self.push_quad(&pts, reverse); |
190 | } |
191 | } else { |
192 | // wholly inside the clip |
193 | self.push_quad(&pts, reverse); |
194 | } |
195 | } |
196 | |
197 | fn push_quad(&mut self, pts: &[Point; 3], reverse: bool) { |
198 | if reverse { |
199 | self.edges.push(PathEdge::QuadTo(pts[2], pts[1], pts[0])); |
200 | } else { |
201 | self.edges.push(PathEdge::QuadTo(pts[0], pts[1], pts[2])); |
202 | } |
203 | } |
204 | |
205 | fn clip_cubic(mut self, p0: Point, p1: Point, p2: Point, p3: Point) -> Option<ClippedEdges> { |
206 | let pts = [p0, p1, p2, p3]; |
207 | let bounds = Rect::from_points(&pts)?; |
208 | |
209 | // check if we're clipped out vertically |
210 | if bounds.bottom() > self.clip.top() && bounds.top() < self.clip.bottom() { |
211 | if too_big_for_reliable_float_math(&bounds) { |
212 | // can't safely clip the cubic, so we give up and draw a line (which we can safely clip) |
213 | // |
214 | // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very |
215 | // likely always handle the cubic safely, but (it seems) at a big loss in speed, so |
216 | // we'd only want to take that alternate impl if needed. |
217 | return self.clip_line(p0, p3); |
218 | } else { |
219 | let mut mono_y = [Point::zero(); 10]; |
220 | let count_y = path_geometry::chop_cubic_at_y_extrema(&pts, &mut mono_y); |
221 | for y in 0..=count_y { |
222 | let mut mono_x = [Point::zero(); 10]; |
223 | let y_points: [Point; 4] = copy_4_points!(mono_y, y * 3); |
224 | let count_x = path_geometry::chop_cubic_at_x_extrema(&y_points, &mut mono_x); |
225 | for x in 0..=count_x { |
226 | let x_points: [Point; 4] = copy_4_points!(mono_x, x * 3); |
227 | self.clip_mono_cubic(&x_points); |
228 | } |
229 | } |
230 | } |
231 | } |
232 | |
233 | if self.edges.is_empty() { |
234 | None |
235 | } else { |
236 | Some(self.edges) |
237 | } |
238 | } |
239 | |
240 | // src[] must be monotonic in X and Y |
241 | fn clip_mono_cubic(&mut self, src: &[Point; 4]) { |
242 | let mut pts = [Point::zero(); 4]; |
243 | let mut reverse = sort_increasing_y(src, &mut pts); |
244 | |
245 | // are we completely above or below |
246 | if pts[3].y <= self.clip.top() || pts[0].y >= self.clip.bottom() { |
247 | return; |
248 | } |
249 | |
250 | // Now chop so that pts is contained within clip in Y |
251 | chop_cubic_in_y(&self.clip, &mut pts); |
252 | |
253 | if pts[0].x > pts[3].x { |
254 | pts.swap(0, 3); |
255 | pts.swap(1, 2); |
256 | reverse = !reverse; |
257 | } |
258 | |
259 | // Now chop in X has needed, and record the segments |
260 | |
261 | if pts[3].x <= self.clip.left() { |
262 | // wholly to the left |
263 | self.push_vline(self.clip.left(), pts[0].y, pts[3].y, reverse); |
264 | return; |
265 | } |
266 | |
267 | if pts[0].x >= self.clip.right() { |
268 | // wholly to the right |
269 | if !self.can_cull_to_the_right { |
270 | self.push_vline(self.clip.right(), pts[0].y, pts[3].y, reverse); |
271 | } |
272 | |
273 | return; |
274 | } |
275 | |
276 | // are we partially to the left |
277 | if pts[0].x < self.clip.left() { |
278 | let mut tmp = [Point::zero(); 7]; |
279 | chop_mono_cubic_at_x(&pts, self.clip.left(), &mut tmp); |
280 | self.push_vline(self.clip.left(), tmp[0].y, tmp[3].y, reverse); |
281 | |
282 | // tmp[3, 4].fX should all be to the right of clip.left(). |
283 | // Since we can't trust the numerics of |
284 | // the chopper, we force those conditions now |
285 | tmp[3].x = self.clip.left(); |
286 | tmp[4].x = tmp[4].x.max(self.clip.left()); |
287 | |
288 | pts[0] = tmp[3]; |
289 | pts[1] = tmp[4]; |
290 | pts[2] = tmp[5]; |
291 | } |
292 | |
293 | // are we partially to the right |
294 | if pts[3].x > self.clip.right() { |
295 | let mut tmp = [Point::zero(); 7]; |
296 | chop_mono_cubic_at_x(&pts, self.clip.right(), &mut tmp); |
297 | tmp[3].x = self.clip.right(); |
298 | tmp[2].x = tmp[2].x.min(self.clip.right()); |
299 | |
300 | self.push_cubic(©_4_points!(tmp, 0), reverse); |
301 | self.push_vline(self.clip.right(), tmp[3].y, tmp[6].y, reverse); |
302 | } else { |
303 | // wholly inside the clip |
304 | self.push_cubic(&pts, reverse); |
305 | } |
306 | } |
307 | |
308 | fn push_cubic(&mut self, pts: &[Point; 4], reverse: bool) { |
309 | if reverse { |
310 | self.edges |
311 | .push(PathEdge::CubicTo(pts[3], pts[2], pts[1], pts[0])); |
312 | } else { |
313 | self.edges |
314 | .push(PathEdge::CubicTo(pts[0], pts[1], pts[2], pts[3])); |
315 | } |
316 | } |
317 | } |
318 | |
319 | pub struct EdgeClipperIter<'a> { |
320 | edge_iter: PathEdgeIter<'a>, |
321 | clip: Rect, |
322 | can_cull_to_the_right: bool, |
323 | } |
324 | |
325 | impl<'a> EdgeClipperIter<'a> { |
326 | pub fn new(path: &'a Path, clip: Rect, can_cull_to_the_right: bool) -> Self { |
327 | EdgeClipperIter { |
328 | edge_iter: edge_iter(path), |
329 | clip, |
330 | can_cull_to_the_right, |
331 | } |
332 | } |
333 | } |
334 | |
335 | impl Iterator for EdgeClipperIter<'_> { |
336 | type Item = ClippedEdges; |
337 | |
338 | fn next(&mut self) -> Option<Self::Item> { |
339 | for edge in &mut self.edge_iter { |
340 | let clipper = EdgeClipper::new(self.clip, self.can_cull_to_the_right); |
341 | |
342 | match edge { |
343 | PathEdge::LineTo(p0, p1) => { |
344 | if let Some(edges) = clipper.clip_line(p0, p1) { |
345 | return Some(edges); |
346 | } |
347 | } |
348 | PathEdge::QuadTo(p0, p1, p2) => { |
349 | if let Some(edges) = clipper.clip_quad(p0, p1, p2) { |
350 | return Some(edges); |
351 | } |
352 | } |
353 | PathEdge::CubicTo(p0, p1, p2, p3) => { |
354 | if let Some(edges) = clipper.clip_cubic(p0, p1, p2, p3) { |
355 | return Some(edges); |
356 | } |
357 | } |
358 | } |
359 | } |
360 | |
361 | None |
362 | } |
363 | } |
364 | |
365 | fn quick_reject(bounds: &Rect, clip: &Rect) -> bool { |
366 | bounds.top() >= clip.bottom() || bounds.bottom() <= clip.top() |
367 | } |
368 | |
369 | // src[] must be monotonic in Y. This routine copies src into dst, and sorts |
370 | // it to be increasing in Y. If it had to reverse the order of the points, |
371 | // it returns true, otherwise it returns false |
372 | fn sort_increasing_y(src: &[Point], dst: &mut [Point]) -> bool { |
373 | // We need the data to be monotonically increasing in Y. |
374 | // Never fails, because src is always non-empty. |
375 | if src[0].y > src.last().unwrap().y { |
376 | for (i: usize, p: &Point) in src.iter().rev().enumerate() { |
377 | dst[i] = *p; |
378 | } |
379 | |
380 | true |
381 | } else { |
382 | dst[0..src.len()].copy_from_slice(src); |
383 | false |
384 | } |
385 | } |
386 | |
387 | /// Modifies pts[] in place so that it is clipped in Y to the clip rect. |
388 | fn chop_quad_in_y(clip: &Rect, pts: &mut [Point; 3]) { |
389 | let mut t = NormalizedF32Exclusive::ANY; |
390 | let mut tmp = [Point::zero(); 5]; |
391 | |
392 | // are we partially above |
393 | if pts[0].y < clip.top() { |
394 | if chop_mono_quad_at_y(pts, clip.top(), &mut t) { |
395 | // take the 2nd chopped quad |
396 | path_geometry::chop_quad_at(pts, t, &mut tmp); |
397 | // clamp to clean up imprecise numerics in the chop |
398 | tmp[2].y = clip.top(); |
399 | tmp[3].y = tmp[3].y.max(clip.top()); |
400 | |
401 | pts[0] = tmp[2]; |
402 | pts[1] = tmp[3]; |
403 | } else { |
404 | // if chop_mono_quad_at_y failed, then we may have hit inexact numerics |
405 | // so we just clamp against the top |
406 | for p in pts.iter_mut() { |
407 | if p.y < clip.top() { |
408 | p.y = clip.top(); |
409 | } |
410 | } |
411 | } |
412 | } |
413 | |
414 | // are we partially below |
415 | if pts[2].y > clip.bottom() { |
416 | if chop_mono_quad_at_y(pts, clip.bottom(), &mut t) { |
417 | path_geometry::chop_quad_at(pts, t, &mut tmp); |
418 | // clamp to clean up imprecise numerics in the chop |
419 | tmp[1].y = tmp[1].y.min(clip.bottom()); |
420 | tmp[2].y = clip.bottom(); |
421 | |
422 | pts[1] = tmp[1]; |
423 | pts[2] = tmp[2]; |
424 | } else { |
425 | // if chop_mono_quad_at_y failed, then we may have hit inexact numerics |
426 | // so we just clamp against the bottom |
427 | for p in pts.iter_mut() { |
428 | if p.y > clip.bottom() { |
429 | p.y = clip.bottom(); |
430 | } |
431 | } |
432 | } |
433 | } |
434 | } |
435 | |
436 | fn chop_mono_quad_at_x(pts: &[Point; 3], x: f32, t: &mut NormalizedF32Exclusive) -> bool { |
437 | chop_mono_quad_at(c0:pts[0].x, c1:pts[1].x, c2:pts[2].x, target:x, t) |
438 | } |
439 | |
440 | fn chop_mono_quad_at_y(pts: &[Point; 3], y: f32, t: &mut NormalizedF32Exclusive) -> bool { |
441 | chop_mono_quad_at(c0:pts[0].y, c1:pts[1].y, c2:pts[2].y, target:y, t) |
442 | } |
443 | |
444 | fn chop_mono_quad_at( |
445 | c0: f32, |
446 | c1: f32, |
447 | c2: f32, |
448 | target: f32, |
449 | t: &mut NormalizedF32Exclusive, |
450 | ) -> bool { |
451 | // Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2 |
452 | // We solve for t, using quadratic equation, hence we have to rearrange |
453 | // our coefficients to look like At^2 + Bt + C |
454 | let a: f32 = c0 - c1 - c1 + c2; |
455 | let b: f32 = 2.0 * (c1 - c0); |
456 | let c: f32 = c0 - target; |
457 | |
458 | let mut roots: [NormalizedF32Exclusive; 3] = path_geometry::new_t_values(); |
459 | let count: usize = path_geometry::find_unit_quad_roots(a, b, c, &mut roots); |
460 | if count != 0 { |
461 | *t = roots[0]; |
462 | true |
463 | } else { |
464 | false |
465 | } |
466 | } |
467 | |
468 | fn too_big_for_reliable_float_math(r: &Rect) -> bool { |
469 | // limit set as the largest float value for which we can still reliably compute things like |
470 | // - chopping at XY extrema |
471 | // - chopping at Y or X values for clipping |
472 | // |
473 | // Current value chosen just by experiment. Larger (and still succeeds) is always better. |
474 | |
475 | let limit: f32 = (1 << 22) as f32; |
476 | r.left() < -limit || r.top() < -limit || r.right() > limit || r.bottom() > limit |
477 | } |
478 | |
479 | /// Modifies pts[] in place so that it is clipped in Y to the clip rect. |
480 | fn chop_cubic_in_y(clip: &Rect, pts: &mut [Point; 4]) { |
481 | // are we partially above |
482 | if pts[0].y < clip.top() { |
483 | let mut tmp = [Point::zero(); 7]; |
484 | chop_mono_cubic_at_y(pts, clip.top(), &mut tmp); |
485 | |
486 | // For a large range in the points, we can do a poor job of chopping, such that the t |
487 | // we computed resulted in the lower cubic still being partly above the clip. |
488 | // |
489 | // If just the first or first 2 Y values are above the fTop, we can just smash them |
490 | // down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really |
491 | // distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as |
492 | // a guess, and re-chop against fTop. Then we fall through to checking if we need to |
493 | // smash the first 1 or 2 Y values. |
494 | if tmp[3].y < clip.top() && tmp[4].y < clip.top() && tmp[5].y < clip.top() { |
495 | let tmp2: [Point; 4] = copy_4_points!(tmp, 3); |
496 | chop_mono_cubic_at_y(&tmp2, clip.top(), &mut tmp); |
497 | } |
498 | |
499 | // tmp[3, 4].y should all be to the below clip.fTop. |
500 | // Since we can't trust the numerics of the chopper, we force those conditions now |
501 | tmp[3].y = clip.top(); |
502 | tmp[4].y = tmp[4].y.max(clip.top()); |
503 | |
504 | pts[0] = tmp[3]; |
505 | pts[1] = tmp[4]; |
506 | pts[2] = tmp[5]; |
507 | } |
508 | |
509 | // are we partially below |
510 | if pts[3].y > clip.bottom() { |
511 | let mut tmp = [Point::zero(); 7]; |
512 | chop_mono_cubic_at_y(pts, clip.bottom(), &mut tmp); |
513 | tmp[3].y = clip.bottom(); |
514 | tmp[2].y = tmp[2].y.min(clip.bottom()); |
515 | |
516 | pts[1] = tmp[1]; |
517 | pts[2] = tmp[2]; |
518 | pts[3] = tmp[3]; |
519 | } |
520 | } |
521 | |
522 | fn chop_mono_cubic_at_x(src: &[Point; 4], x: f32, dst: &mut [Point; 7]) { |
523 | if path_geometry::chop_mono_cubic_at_x(src, x, dst) { |
524 | return; |
525 | } |
526 | |
527 | let src_values: [f32; 4] = [src[0].x, src[1].x, src[2].x, src[3].x]; |
528 | path_geometry::chop_cubic_at2(src, t:mono_cubic_closest_t(&src_values, x), dst); |
529 | } |
530 | |
531 | fn chop_mono_cubic_at_y(src: &[Point; 4], y: f32, dst: &mut [Point; 7]) { |
532 | if path_geometry::chop_mono_cubic_at_y(src, y, dst) { |
533 | return; |
534 | } |
535 | |
536 | let src_values: [f32; 4] = [src[0].y, src[1].y, src[2].y, src[3].y]; |
537 | path_geometry::chop_cubic_at2(src, t:mono_cubic_closest_t(&src_values, x:y), dst); |
538 | } |
539 | |
540 | fn mono_cubic_closest_t(src: &[f32; 4], mut x: f32) -> NormalizedF32Exclusive { |
541 | let mut t = 0.5; |
542 | let mut last_t; |
543 | let mut best_t = t; |
544 | let mut step = 0.25; |
545 | let d = src[0]; |
546 | let a = src[3] + 3.0 * (src[1] - src[2]) - d; |
547 | let b = 3.0 * (src[2] - src[1] - src[1] + d); |
548 | let c = 3.0 * (src[1] - d); |
549 | x -= d; |
550 | let mut closest = SCALAR_MAX; |
551 | loop { |
552 | let loc = ((a * t + b) * t + c) * t; |
553 | let dist = (loc - x).abs(); |
554 | if closest > dist { |
555 | closest = dist; |
556 | best_t = t; |
557 | } |
558 | |
559 | last_t = t; |
560 | t += if loc < x { step } else { -step }; |
561 | step *= 0.5; |
562 | |
563 | if !(closest > 0.25 && last_t != t) { |
564 | break; |
565 | } |
566 | } |
567 | |
568 | NormalizedF32Exclusive::new(best_t).unwrap() |
569 | } |
570 | |