Skip to main content

clipper2_sys/clipperd/
clipper.rs

1use std::fmt;
2
3use crate::cxx_bridge::clipper2_sys_cxx;
4use crate::paths_blob::{pathsd_to_blob, PathsBlobDIter};
5use crate::poly_path::PolyCxxPreorderIterD;
6use crate::{ClipType, FillRule, PathsD};
7
8use super::LazyPathsD;
9
10/// Result of [`ClipperD::execute`]. / [`ClipperD::execute`] 的返回值。
11#[derive(Clone, Debug)]
12pub struct ClipSolutionD {
13    closed: clipper2_sys_cxx::PathsBlobD,
14    open: clipper2_sys_cxx::PathsBlobD,
15}
16
17impl ClipSolutionD {
18    /// No closed solution. / 无闭合解。
19    #[inline]
20    pub fn closed_is_empty(&self) -> bool {
21        self.closed.path_starts.len() < 2
22    }
23
24    /// No open solution. / 无开放解。
25    #[inline]
26    pub fn open_is_empty(&self) -> bool {
27        self.open.path_starts.len() < 2
28    }
29
30    /// Iterator over closed paths. / 闭合路径迭代器。
31    #[inline]
32    pub fn iter_closed(&self) -> PathsBlobDIter<'_> {
33        PathsBlobDIter::new(&self.closed)
34    }
35
36    /// Iterator over open paths. / 开放路径迭代器。
37    #[inline]
38    pub fn iter_open(&self) -> PathsBlobDIter<'_> {
39        PathsBlobDIter::new(&self.open)
40    }
41
42    /// Collect closed paths. / 收集闭合路径。
43    pub fn to_closed(&self) -> PathsD {
44        self.iter_closed().collect()
45    }
46
47    /// Collect open paths. / 收集开放路径。
48    pub fn to_open(&self) -> PathsD {
49        self.iter_open().collect()
50    }
51
52    /// Split into lazy holders. / 拆分为惰性包装。
53    pub fn into_lazy(self) -> (LazyPathsD, LazyPathsD) {
54        (
55            LazyPathsD::from_blob(self.closed),
56            LazyPathsD::from_blob(self.open),
57        )
58    }
59}
60
61/// Result of [`ClipperD::execute_tree`]. / [`ClipperD::execute_tree`] 的返回值。
62pub struct ClipTreeSolutionD {
63    poly_root: Option<usize>,
64    open: clipper2_sys_cxx::PathsBlobD,
65}
66
67impl fmt::Debug for ClipTreeSolutionD {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.debug_struct("ClipTreeSolutionD")
70            .field("has_poly_root", &self.poly_root.map(|r| r != 0))
71            .field("open_paths", &(self.open.path_starts.len().saturating_sub(1)))
72            .finish()
73    }
74}
75
76impl Drop for ClipTreeSolutionD {
77    fn drop(&mut self) {
78        if let Some(r) = self.poly_root.take() {
79            if r != 0 {
80                clipper2_sys_cxx::cxx_polyd_delete(r);
81            }
82        }
83    }
84}
85
86impl ClipTreeSolutionD {
87    /// `true` if a polygon-tree root is present. / 是否存在多边形树根。
88    #[inline]
89    pub fn has_poly_tree(&self) -> bool {
90        matches!(self.poly_root, Some(r) if r != 0)
91    }
92
93    /// No open paths. / 无开放路径。
94    #[inline]
95    pub fn open_is_empty(&self) -> bool {
96        self.open.path_starts.len() < 2
97    }
98
99    /// Open-path iterator. / 开放路径迭代器。
100    #[inline]
101    pub fn iter_open(&self) -> PathsBlobDIter<'_> {
102        PathsBlobDIter::new(&self.open)
103    }
104
105    /// Collect open paths. / 收集开放路径。
106    pub fn to_open(&self) -> PathsD {
107        self.iter_open().collect()
108    }
109
110    /// Keep only open solution; free polygon tree. / 只要开放解并释放树。
111    pub fn into_open_lazy(mut self) -> LazyPathsD {
112        if let Some(r) = self.poly_root.take() {
113            if r != 0 {
114                clipper2_sys_cxx::cxx_polyd_delete(r);
115            }
116        }
117        LazyPathsD::from_blob(std::mem::take(&mut self.open))
118    }
119
120    /// Lazy open paths + preorder over C++ `PolyPathD`. / 开放路径 + `PolyPathD` 前序迭代。
121    pub fn into_open_and_poly_preorder(mut self) -> (LazyPathsD, PolyCxxPreorderIterD) {
122        let root = self.poly_root.take().unwrap_or(0);
123        let open = std::mem::take(&mut self.open);
124        let iter = PolyCxxPreorderIterD::new(root);
125        (LazyPathsD::from_blob(open), iter)
126    }
127}
128
129/// Double-precision Clipper2 engine (`ClipperD`); `precision` controls decimal places.
130///
131/// 双精度布尔裁剪引擎;`precision` 为小数精度位数。
132pub struct ClipperD {
133    inner: cxx::UniquePtr<clipper2_sys_cxx::ClipperDBox>,
134}
135
136impl ClipperD {
137    /// New clipper with decimal precision (e.g. `4`). / 指定小数精度构造。
138    pub fn new(precision: i32) -> Self {
139        Self {
140            inner: clipper2_sys_cxx::cxx_clipperd_new(precision),
141        }
142    }
143
144    /// Sets `PreserveCollinear`. / 设置保留共线点。
145    pub fn set_preserve_collinear(&mut self, value: bool) {
146        clipper2_sys_cxx::cxx_clipperd_set_preserve_collinear(self.inner.pin_mut(), value);
147    }
148
149    /// Gets `PreserveCollinear`. / 读取保留共线点。
150    pub fn get_preserve_collinear(&self) -> bool {
151        clipper2_sys_cxx::cxx_clipperd_get_preserve_collinear(&self.inner)
152    }
153
154    /// Sets `ReverseSolution`. / 设置反转解。
155    pub fn set_reverse_solution(&mut self, value: bool) {
156        clipper2_sys_cxx::cxx_clipperd_set_reverse_solution(self.inner.pin_mut(), value);
157    }
158
159    /// Gets `ReverseSolution`. / 读取反转解。
160    pub fn get_reverse_solution(&self) -> bool {
161        clipper2_sys_cxx::cxx_clipperd_get_reverse_solution(&self.inner)
162    }
163
164    /// Clears all paths. / 清空路径。
165    pub fn clear(&mut self) {
166        clipper2_sys_cxx::cxx_clipperd_clear(self.inner.pin_mut());
167    }
168
169    /// Adds open subjects. / 添加开放 subject。
170    pub fn add_open_subject(&mut self, open_subject: &PathsD) {
171        clipper2_sys_cxx::cxx_clipperd_add_open_subject(
172            self.inner.pin_mut(),
173            &pathsd_to_blob(open_subject),
174        );
175    }
176
177    /// Adds closed subjects. / 添加闭合 subject。
178    pub fn add_subject(&mut self, subject: &PathsD) {
179        clipper2_sys_cxx::cxx_clipperd_add_subject(self.inner.pin_mut(), &pathsd_to_blob(subject));
180    }
181
182    /// Adds clip paths. / 添加 clip。
183    pub fn add_clip(&mut self, clip: &PathsD) {
184        clipper2_sys_cxx::cxx_clipperd_add_clip(self.inner.pin_mut(), &pathsd_to_blob(clip));
185    }
186
187    /// Planar clip; same idea as [`crate::Clipper64::execute`]. / 平面裁剪,语义同 [`crate::Clipper64::execute`]。
188    pub fn execute(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipSolutionD {
189        let out = clipper2_sys_cxx::cxx_clipperd_execute(
190            self.inner.pin_mut(),
191            clip_type.into(),
192            fill_rule.into(),
193        );
194        ClipSolutionD {
195            closed: out.closed,
196            open: out.open,
197        }
198    }
199
200    /// Hierarchical clip. / 带层次结构的裁剪。
201    pub fn execute_tree(&mut self, clip_type: ClipType, fill_rule: FillRule) -> ClipTreeSolutionD {
202        let te = clipper2_sys_cxx::cxx_clipperd_execute_tree(
203            self.inner.pin_mut(),
204            clip_type.into(),
205            fill_rule.into(),
206        );
207        ClipTreeSolutionD {
208            poly_root: if te.root == 0 { None } else { Some(te.root) },
209            open: te.open,
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use crate::{ClipType, ClipperD, FillRule, PathD, PathsD, PointD};
217
218    fn square(x0: f64, y0: f64, s: f64) -> PathD {
219        PathD::new(vec![
220            PointD::new(x0, y0),
221            PointD::new(x0 + s, y0),
222            PointD::new(x0 + s, y0 + s),
223            PointD::new(x0, y0 + s),
224        ])
225    }
226
227    #[test]
228    fn union_overlapping_squares_non_empty() {
229        let mut c = ClipperD::new(4);
230        c.add_subject(&PathsD::new(vec![square(0.0, 0.0, 100.0)]));
231        c.add_clip(&PathsD::new(vec![square(50.0, 50.0, 100.0)]));
232        let sol = c.execute(ClipType::Union, FillRule::NonZero);
233        assert!(!sol.closed_is_empty() || !sol.open_is_empty());
234    }
235
236    #[test]
237    fn execute_tree_poly_preorder_d_count_stable() {
238        let mut c = ClipperD::new(4);
239        c.add_subject(&PathsD::new(vec![square(0.0, 0.0, 100.0)]));
240        c.add_clip(&PathsD::new(vec![square(50.0, 50.0, 100.0)]));
241        let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
242        let (_, iter_a) = sol.into_open_and_poly_preorder();
243        let n_a = iter_a.count();
244
245        let mut c2 = ClipperD::new(4);
246        c2.add_subject(&PathsD::new(vec![square(0.0, 0.0, 100.0)]));
247        c2.add_clip(&PathsD::new(vec![square(50.0, 50.0, 100.0)]));
248        let sol2 = c2.execute_tree(ClipType::Union, FillRule::NonZero);
249        let (_, iter_b) = sol2.into_open_and_poly_preorder();
250        let n_b = iter_b.count();
251
252        assert_eq!(n_a, n_b);
253    }
254
255    #[test]
256    fn execute_tree_union_d_has_poly_or_open() {
257        let mut c = ClipperD::new(4);
258        c.add_subject(&PathsD::new(vec![square(0.0, 0.0, 100.0)]));
259        c.add_clip(&PathsD::new(vec![square(50.0, 50.0, 100.0)]));
260        let sol = c.execute_tree(ClipType::Union, FillRule::NonZero);
261        assert!(sol.has_poly_tree() || !sol.open_is_empty());
262    }
263}