tiny_skia_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    pub fn push_rect(&mut self, rect: Rect) {
289        self.move_to(rect.left(), rect.top());
290        self.line_to(rect.right(), rect.top());
291        self.line_to(rect.right(), rect.bottom());
292        self.line_to(rect.left(), rect.bottom());
293        self.close();
294    }
295
296    /// Adds an oval contour bounded by the provided rectangle.
297    ///
298    /// The contour is closed and has a clock-wise direction.
299    pub fn push_oval(&mut self, oval: Rect) {
300        let cx = oval.left().half() + oval.right().half();
301        let cy = oval.top().half() + oval.bottom().half();
302
303        let oval_points = [
304            Point::from_xy(cx, oval.bottom()),
305            Point::from_xy(oval.left(), cy),
306            Point::from_xy(cx, oval.top()),
307            Point::from_xy(oval.right(), cy),
308        ];
309
310        let rect_points = [
311            Point::from_xy(oval.right(), oval.bottom()),
312            Point::from_xy(oval.left(), oval.bottom()),
313            Point::from_xy(oval.left(), oval.top()),
314            Point::from_xy(oval.right(), oval.top()),
315        ];
316
317        let weight = SCALAR_ROOT_2_OVER_2;
318        self.move_to(oval_points[3].x, oval_points[3].y);
319        for (p1, p2) in rect_points.iter().zip(oval_points.iter()) {
320            self.conic_points_to(*p1, *p2, weight);
321        }
322        self.close();
323    }
324
325    /// Adds a circle contour.
326    ///
327    /// The contour is closed and has a clock-wise direction.
328    ///
329    /// Does nothing when:
330    /// - `radius` <= 0
331    /// - any value is not finite or really large
332    pub fn push_circle(&mut self, x: f32, y: f32, r: f32) {
333        if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) {
334            self.push_oval(r);
335        }
336    }
337
338    /// Adds a path.
339    pub fn push_path(&mut self, other: &Path) {
340        self.last_move_to_index = self.points.len();
341
342        self.verbs.extend_from_slice(&other.verbs);
343        self.points.extend_from_slice(&other.points);
344    }
345
346    pub(crate) fn push_path_builder(&mut self, other: &PathBuilder) {
347        if other.is_empty() {
348            return;
349        }
350
351        if self.last_move_to_index != 0 {
352            self.last_move_to_index = self.points.len() + other.last_move_to_index;
353        }
354
355        self.verbs.extend_from_slice(&other.verbs);
356        self.points.extend_from_slice(&other.points);
357    }
358
359    /// Appends, in a reverse order, the first contour of path ignoring path's last point.
360    pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) {
361        if other.is_empty() {
362            return;
363        }
364
365        debug_assert_eq!(other.verbs[0], PathVerb::Move);
366
367        let mut points_offset = other.points.len() - 1;
368        for verb in other.verbs.iter().rev() {
369            match verb {
370                PathVerb::Move => {
371                    // if the path has multiple contours, stop after reversing the last
372                    break;
373                }
374                PathVerb::Line => {
375                    // We're moving one point back manually, to prevent points_offset overflow.
376                    let pt = other.points[points_offset - 1];
377                    points_offset -= 1;
378                    self.line_to(pt.x, pt.y);
379                }
380                PathVerb::Quad => {
381                    let pt1 = other.points[points_offset - 1];
382                    let pt2 = other.points[points_offset - 2];
383                    points_offset -= 2;
384                    self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
385                }
386                PathVerb::Cubic => {
387                    let pt1 = other.points[points_offset - 1];
388                    let pt2 = other.points[points_offset - 2];
389                    let pt3 = other.points[points_offset - 3];
390                    points_offset -= 3;
391                    self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
392                }
393                PathVerb::Close => {}
394            }
395        }
396    }
397
398    /// Reset the builder.
399    ///
400    /// Memory is not deallocated.
401    pub fn clear(&mut self) {
402        self.verbs.clear();
403        self.points.clear();
404        self.last_move_to_index = 0;
405        self.move_to_required = true;
406    }
407
408    /// Finishes the builder and returns a `Path`.
409    ///
410    /// Returns `None` when `Path` is empty or has invalid bounds.
411    pub fn finish(self) -> Option<Path> {
412        if self.is_empty() {
413            return None;
414        }
415
416        // Just a move to? Bail.
417        if self.verbs.len() == 1 {
418            return None;
419        }
420
421        let bounds = Rect::from_points(&self.points)?;
422
423        Some(Path {
424            bounds,
425            verbs: self.verbs,
426            points: self.points,
427        })
428    }
429}