ruisa/
edge_builder.rs

1// Copyright 2011 Google Inc.
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
7use alloc::vec::Vec;
8
9use ruisa_path::{PathVerb, ScreenIntRect};
10
11use crate::{Path, Point};
12
13use crate::edge::{CubicEdge, Edge, LineEdge, QuadraticEdge};
14use crate::edge_clipper::EdgeClipperIter;
15use crate::path_geometry;
16
17#[derive(Copy, Clone, PartialEq, Debug)]
18enum Combine {
19    No,
20    Partial,
21    Total,
22}
23
24#[derive(Copy, Clone, Debug)]
25pub struct ShiftedIntRect {
26    shifted: ScreenIntRect,
27    shift: i32,
28}
29
30impl ShiftedIntRect {
31    pub fn new(rect: &ScreenIntRect, shift: i32) -> Option<Self> {
32        let shifted = ScreenIntRect::from_xywh(
33            rect.x() << shift,
34            rect.y() << shift,
35            rect.width() << shift,
36            rect.height() << shift,
37        )?;
38        Some(ShiftedIntRect { shifted, shift })
39    }
40
41    pub fn shifted(&self) -> &ScreenIntRect {
42        &self.shifted
43    }
44
45    pub fn recover(&self) -> ScreenIntRect {
46        ScreenIntRect::from_xywh(
47            self.shifted.x() >> self.shift,
48            self.shifted.y() >> self.shift,
49            self.shifted.width() >> self.shift,
50            self.shifted.height() >> self.shift,
51        )
52        .unwrap() // cannot fail, because the original rect was valid
53    }
54}
55
56pub struct BasicEdgeBuilder {
57    edges: Vec<Edge>,
58    clip_shift: i32,
59}
60
61impl BasicEdgeBuilder {
62    pub fn new(clip_shift: i32) -> Self {
63        BasicEdgeBuilder {
64            edges: Vec::with_capacity(64), // TODO: stack array + fallback
65            clip_shift,
66        }
67    }
68
69    // Skia returns a linked list here, but it's a nightmare to use in Rust,
70    // so we're mimicking it with Vec.
71    pub fn build_edges(
72        path: &Path,
73        clip: Option<&ShiftedIntRect>,
74        clip_shift: i32,
75    ) -> Option<Vec<Edge>> {
76        // If we're convex, then we need both edges, even if the right edge is past the clip.
77        // let can_cull_to_the_right = !path.isConvex();
78        let can_cull_to_the_right = false; // TODO: this
79
80        let mut builder = BasicEdgeBuilder::new(clip_shift);
81        if !builder.build(path, clip, can_cull_to_the_right) {
82            log::warn!("infinite or NaN segments detected during edges building");
83            return None;
84        }
85
86        if builder.edges.len() < 2 {
87            return None;
88        }
89
90        Some(builder.edges)
91    }
92
93    // TODO: build_poly
94    pub fn build(
95        &mut self,
96        path: &Path,
97        clip: Option<&ShiftedIntRect>,
98        can_cull_to_the_right: bool,
99    ) -> bool {
100        if let Some(clip) = clip {
101            let clip = clip.recover().to_rect();
102            for edges in EdgeClipperIter::new(path, clip, can_cull_to_the_right) {
103                for edge in edges {
104                    match edge {
105                        PathEdge::LineTo(p0, p1) => {
106                            if !p0.is_finite() || !p1.is_finite() {
107                                return false;
108                            }
109
110                            self.push_line(&[p0, p1])
111                        }
112                        PathEdge::QuadTo(p0, p1, p2) => {
113                            if !p0.is_finite() || !p1.is_finite() || !p2.is_finite() {
114                                return false;
115                            }
116
117                            self.push_quad(&[p0, p1, p2])
118                        }
119                        PathEdge::CubicTo(p0, p1, p2, p3) => {
120                            if !p0.is_finite()
121                                || !p1.is_finite()
122                                || !p2.is_finite()
123                                || !p3.is_finite()
124                            {
125                                return false;
126                            }
127
128                            self.push_cubic(&[p0, p1, p2, p3])
129                        }
130                    }
131                }
132            }
133        } else {
134            for edge in edge_iter(path) {
135                match edge {
136                    PathEdge::LineTo(p0, p1) => {
137                        self.push_line(&[p0, p1]);
138                    }
139                    PathEdge::QuadTo(p0, p1, p2) => {
140                        let points = [p0, p1, p2];
141                        let mut mono_x = [Point::zero(); 5];
142                        let n = path_geometry::chop_quad_at_y_extrema(&points, &mut mono_x);
143                        for i in 0..=n {
144                            self.push_quad(&mono_x[i * 2..]);
145                        }
146                    }
147                    PathEdge::CubicTo(p0, p1, p2, p3) => {
148                        let points = [p0, p1, p2, p3];
149                        let mut mono_y = [Point::zero(); 10];
150                        let n = path_geometry::chop_cubic_at_y_extrema(&points, &mut mono_y);
151                        for i in 0..=n {
152                            self.push_cubic(&mono_y[i * 3..]);
153                        }
154                    }
155                }
156            }
157        }
158
159        true
160    }
161
162    fn push_line(&mut self, points: &[Point; 2]) {
163        if let Some(edge) = LineEdge::new(points[0], points[1], self.clip_shift) {
164            let combine = if edge.is_vertical() && !self.edges.is_empty() {
165                if let Some(Edge::Line(last)) = self.edges.last_mut() {
166                    combine_vertical(&edge, last)
167                } else {
168                    Combine::No
169                }
170            } else {
171                Combine::No
172            };
173
174            match combine {
175                Combine::Total => {
176                    self.edges.pop();
177                }
178                Combine::Partial => {}
179                Combine::No => self.edges.push(Edge::Line(edge)),
180            }
181        }
182    }
183
184    fn push_quad(&mut self, points: &[Point]) {
185        if let Some(edge) = QuadraticEdge::new(points, self.clip_shift) {
186            self.edges.push(Edge::Quadratic(edge));
187        }
188    }
189
190    fn push_cubic(&mut self, points: &[Point]) {
191        if let Some(edge) = CubicEdge::new(points, self.clip_shift) {
192            self.edges.push(Edge::Cubic(edge));
193        }
194    }
195}
196
197fn combine_vertical(edge: &LineEdge, last: &mut LineEdge) -> Combine {
198    if last.dx != 0 || edge.x != last.x {
199        return Combine::No;
200    }
201
202    if edge.winding == last.winding {
203        return if edge.last_y + 1 == last.first_y {
204            last.first_y = edge.first_y;
205            Combine::Partial
206        } else if edge.first_y == last.last_y + 1 {
207            last.last_y = edge.last_y;
208            Combine::Partial
209        } else {
210            Combine::No
211        };
212    }
213
214    if edge.first_y == last.first_y {
215        return if edge.last_y == last.last_y {
216            Combine::Total
217        } else if edge.last_y < last.last_y {
218            last.first_y = edge.last_y + 1;
219            Combine::Partial
220        } else {
221            last.first_y = last.last_y + 1;
222            last.last_y = edge.last_y;
223            last.winding = edge.winding;
224            Combine::Partial
225        };
226    }
227
228    if edge.last_y == last.last_y {
229        if edge.first_y > last.first_y {
230            last.last_y = edge.first_y - 1;
231        } else {
232            last.last_y = last.first_y - 1;
233            last.first_y = edge.first_y;
234            last.winding = edge.winding;
235        }
236
237        return Combine::Partial;
238    }
239
240    Combine::No
241}
242
243pub fn edge_iter(path: &Path) -> PathEdgeIter {
244    PathEdgeIter {
245        path,
246        verb_index: 0,
247        points_index: 0,
248        move_to: Point::zero(),
249        needs_close_line: false,
250    }
251}
252
253#[derive(Copy, Clone, PartialEq, Debug)]
254pub enum PathEdge {
255    LineTo(Point, Point),
256    QuadTo(Point, Point, Point),
257    CubicTo(Point, Point, Point, Point),
258}
259
260/// Lightweight variant of PathIter that only returns segments (e.g. lines/quads).
261///
262/// Does not return Move or Close. Always "auto-closes" each contour.
263pub struct PathEdgeIter<'a> {
264    path: &'a Path,
265    verb_index: usize,
266    points_index: usize,
267    move_to: Point,
268    needs_close_line: bool,
269}
270
271impl<'a> PathEdgeIter<'a> {
272    fn close_line(&mut self) -> Option<PathEdge> {
273        self.needs_close_line = false;
274
275        let edge = PathEdge::LineTo(self.path.points()[self.points_index - 1], self.move_to);
276        Some(edge)
277    }
278}
279
280impl<'a> Iterator for PathEdgeIter<'a> {
281    type Item = PathEdge;
282
283    fn next(&mut self) -> Option<Self::Item> {
284        if self.verb_index < self.path.verbs().len() {
285            let verb = self.path.verbs()[self.verb_index];
286            self.verb_index += 1;
287
288            match verb {
289                PathVerb::Move => {
290                    if self.needs_close_line {
291                        let res = self.close_line();
292                        self.move_to = self.path.points()[self.points_index];
293                        self.points_index += 1;
294                        return res;
295                    }
296
297                    self.move_to = self.path.points()[self.points_index];
298                    self.points_index += 1;
299                    self.next()
300                }
301                PathVerb::Close => {
302                    if self.needs_close_line {
303                        return self.close_line();
304                    }
305
306                    self.next()
307                }
308                _ => {
309                    // Actual edge.
310                    self.needs_close_line = true;
311
312                    let edge;
313                    match verb {
314                        PathVerb::Line => {
315                            edge = PathEdge::LineTo(
316                                self.path.points()[self.points_index - 1],
317                                self.path.points()[self.points_index + 0],
318                            );
319                            self.points_index += 1;
320                        }
321                        PathVerb::Quad => {
322                            edge = PathEdge::QuadTo(
323                                self.path.points()[self.points_index - 1],
324                                self.path.points()[self.points_index + 0],
325                                self.path.points()[self.points_index + 1],
326                            );
327                            self.points_index += 2;
328                        }
329                        PathVerb::Cubic => {
330                            edge = PathEdge::CubicTo(
331                                self.path.points()[self.points_index - 1],
332                                self.path.points()[self.points_index + 0],
333                                self.path.points()[self.points_index + 1],
334                                self.path.points()[self.points_index + 2],
335                            );
336                            self.points_index += 3;
337                        }
338                        _ => unreachable!(),
339                    };
340
341                    Some(edge)
342                }
343            }
344        } else if self.needs_close_line {
345            self.close_line()
346        } else {
347            None
348        }
349    }
350}