1use std::fmt;
2
3use crate::cxx_bridge::clipper2_sys_cxx;
4use crate::paths_blob::{paths64_to_blob, PathsBlob64Iter};
5use crate::poly_path::PolyCxxPreorderIter64;
6use crate::{ClipType, FillRule, Paths64};
7
8use super::LazyPaths64;
9
10#[derive(Clone, Debug)]
16pub struct ClipSolution64 {
17 closed: clipper2_sys_cxx::PathsBlob64,
18 open: clipper2_sys_cxx::PathsBlob64,
19}
20
21impl ClipSolution64 {
22 #[inline]
24 pub fn closed_is_empty(&self) -> bool {
25 self.closed.path_starts.len() < 2
26 }
27
28 #[inline]
30 pub fn open_is_empty(&self) -> bool {
31 self.open.path_starts.len() < 2
32 }
33
34 #[inline]
36 pub fn iter_closed(&self) -> PathsBlob64Iter<'_> {
37 PathsBlob64Iter::new(&self.closed)
38 }
39
40 #[inline]
42 pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
43 PathsBlob64Iter::new(&self.open)
44 }
45
46 pub fn to_closed(&self) -> Paths64 {
48 self.iter_closed().collect()
49 }
50
51 pub fn to_open(&self) -> Paths64 {
53 self.iter_open().collect()
54 }
55
56 pub fn into_lazy(self) -> (LazyPaths64, LazyPaths64) {
58 (
59 LazyPaths64::from_blob(self.closed),
60 LazyPaths64::from_blob(self.open),
61 )
62 }
63}
64
65pub struct ClipTreeSolution64 {
72 poly_root: Option<usize>,
73 open: clipper2_sys_cxx::PathsBlob64,
74}
75
76impl fmt::Debug for ClipTreeSolution64 {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("ClipTreeSolution64")
79 .field("has_poly_root", &self.poly_root.map(|r| r != 0))
80 .field("open_paths", &(self.open.path_starts.len().saturating_sub(1)))
81 .finish()
82 }
83}
84
85impl Drop for ClipTreeSolution64 {
86 fn drop(&mut self) {
87 if let Some(r) = self.poly_root.take() {
88 if r != 0 {
89 clipper2_sys_cxx::cxx_poly64_delete(r);
90 }
91 }
92 }
93}
94
95impl ClipTreeSolution64 {
96 #[inline]
98 pub fn has_poly_tree(&self) -> bool {
99 matches!(self.poly_root, Some(r) if r != 0)
100 }
101
102 #[inline]
104 pub fn open_is_empty(&self) -> bool {
105 self.open.path_starts.len() < 2
106 }
107
108 #[inline]
110 pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
111 PathsBlob64Iter::new(&self.open)
112 }
113
114 pub fn to_open(&self) -> Paths64 {
116 self.iter_open().collect()
117 }
118
119 pub fn into_open_lazy(mut self) -> LazyPaths64 {
121 if let Some(r) = self.poly_root.take() {
122 if r != 0 {
123 clipper2_sys_cxx::cxx_poly64_delete(r);
124 }
125 }
126 LazyPaths64::from_blob(std::mem::take(&mut self.open))
127 }
128
129 pub fn into_open_and_poly_preorder(mut self) -> (LazyPaths64, PolyCxxPreorderIter64) {
133 let root = self.poly_root.take().unwrap_or(0);
134 let open = std::mem::take(&mut self.open);
135 let iter = PolyCxxPreorderIter64::new(root);
136 (LazyPaths64::from_blob(open), iter)
137 }
138}
139
140pub struct Clipper64 {
144 inner: cxx::UniquePtr<clipper2_sys_cxx::Clipper64Box>,
145}
146
147impl Clipper64 {
148 pub fn new() -> Self {
150 Self {
151 inner: clipper2_sys_cxx::cxx_clipper64_new(),
152 }
153 }
154
155 pub fn set_preserve_collinear(&mut self, value: bool) {
157 clipper2_sys_cxx::cxx_clipper64_set_preserve_collinear(self.inner.pin_mut(), value);
158 }
159
160 pub fn get_preserve_collinear(&self) -> bool {
162 clipper2_sys_cxx::cxx_clipper64_get_preserve_collinear(&self.inner)
163 }
164
165 pub fn set_reverse_solution(&mut self, value: bool) {
167 clipper2_sys_cxx::cxx_clipper64_set_reverse_solution(self.inner.pin_mut(), value);
168 }
169
170 pub fn get_reverse_solution(&self) -> bool {
172 clipper2_sys_cxx::cxx_clipper64_get_reverse_solution(&self.inner)
173 }
174
175 pub fn clear(&mut self) {
177 clipper2_sys_cxx::cxx_clipper64_clear(self.inner.pin_mut());
178 }
179
180 pub fn add_open_subject(&mut self, open_subject: &Paths64) {
182 clipper2_sys_cxx::cxx_clipper64_add_open_subject(
183 self.inner.pin_mut(),
184 &paths64_to_blob(open_subject),
185 );
186 }
187
188 pub fn add_subject(&mut self, subject: &Paths64) {
190 clipper2_sys_cxx::cxx_clipper64_add_subject(self.inner.pin_mut(), &paths64_to_blob(subject));
191 }
192
193 pub fn add_clip(&mut self, clip: &Paths64) {
195 clipper2_sys_cxx::cxx_clipper64_add_clip(self.inner.pin_mut(), &paths64_to_blob(clip));
196 }
197
198 pub fn execute(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipSolution64 {
202 let out = clipper2_sys_cxx::cxx_clipper64_execute(
203 self.inner.pin_mut(),
204 clip_type.into(),
205 fill_rule.into(),
206 );
207 ClipSolution64 {
208 closed: out.closed,
209 open: out.open,
210 }
211 }
212
213 pub fn execute_tree(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipTreeSolution64 {
217 let te = clipper2_sys_cxx::cxx_clipper64_execute_tree(
218 self.inner.pin_mut(),
219 clip_type.into(),
220 fill_rule.into(),
221 );
222 ClipTreeSolution64 {
223 poly_root: if te.root == 0 { None } else { Some(te.root) },
224 open: te.open,
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::{ClipType, Clipper64, FillRule, Path64, Paths64, Point64};
232
233 fn square(x0: i64, y0: i64, s: i64) -> Path64 {
234 Path64::new(vec![
235 Point64::new(x0, y0),
236 Point64::new(x0 + s, y0),
237 Point64::new(x0 + s, y0 + s),
238 Point64::new(x0, y0 + s),
239 ])
240 }
241
242 #[test]
243 fn union_overlapping_squares_non_empty() {
244 let mut c = Clipper64::new();
245 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
246 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
247 let sol = c.execute(ClipType::Union, FillRule::NonZero);
248 assert!(!sol.closed_is_empty() || !sol.open_is_empty());
249 }
250
251 #[test]
252 fn execute_iter_closed_collect_matches_into_lazy() {
253 let mut c = Clipper64::new();
254 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
255 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
256 let sol = c.execute(ClipType::Union, FillRule::NonZero);
257 let a: Paths64 = sol.iter_closed().chain(sol.iter_open()).collect();
258 let (closed, open) = sol.into_lazy();
259 let mut b = closed.into_paths();
260 b.0.extend(open.into_paths().0);
261 assert_eq!(a.len(), b.len());
262 }
263
264 #[test]
265 fn execute_tree_poly_preorder_count_stable() {
266 let mut c = Clipper64::new();
267 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
268 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
269 let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
270 let (_, iter_a) = sol.into_open_and_poly_preorder();
271 let n_a = iter_a.count();
272
273 let mut c2 = Clipper64::new();
274 c2.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
275 c2.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
276 let sol2 = c2.execute_tree(ClipType::Union, FillRule::NonZero);
277 let (_, iter_b) = sol2.into_open_and_poly_preorder();
278 let n_b = iter_b.count();
279
280 assert_eq!(n_a, n_b);
281 }
282
283 #[test]
284 fn execute_tree_union_has_poly_or_open() {
285 let mut c = Clipper64::new();
286 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
287 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
288 let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
289 assert!(sol.has_poly_tree() || !sol.open_is_empty());
290 }
291
292 #[test]
293 fn intersection_overlapping_squares_non_empty() {
294 let mut c = Clipper64::new();
295 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
296 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
297 let sol = c.execute(ClipType::Intersection, FillRule::NonZero);
298 assert!(!sol.closed_is_empty() || !sol.open_is_empty());
299 }
300
301 #[test]
302 fn difference_frame_non_empty() {
303 let mut c = Clipper64::new();
304 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
305 c.add_clip(&Paths64::new(vec![square(25, 25, 50)]));
306 let sol = c.execute(ClipType::Difference, FillRule::NonZero);
307 assert!(!sol.closed_is_empty() || !sol.open_is_empty());
308 }
309
310 #[test]
311 fn xor_overlapping_squares_non_empty() {
312 let mut c = Clipper64::new();
313 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
314 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
315 let sol = c.execute(ClipType::Xor, FillRule::NonZero);
316 assert!(!sol.closed_is_empty() || !sol.open_is_empty());
317 }
318
319 #[test]
320 fn union_even_odd_fill_non_empty() {
321 let mut c = Clipper64::new();
322 c.add_subject(&Paths64::new(vec![square(0, 0, 100)]));
323 c.add_clip(&Paths64::new(vec![square(50, 50, 100)]));
324 let sol = c.execute(ClipType::Union, FillRule::EvenOdd);
325 assert!(!sol.closed_is_empty() || !sol.open_is_empty());
326 }
327
328 #[test]
329 fn open_subject_line_intersection_with_clip_non_empty() {
330 let mut c = Clipper64::new();
331 let line = Path64::new(vec![
332 Point64::new(-10, 50),
333 Point64::new(110, 50),
334 ]);
335 c.add_open_subject(&Paths64::new(vec![line]));
336 c.add_clip(&Paths64::new(vec![square(0, 0, 100)]));
337 let sol = c.execute(ClipType::Intersection, FillRule::NonZero);
338 assert!(!sol.open_is_empty() || !sol.closed_is_empty());
339 }
340}