ruisa_path/
path_builder.rs

1// Copyright 2006 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// NOTE: this is not SkPathBuilder, but rather a reimplementation of SkPath.
8
9use alloc::vec;
10use alloc::vec::Vec;
11
12use crate::{Path, Point, Rect};
13
14use crate::path::PathVerb;
15use crate::path_geometry;
16use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2};
17
18#[derive(Copy, Clone, PartialEq, Debug)]
19pub(crate) enum PathDirection {
20    /// Clockwise direction for adding closed contours.
21    CW,
22    /// Counter-clockwise direction for adding closed contours.
23    CCW,
24}
25
26/// A path builder.
27#[derive(Clone, Default, Debug)]
28pub struct PathBuilder {
29    pub(crate) verbs: Vec<PathVerb>,
30    pub(crate) points: Vec<Point>,
31    pub(crate) last_move_to_index: usize,
32    pub(crate) move_to_required: bool,
33}
34
35impl PathBuilder {
36    /// Creates a new builder.
37    pub fn new() -> Self {
38        PathBuilder {
39            verbs: Vec::new(),
40            points: Vec::new(),
41            last_move_to_index: 0,
42            move_to_required: true,
43        }
44    }
45
46    /// Creates a new builder with a specified capacity.
47    ///
48    /// Number of points depends on a verb type:
49    ///
50    /// - Move - 1
51    /// - Line - 1
52    /// - Quad - 2
53    /// - Cubic - 3
54    /// - Close - 0
55    pub fn with_capacity(verbs_capacity: usize, points_capacity: usize) -> Self {
56        PathBuilder {
57            verbs: Vec::with_capacity(verbs_capacity),
58            points: Vec::with_capacity(points_capacity),
59            last_move_to_index: 0,
60            move_to_required: true,
61        }
62    }
63
64    /// Creates a new `Path` from `Rect`.
65    ///
66    /// Never fails since `Rect` is always valid.
67    ///
68    /// Segments are created clockwise: TopLeft -> TopRight -> BottomRight -> BottomLeft
69    ///
70    /// The contour is closed.
71    pub fn from_rect(rect: Rect) -> Path {
72        let verbs = vec![
73            PathVerb::Move,
74            PathVerb::Line,
75            PathVerb::Line,
76            PathVerb::Line,
77            PathVerb::Close,
78        ];
79
80        let points = vec![
81            Point::from_xy(rect.left(), rect.top()),
82            Point::from_xy(rect.right(), rect.top()),
83            Point::from_xy(rect.right(), rect.bottom()),
84            Point::from_xy(rect.left(), rect.bottom()),
85        ];
86
87        Path {
88            bounds: rect,
89            verbs,
90            points,
91        }
92    }
93
94    /// Creates a new `Path` from a circle.
95    ///
96    /// See [`PathBuilder::push_circle`] for details.
97    pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option<Path> {
98        let mut b = PathBuilder::new();
99        b.push_circle(cx, cy, radius);
100        b.finish()
101    }
102
103    /// Creates a new `Path` from an oval.
104    ///
105    /// See [`PathBuilder::push_oval`] for details.
106    pub fn from_oval(oval: Rect) -> Option<Path> {
107        let mut b = PathBuilder::new();
108        b.push_oval(oval);
109        b.finish()
110    }
111
112    pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) {
113        self.verbs.reserve(additional_verbs);
114        self.points.reserve(additional_points);
115    }
116
117    /// Returns the current number of segments in the builder.
118    pub fn len(&self) -> usize {
119        self.verbs.len()
120    }
121
122    /// Checks if the builder has any segments added.
123    pub fn is_empty(&self) -> bool {
124        self.verbs.is_empty()
125    }
126
127    /// Adds beginning of a contour.
128    ///
129    /// Multiple continuous MoveTo segments are not allowed.
130    /// If the previous segment was also MoveTo, it will be overwritten with the current one.
131    pub fn move_to(&mut self, x: f32, y: f32) {
132        if let Some(PathVerb::Move) = self.verbs.last() {
133            let last_idx = self.points.len() - 1;
134            self.points[last_idx] = Point::from_xy(x, y);
135        } else {
136            self.last_move_to_index = self.points.len();
137            self.move_to_required = false;
138
139            self.verbs.push(PathVerb::Move);
140            self.points.push(Point::from_xy(x, y));
141        }
142    }
143
144    fn inject_move_to_if_needed(&mut self) {
145        if self.move_to_required {
146            match self.points.get(self.last_move_to_index).cloned() {
147                Some(p) => self.move_to(p.x, p.y),
148                None => self.move_to(0.0, 0.0),
149            }
150        }
151    }
152
153    /// Adds a line from the last point.
154    ///
155    /// - If `Path` is empty - adds Move(0, 0) first.
156    /// - If `Path` ends with Close - adds Move(last_x, last_y) first.
157    pub fn line_to(&mut self, x: f32, y: f32) {
158        self.inject_move_to_if_needed();
159
160        self.verbs.push(PathVerb::Line);
161        self.points.push(Point::from_xy(x, y));
162    }
163
164    /// Adds a quad curve from the last point to `x`, `y`.
165    ///
166    /// - If `Path` is empty - adds Move(0, 0) first.
167    /// - If `Path` ends with Close - adds Move(last_x, last_y) first.
168    pub fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
169        self.inject_move_to_if_needed();
170
171        self.verbs.push(PathVerb::Quad);
172        self.points.push(Point::from_xy(x1, y1));
173        self.points.push(Point::from_xy(x, y));
174    }
175
176    pub(crate) fn quad_to_pt(&mut self, p1: Point, p: Point) {
177        self.quad_to(p1.x, p1.y, p.x, p.y);
178    }
179
180    // We do not support conic segments, but Skia still relies on them from time to time.
181    // This method will simply convert the input data into quad segments.
182    pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) {
183        // check for <= 0 or NaN with this test
184        if !(weight > 0.0) {
185            self.line_to(x, y);
186        } else if !weight.is_finite() {
187            self.line_to(x1, y1);
188            self.line_to(x, y);
189        } else if weight == 1.0 {
190            self.quad_to(x1, y1, x, y);
191        } else {
192            self.inject_move_to_if_needed();
193
194            let last = self.last_point().unwrap();
195            let quadder = path_geometry::AutoConicToQuads::compute(
196                last,
197                Point::from_xy(x1, y1),
198                Point::from_xy(x, y),
199                weight,
200            );
201            if let Some(quadder) = quadder {
202                // Points are ordered as: 0 - 1 2 - 3 4 - 5 6 - ..
203                // `count` is a number of pairs +1
204                let mut offset = 1;
205                for _ in 0..quadder.len {
206                    let pt1 = quadder.points[offset + 0];
207                    let pt2 = quadder.points[offset + 1];
208                    self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
209                    offset += 2;
210                }
211            }
212        }
213    }
214
215    pub(crate) fn conic_points_to(&mut self, pt1: Point, pt2: Point, weight: f32) {
216        self.conic_to(pt1.x, pt1.y, pt2.x, pt2.y, weight);
217    }
218
219    /// Adds a cubic curve from the last point to `x`, `y`.
220    ///
221    /// - If `Path` is empty - adds Move(0, 0) first.
222    /// - If `Path` ends with Close - adds Move(last_x, last_y) first.
223    pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
224        self.inject_move_to_if_needed();
225
226        self.verbs.push(PathVerb::Cubic);
227        self.points.push(Point::from_xy(x1, y1));
228        self.points.push(Point::from_xy(x2, y2));
229        self.points.push(Point::from_xy(x, y));
230    }
231
232    pub(crate) fn cubic_to_pt(&mut self, p1: Point, p2: Point, p: Point) {
233        self.cubic_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y);
234    }
235
236    /// Closes the current contour.
237    ///
238    /// A closed contour connects the first and the last Point
239    /// with a line, forming a continuous loop.
240    ///
241    /// Does nothing when `Path` is empty or already closed.
242    ///
243    /// Open and closed contour will be filled the same way.
244    /// Stroking an open contour will add LineCap at contour's start and end.
245    /// Stroking an closed contour will add LineJoin at contour's start and end.
246    pub fn close(&mut self) {
247        // don't add a close if it's the first verb or a repeat
248        if !self.verbs.is_empty() {
249            if self.verbs.last().cloned() != Some(PathVerb::Close) {
250                self.verbs.push(PathVerb::Close);
251            }
252        }
253
254        self.move_to_required = true;
255    }
256
257    /// Returns the last point if any.
258    pub fn last_point(&self) -> Option<Point> {
259        self.points.last().cloned()
260    }
261
262    pub(crate) fn set_last_point(&mut self, pt: Point) {
263        match self.points.last_mut() {
264            Some(last) => *last = pt,
265            None => self.move_to(pt.x, pt.y),
266        }
267    }
268
269    pub(crate) fn is_zero_length_since_point(&self, start_pt_index: usize) -> bool {
270        let count = self.points.len() - start_pt_index;
271        if count < 2 {
272            return true;
273        }
274
275        let first = self.points[start_pt_index];
276        for i in 1..count {
277            if first != self.points[start_pt_index + i] {
278                return false;
279            }
280        }
281
282        true
283    }
284
285    /// Adds a rectangle contour.
286    ///
287    /// The contour is closed and has a clock-wise direction.
288    ///
289    /// Does nothing when:
290    /// - any value is not finite or really large
291    pub fn push_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
292        if let Some(rect) = Rect::from_xywh(x, y, w, h) {
293            self.move_to(rect.left(), rect.top());
294            self.line_to(rect.right(), rect.top());
295            self.line_to(rect.right(), rect.bottom());
296            self.line_to(rect.left(), rect.bottom());
297            self.close();
298        }
299    }
300
301    /// Adds an oval contour bounded by the provided rectangle.
302    ///
303    /// The contour is closed and has a clock-wise direction.
304    pub fn push_oval(&mut self, oval: Rect) {
305        let cx = oval.left().half() + oval.right().half();
306        let cy = oval.top().half() + oval.bottom().half();
307
308        let oval_points = [
309            Point::from_xy(cx, oval.bottom()),
310            Point::from_xy(oval.left(), cy),
311            Point::from_xy(cx, oval.top()),
312            Point::from_xy(oval.right(), cy),
313        ];
314
315        let rect_points = [
316            Point::from_xy(oval.right(), oval.bottom()),
317            Point::from_xy(oval.left(), oval.bottom()),
318            Point::from_xy(oval.left(), oval.top()),
319            Point::from_xy(oval.right(), oval.top()),
320        ];
321
322        let weight = SCALAR_ROOT_2_OVER_2;
323        self.move_to(oval_points[3].x, oval_points[3].y);
324        for (p1, p2) in rect_points.iter().zip(oval_points.iter()) {
325            self.conic_points_to(*p1, *p2, weight);
326        }
327        self.close();
328    }
329
330    /// Adds a circle contour.
331    ///
332    /// The contour is closed and has a clock-wise direction.
333    ///
334    /// Does nothing when:
335    /// - `radius` <= 0
336    /// - any value is not finite or really large
337    pub fn push_circle(&mut self, x: f32, y: f32, r: f32) {
338        if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) {
339            self.push_oval(r);
340        }
341    }
342
343    pub(crate) fn push_path(&mut self, other: &PathBuilder) {
344        if other.is_empty() {
345            return;
346        }
347
348        if self.last_move_to_index != 0 {
349            self.last_move_to_index = self.points.len() + other.last_move_to_index;
350        }
351
352        self.verbs.extend_from_slice(&other.verbs);
353        self.points.extend_from_slice(&other.points);
354    }
355
356    /// Appends, in a reverse order, the first contour of path ignoring path's last point.
357    pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) {
358        if other.is_empty() {
359            return;
360        }
361
362        debug_assert_eq!(other.verbs[0], PathVerb::Move);
363
364        let mut points_offset = other.points.len() - 1;
365        for verb in other.verbs.iter().rev() {
366            match verb {
367                PathVerb::Move => {
368                    // if the path has multiple contours, stop after reversing the last
369                    break;
370                }
371                PathVerb::Line => {
372                    // We're moving one point back manually, to prevent points_offset overflow.
373                    let pt = other.points[points_offset - 1];
374                    points_offset -= 1;
375                    self.line_to(pt.x, pt.y);
376                }
377                PathVerb::Quad => {
378                    let pt1 = other.points[points_offset - 1];
379                    let pt2 = other.points[points_offset - 2];
380                    points_offset -= 2;
381                    self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
382                }
383                PathVerb::Cubic => {
384                    let pt1 = other.points[points_offset - 1];
385                    let pt2 = other.points[points_offset - 2];
386                    let pt3 = other.points[points_offset - 3];
387                    points_offset -= 3;
388                    self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
389                }
390                PathVerb::Close => {}
391            }
392        }
393    }
394
395    /// Reset the builder.
396    ///
397    /// Memory is not deallocated.
398    pub fn clear(&mut self) {
399        self.verbs.clear();
400        self.points.clear();
401        self.last_move_to_index = 0;
402        self.move_to_required = true;
403    }
404
405    /// Finishes the builder and returns a `Path`.
406    ///
407    /// Returns `None` when `Path` is empty or has invalid bounds.
408    pub fn finish(self) -> Option<Path> {
409        if self.is_empty() {
410            return None;
411        }
412
413        // Just a move to? Bail.
414        if self.verbs.len() == 1 {
415            return None;
416        }
417
418        let bounds = Rect::from_points(&self.points)?;
419
420        Some(Path {
421            bounds,
422            verbs: self.verbs,
423            points: self.points,
424        })
425    }
426}