path_offset/path/subpath.rs
1//! Provides an iterator to decompose a `Path` into its individual subpaths.
2//!
3//! A `Path` can contain multiple disconnected shapes (e.g., the letter 'i' has two).
4//! This module provides the [`SubpathIter`] iterator, which is created via the
5//! [`IntoIterator`] implementation for `&Path`. This allows you to easily loop
6//! over each continuous segment of a larger path.
7//!
8//! # Example
9//!
10//! ```no_run
11//! use path_offset::path::Path;
12//! use lyon::path::Path as LyonPath;
13//!
14//! // Create a path with two separate subpaths.
15//! let mut builder = LyonPath::builder();
16//! builder.begin(lyon::math::point(0.0, 0.0));
17//! builder.line_to(lyon::math::point(10.0, 0.0));
18//! builder.end(false); // First subpath
19//! builder.begin(lyon::math::point(20.0, 0.0));
20//! builder.line_to(lyon::math::point(30.0, 0.0));
21//! builder.end(false); // Second subpath
22//! let lyon_path = builder.build();
23//!
24//! let path = Path::from(lyon_path);
25//!
26//! // Iterate over the subpaths.
27//! let mut subpath_count = 0;
28//! for subpath in &path {
29//! subpath_count += 1;
30//! // Each `subpath` is a `path_offset::path::Path` containing one continuous shape.
31//! }
32//!
33//! assert_eq!(subpath_count, 2);
34//! ```
35
36use lyon::path::{Event, Iter as PathIter};
37
38/// An iterator that decomposes a path containing multiple shapes into individual subpaths.
39///
40/// This struct and its `Iterator` implementation encapsulate the state management
41/// required to extract independent subpaths (from a `Begin` to an `End` event)
42/// from a continuous stream of path events.
43///
44/// It is typically not used directly, but rather through the `for` loop syntax on a `&Path`.
45pub struct SubpathIter<'a> {
46 /// Holds an iterator over the underlying `lyon` path's event stream.
47 iter: PathIter<'a>,
48}
49
50impl<'a> Iterator for SubpathIter<'a> {
51 // Each iteration yields a complete `Path` object representing one subpath.
52 type Item = super::Path;
53
54 /// Implements the core logic of the iterator.
55 ///
56 /// Each call attempts to build and return the next complete subpath from the
57 /// underlying event stream.
58 fn next(&mut self) -> Option<Self::Item> {
59 // 1. Find the next `Begin` event to start a new subpath builder.
60 let mut builder;
61 if let Some(event) = self.iter.find(|e| matches!(e, Event::Begin { .. })) {
62 if let Event::Begin { at } = event {
63 // Found a start point, initialize the builder.
64 let mut b = lyon::path::Path::builder();
65 b.begin(at);
66 builder = b;
67 } else {
68 // This is theoretically unreachable because `find` ensures it's a Begin event.
69 return None;
70 }
71 } else {
72 // No more `Begin` events are found in the stream, so iteration is complete.
73 return None;
74 }
75
76 // 2. With an active builder, consume events until the corresponding `End` event is found.
77 for event in &mut self.iter {
78 match event {
79 Event::Line { to, .. } => {
80 builder.line_to(to);
81 }
82 Event::Quadratic { ctrl, to, .. } => {
83 builder.quadratic_bezier_to(ctrl, to);
84 }
85 Event::Cubic {
86 ctrl1, ctrl2, to, ..
87 } => {
88 builder.cubic_bezier_to(ctrl1, ctrl2, to);
89 }
90 Event::End { close, .. } => {
91 // An `End` event signifies a complete subpath.
92 if close {
93 builder.close();
94 }
95 // Build the lyon::path::Path, wrap it in our own Path type, and return it.
96 // This concludes the current call to next().
97 return Some(super::Path {
98 inner: builder.build(),
99 });
100 }
101 Event::Begin { .. } => {
102 // If another `Begin` is encountered before an `End`, the previous
103 // subpath was not properly terminated. In an iterator context,
104 // the simplest approach is to stop here and let the next call to `next()`
105 // process this new `Begin` event. This means the unclosed path is discarded.
106 break;
107 }
108 }
109 }
110
111 // If the loop finishes without returning, it means the iterator was exhausted
112 // but the last subpath did not have a corresponding `End` event.
113 // This incomplete subpath is ignored, and we return None.
114 None
115 }
116}
117
118/// Implements the `IntoIterator` trait for references to our `Path` type.
119///
120/// This is what allows a `&Path` to be used directly in a `for` loop,
121/// transparently creating a [`SubpathIter`] to drive the iteration.
122impl<'a> IntoIterator for &'a super::Path {
123 type Item = super::Path;
124 type IntoIter = SubpathIter<'a>;
125
126 /// Defines how to create a [`SubpathIter`] from a `&Path`.
127 fn into_iter(self) -> Self::IntoIter {
128 SubpathIter {
129 iter: self.inner.iter(),
130 }
131 }
132}