Skip to main content

clipper2_sys/clipper64/
clipper.rs

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/// Result of [`Clipper64::execute`]: closed and open solutions as C++ blobs.
11///
12/// 平面布尔 [`Clipper64::execute`] 的结果:闭合解与开放解,内部为 C++ 扁平淡点。
13/// Iterate or call [`Self::into_lazy`] to build `Path64` values lazily.
14/// 可迭代或 [`into_lazy`](Self::into_lazy) 惰性得到 `Path64`。
15#[derive(Clone, Debug)]
16pub struct ClipSolution64 {
17    closed: clipper2_sys_cxx::PathsBlob64,
18    open: clipper2_sys_cxx::PathsBlob64,
19}
20
21impl ClipSolution64 {
22    /// `true` if there is no closed output. / 无闭合输出。
23    #[inline]
24    pub fn closed_is_empty(&self) -> bool {
25        self.closed.path_starts.len() < 2
26    }
27
28    /// `true` if there is no open output. / 无开放输出。
29    #[inline]
30    pub fn open_is_empty(&self) -> bool {
31        self.open.path_starts.len() < 2
32    }
33
34    /// Iterator over closed paths. / 闭合路径迭代器。
35    #[inline]
36    pub fn iter_closed(&self) -> PathsBlob64Iter<'_> {
37        PathsBlob64Iter::new(&self.closed)
38    }
39
40    /// Iterator over open paths. / 开放路径迭代器。
41    #[inline]
42    pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
43        PathsBlob64Iter::new(&self.open)
44    }
45
46    /// Materializes all closed paths. / 物化全部闭合路径。
47    pub fn to_closed(&self) -> Paths64 {
48        self.iter_closed().collect()
49    }
50
51    /// Materializes all open paths. / 物化全部开放路径。
52    pub fn to_open(&self) -> Paths64 {
53        self.iter_open().collect()
54    }
55
56    /// Splits into two lazy wrappers (no extra copy of blobs). / 拆成两个惰性包装。
57    pub fn into_lazy(self) -> (LazyPaths64, LazyPaths64) {
58        (
59            LazyPaths64::from_blob(self.closed),
60            LazyPaths64::from_blob(self.open),
61        )
62    }
63}
64
65/// Result of [`Clipper64::execute_tree`]: optional `PolyPath64` root + open paths.
66///
67/// 树形 [`execute_tree`](Clipper64::execute_tree) 结果:可选的多边形树根指针与开放路径。
68/// The polygon tree must be consumed via [`Self::into_open_and_poly_preorder`] or dropped;
69/// use [`Self::into_open_lazy`] to discard the tree.
70/// 多边形树须通过前序迭代消费或析构;若只要开放解可用 [`into_open_lazy`](Self::into_open_lazy)。
71pub 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    /// `true` if a non-null polygon tree root exists. / 是否存在非空多边形树根。
97    #[inline]
98    pub fn has_poly_tree(&self) -> bool {
99        matches!(self.poly_root, Some(r) if r != 0)
100    }
101
102    /// No open paths when `true`. / 无开放路径。
103    #[inline]
104    pub fn open_is_empty(&self) -> bool {
105        self.open.path_starts.len() < 2
106    }
107
108    /// Open-path iterator. / 开放路径迭代器。
109    #[inline]
110    pub fn iter_open(&self) -> PathsBlob64Iter<'_> {
111        PathsBlob64Iter::new(&self.open)
112    }
113
114    /// Collects open paths. / 收集开放路径。
115    pub fn to_open(&self) -> Paths64 {
116        self.iter_open().collect()
117    }
118
119    /// Frees the polygon tree and returns lazy open paths only. / 释放树,仅返回开放路径惰性包装。
120    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    /// Lazy open paths + preorder iterator over the C++ `PolyPath64` tree (frees tree on drop).
130    ///
131    /// 开放路径惰性包装 + C++ `PolyPath64` 前序迭代器(迭代器 `Drop` 时释放整棵子树)。
132    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
140/// Integer-coordinate Clipper2 boolean engine (`Clipper64`).
141///
142/// Clipper2 整数布尔裁剪引擎。
143pub struct Clipper64 {
144    inner: cxx::UniquePtr<clipper2_sys_cxx::Clipper64Box>,
145}
146
147impl Clipper64 {
148    /// Creates an empty clipper. / 创建空裁剪器。
149    pub fn new() -> Self {
150        Self {
151            inner: clipper2_sys_cxx::cxx_clipper64_new(),
152        }
153    }
154
155    /// Sets `PreserveCollinear`. / 设置保留共线点。
156    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    /// Gets `PreserveCollinear`. / 读取保留共线点。
161    pub fn get_preserve_collinear(&self) -> bool {
162        clipper2_sys_cxx::cxx_clipper64_get_preserve_collinear(&self.inner)
163    }
164
165    /// Sets `ReverseSolution`. / 设置反转解方向。
166    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    /// Gets `ReverseSolution`. / 读取反转解。
171    pub fn get_reverse_solution(&self) -> bool {
172        clipper2_sys_cxx::cxx_clipper64_get_reverse_solution(&self.inner)
173    }
174
175    /// Clears all subjects and clips. / 清空所有 subject 与 clip。
176    pub fn clear(&mut self) {
177        clipper2_sys_cxx::cxx_clipper64_clear(self.inner.pin_mut());
178    }
179
180    /// Adds open subject paths. / 添加开放 subject。
181    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    /// Adds closed subject paths. / 添加闭合 subject。
189    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    /// Adds clip paths. / 添加裁剪区域。
194    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    /// Runs planar clipping, returning closed/open paths.
199    ///
200    /// 执行平面裁剪,返回闭合/开放路径解。
201    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    /// Runs clipping with hierarchy (`PolyTree`) on the closed side.
214    ///
215    /// 执行裁剪并在闭合侧保留层次(`PolyTree`),开放侧仍为扁平淡点。
216    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}