Skip to main content

clipper2_sys/
lib.rs

1//!
2//! # Examples cookbook / 示例合集
3//!
4//! ## `Clipper64`: union + lazy closed / 布尔并 + 惰性闭合解
5//!
6//! ```
7//! use clipper2_sys::{
8//!     ClipType, Clipper64, FillRule, Paths64, Point64,
9//! };
10//! # use clipper2_sys::Path64;
11//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
12//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
13//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
14//! # }
15//!
16//! let mut clip = Clipper64::new();
17//! clip.add_subject(&Paths64::new(vec![square_i(0, 0, 100)]));
18//! clip.add_clip(&Paths64::new(vec![square_i(50, 50, 100)]));
19//! let sol = clip.execute(ClipType::Union, FillRule::NonZero);
20//! let (closed, _open) = sol.into_lazy();
21//! assert!(!closed.is_empty());
22//! ```
23//!
24//! ## `Clipper64`: iterate closed paths / 逐条遍历闭合路径
25//!
26//! ```
27//! use clipper2_sys::{
28//!     ClipType, Clipper64, FillRule, Path64, Paths64, Point64,
29//! };
30//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
31//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
32//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
33//! # }
34//!
35//! let mut clip = Clipper64::new();
36//! clip.add_subject(&Paths64::new(vec![square_i(0, 0, 100)]));
37//! clip.add_clip(&Paths64::new(vec![square_i(50, 50, 100)]));
38//! let sol = clip.execute(ClipType::Union, FillRule::NonZero);
39//! let all: Paths64 = sol.iter_closed().chain(sol.iter_open()).collect();
40//! assert!(!all.is_empty());
41//! ```
42//!
43//! ## `Clipper64`: `execute_tree` + preorder on `PolyPath` / 树形解与前序遍历
44//!
45//! ```
46//! use clipper2_sys::{
47//!     ClipType, Clipper64, FillRule, Path64, Paths64, Point64,
48//! };
49//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
50//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
51//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
52//! # }
53//!
54//! let mut clip = Clipper64::new();
55//! clip.add_subject(&Paths64::new(vec![square_i(0, 0, 100)]));
56//! clip.add_clip(&Paths64::new(vec![square_i(50, 50, 100)]));
57//! let sol = clip.execute_tree(ClipType::Union, FillRule::NonZero);
58//! let (_open_lazy, preorder) = sol.into_open_and_poly_preorder();
59//! let n = preorder.count();
60//! assert!(n > 0);
61//! ```
62//!
63//! ## `ClipperD`: union / 双精度布尔并
64//!
65//! ```
66//! use clipper2_sys::{
67//!     ClipType, ClipperD, FillRule, PathD, PathsD, PointD,
68//! };
69//! # fn square_f(x0: f64, y0: f64, s: f64) -> PathD {
70//! #     PathD::new(vec![PointD::new(x0, y0), PointD::new(x0 + s, y0),
71//! #         PointD::new(x0 + s, y0 + s), PointD::new(x0, y0 + s)])
72//! # }
73//!
74//! let mut clip = ClipperD::new(4);
75//! clip.add_subject(&PathsD::new(vec![square_f(0.0, 0.0, 100.0)]));
76//! clip.add_clip(&PathsD::new(vec![square_f(50.0, 50.0, 100.0)]));
77//! let sol = clip.execute(ClipType::Union, FillRule::NonZero);
78//! let (closed, _open) = sol.into_lazy();
79//! assert!(!closed.is_empty());
80//! ```
81//!
82//! ## `ClipperOffset`: inflate a square / 方形外扩
83//!
84//! ```
85//! use clipper2_sys::{ClipperOffset, EndType, JoinType, Path64, Point64};
86//!
87//! let path = Path64::new(vec![
88//!     Point64::new(0, 0),
89//!     Point64::new(100, 0),
90//!     Point64::new(100, 100),
91//!     Point64::new(0, 100),
92//! ]);
93//! let mut co = ClipperOffset::new(2.0, 0.0, false, false);
94//! co.add_path(&path, JoinType::MiterJoin, EndType::PolygonEnd);
95//! let out = co.execute(10.0);
96//! assert!(!out.is_empty());
97//! ```
98//!
99//! ## `Path64`: area, point-in-polygon, translate, simplify / 面积、点包含、平移、简化
100//!
101//! ```
102//! use clipper2_sys::{Path64, Point64, PointInPolygonResult};
103//!
104//! let p = Path64::new(vec![
105//!     Point64::new(0, 0),
106//!     Point64::new(10, 0),
107//!     Point64::new(10, 10),
108//!     Point64::new(0, 10),
109//! ]);
110//! assert!((p.area().abs() - 100.0).abs() < 1e-3);
111//! assert!(matches!(
112//!     p.point_in_polygon(Point64::new(5, 5)),
113//!     PointInPolygonResult::Inside
114//! ));
115//! let t = p.translate(3, -2);
116//! assert_eq!(t.get_point(0).x, 3);
117//! let collinear = Path64::new(vec![
118//!     Point64::new(0, 0),
119//!     Point64::new(5, 0),
120//!     Point64::new(10, 0),
121//!     Point64::new(10, 10),
122//! ]);
123//! let simp = collinear.simplify(1.0, false);
124//! assert!(simp.into_first_path().len() <= collinear.len());
125//! ```
126//!
127//! ## `Paths64`: inflate (offset helper) / 多路径偏移
128//!
129//! ```
130//! use clipper2_sys::{EndType, JoinType, Path64, Paths64, Point64};
131//!
132//! let paths = Paths64::new(vec![Path64::new(vec![
133//!     Point64::new(0, 0),
134//!     Point64::new(100, 0),
135//!     Point64::new(100, 100),
136//!     Point64::new(0, 100),
137//! ])]);
138//! let grown = paths.inflate(10.0, JoinType::MiterJoin, EndType::PolygonEnd, 2.0);
139//! assert!(!grown.is_empty());
140//! ```
141//!
142//! ## `Path64` / `Paths64`: Minkowski sum / 闵可夫斯基和
143//!
144//! ```
145//! use clipper2_sys::{FillRule, Path64, Paths64, Point64};
146//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
147//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
148//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
149//! # }
150//!
151//! let a = square_i(0, 0, 50);
152//! let b = square_i(0, 0, 30);
153//! let ms = a.minkowski_sum(&b, true);
154//! assert!(!ms.is_empty());
155//!
156//! let many = Paths64::new(vec![square_i(0, 0, 40)]);
157//! let ms2 = many.minkowski_sum(&b, true, FillRule::NonZero.into());
158//! assert!(!ms2.is_empty());
159//! ```
160//!
161//! ## `PathD` ↔ `Path64`: simplify and convert / 双精度简化与转整型
162//!
163//! ```
164//! use clipper2_sys::{LazyPaths64, PathD, PointD};
165//!
166//! let p = PathD::new(vec![
167//!     PointD::new(0.0, 0.0),
168//!     PointD::new(5.0, 0.0),
169//!     PointD::new(10.0, 0.0),
170//!     PointD::new(10.0, 10.0),
171//! ]);
172//! let simplified = p.simplify(1.0, false);
173//! let _: LazyPaths64 = p.to_path64();
174//! assert!(simplified.into_first_path().len() <= p.len());
175//! ```
176//!
177//! ## `ClipSolution`: `to_closed` / 物化全部闭合多边形
178//!
179//! ```
180//! use clipper2_sys::{
181//!     ClipType, Clipper64, FillRule, Path64, Paths64, Point64,
182//! };
183//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
184//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
185//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
186//! # }
187//!
188//! let mut clip = Clipper64::new();
189//! clip.add_subject(&Paths64::new(vec![square_i(0, 0, 100)]));
190//! clip.add_clip(&Paths64::new(vec![square_i(50, 50, 100)]));
191//! let sol = clip.execute(ClipType::Union, FillRule::NonZero);
192//! let closed: Paths64 = sol.to_closed();
193//! assert!(!closed.is_empty());
194//! ```
195//!
196//! ## `ClipTreeSolution`: open-only shortcut / 只要开放解
197//!
198//! ```
199//! use clipper2_sys::{
200//!     ClipType, Clipper64, FillRule, Path64, Paths64, Point64,
201//! };
202//! # fn square_i(x0: i64, y0: i64, s: i64) -> Path64 {
203//! #     Path64::new(vec![Point64::new(x0, y0), Point64::new(x0 + s, y0),
204//! #         Point64::new(x0 + s, y0 + s), Point64::new(x0, y0 + s)])
205//! # }
206//!
207//! let mut clip = Clipper64::new();
208//! clip.add_subject(&Paths64::new(vec![square_i(0, 0, 100)]));
209//! clip.add_clip(&Paths64::new(vec![square_i(50, 50, 100)]));
210//! let sol = clip.execute_tree(ClipType::Union, FillRule::NonZero);
211//! let _open = sol.into_open_lazy();
212//! ```
213//!
214//! # Module map / 模块结构
215//!
216//! - `clipper64` (logic in `src/clipper64/`, types re-exported here) — integer paths and
217//!   [`Clipper64`]. / 整数路径与 [`Clipper64`],实现位于 `clipper64` 子目录。
218//! - `clipperd` — double paths and [`ClipperD`]. / 双精度路径与 [`ClipperD`]。
219//! - [`ClipperOffset`] — path offset (inflate/deflate). / 路径偏移。
220
221#[allow(dead_code)]
222mod cxx_bridge;
223
224#[macro_use]
225mod macros;
226
227mod paths_blob;
228pub use paths_blob::{PathsBlob64Iter, PathsBlobDIter};
229
230mod poly_path;
231pub use poly_path::{PolyCxxNode64, PolyCxxNodeD, PolyCxxPreorderIter64, PolyCxxPreorderIterD};
232
233mod offset;
234pub use offset::*;
235
236mod clipper64;
237pub use clipper64::*;
238
239mod clipperd;
240pub use clipperd::*;
241
242/// Polygon fill rule (non-zero, even-odd, …), maps to Clipper2 `FillRule`.
243///
244/// 多边形填充规则,对应 Clipper2 `FillRule`。
245#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
246pub enum FillRule {
247    /// Even-odd rule. / 偶奇规则。
248    EvenOdd,
249    /// Non-zero winding rule. / 非零环绕规则。
250    NonZero,
251    /// Positive winding only. / 仅正向环绕。
252    Positive,
253    /// Negative winding only. / 仅负向环绕。
254    Negative,
255}
256
257/// Boolean clip operation between subject and clip paths.
258///
259/// Subject 与 Clip 之间的裁剪运算类型。
260#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
261pub enum ClipType {
262    /// No clipping (placeholder). / 无裁剪(占位)。
263    None,
264    /// Intersection. / 交集。
265    Intersection,
266    /// Union. / 并集。
267    Union,
268    /// Difference (subject minus clip). / 差集(Subject 减 Clip)。
269    Difference,
270    /// Exclusive OR. / 异或。
271    Xor,
272}
273
274/// Vertex join style for offsets and offset-like operations.
275///
276/// 顶点连接样式,用于偏移等运算。
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
278pub enum JoinType {
279    /// Square corner join. / 方角连接。
280    SquareJoin,
281    /// Round join. / 圆角连接。
282    RoundJoin,
283    /// Miter join. / 尖角连接。
284    MiterJoin,
285}
286
287/// End cap / polygon closure mode for open paths in offsetting.
288///
289/// 开放路径在偏移时的端点与闭合模式。
290#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
291pub enum EndType {
292    /// Closed polygon. / 闭合多边形。
293    PolygonEnd,
294    /// Open path with joined ends. / 开放路径且端点相连。
295    JoinedEnd,
296    /// Square end cap. / 平头端点。
297    SquareEnd,
298    /// Round end cap. / 圆头端点。
299    RoundEnd,
300}
301
302/// Point-in-polygon classification result.
303///
304/// 点在多边形内外的判定结果。
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
306pub enum PointInPolygonResult {
307    /// Point lies on an edge. / 点在边上。
308    On,
309    /// Point strictly inside. / 点在内部。
310    Inside,
311    /// Point strictly outside. / 点在外部。
312    Outside,
313}
314
315/// Maps our [`ClipType`] to the cxx bridge enum. / [`ClipType`] → cxx 枚举。
316impl From<ClipType> for crate::cxx_bridge::clipper2_sys_cxx::ClipperClipType {
317    fn from(value: ClipType) -> Self {
318        use crate::cxx_bridge::clipper2_sys_cxx::ClipperClipType;
319        match value {
320            ClipType::None => ClipperClipType::NoClip,
321            ClipType::Intersection => ClipperClipType::Intersection,
322            ClipType::Union => ClipperClipType::Union,
323            ClipType::Difference => ClipperClipType::Difference,
324            ClipType::Xor => ClipperClipType::Xor,
325        }
326    }
327}
328
329/// Maps our [`FillRule`] to the cxx bridge enum. / [`FillRule`] → cxx。
330impl From<FillRule> for crate::cxx_bridge::clipper2_sys_cxx::ClipperFillRule {
331    fn from(value: FillRule) -> Self {
332        use crate::cxx_bridge::clipper2_sys_cxx::ClipperFillRule;
333        match value {
334            FillRule::EvenOdd => ClipperFillRule::EvenOdd,
335            FillRule::NonZero => ClipperFillRule::NonZero,
336            FillRule::Positive => ClipperFillRule::Positive,
337            FillRule::Negative => ClipperFillRule::Negative,
338        }
339    }
340}
341
342/// Maps our [`JoinType`] to the cxx bridge enum. / [`JoinType`] → cxx。
343impl From<JoinType> for crate::cxx_bridge::clipper2_sys_cxx::ClipperJoinType {
344    fn from(value: JoinType) -> Self {
345        use crate::cxx_bridge::clipper2_sys_cxx::ClipperJoinType;
346        match value {
347            JoinType::SquareJoin => ClipperJoinType::Square,
348            JoinType::RoundJoin => ClipperJoinType::Round,
349            JoinType::MiterJoin => ClipperJoinType::Miter,
350        }
351    }
352}
353
354/// Maps our [`EndType`] to the cxx bridge enum. / [`EndType`] → cxx。
355impl From<EndType> for crate::cxx_bridge::clipper2_sys_cxx::ClipperEndType {
356    fn from(value: EndType) -> Self {
357        use crate::cxx_bridge::clipper2_sys_cxx::ClipperEndType;
358        match value {
359            EndType::PolygonEnd => ClipperEndType::Polygon,
360            EndType::JoinedEnd => ClipperEndType::Joined,
361            EndType::SquareEnd => ClipperEndType::Square,
362            EndType::RoundEnd => ClipperEndType::Round,
363        }
364    }
365}
366
367/// Maps cxx point-in-polygon result to [`PointInPolygonResult`]. / cxx 判定结果 → [`PointInPolygonResult`]。
368impl From<crate::cxx_bridge::clipper2_sys_cxx::ClipperPointInPolygonResult> for PointInPolygonResult {
369    fn from(value: crate::cxx_bridge::clipper2_sys_cxx::ClipperPointInPolygonResult) -> Self {
370        use crate::cxx_bridge::clipper2_sys_cxx::ClipperPointInPolygonResult;
371        match value {
372            ClipperPointInPolygonResult::IsOn => PointInPolygonResult::On,
373            ClipperPointInPolygonResult::IsInside => PointInPolygonResult::Inside,
374            ClipperPointInPolygonResult::IsOutside => PointInPolygonResult::Outside,
375            _ => PointInPolygonResult::Outside,
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::cxx_bridge::clipper2_sys_cxx::{
384        ClipperClipType, ClipperFillRule, ClipperPointInPolygonResult,
385    };
386
387    #[test]
388    fn fill_rule_into_clipper_matches_even_odd() {
389        let v: ClipperFillRule = FillRule::EvenOdd.into();
390        assert_eq!(v, ClipperFillRule::EvenOdd);
391    }
392
393    #[test]
394    fn clip_type_into_clipper_matches_union() {
395        let v: ClipperClipType = ClipType::Union.into();
396        assert_eq!(v, ClipperClipType::Union);
397    }
398
399    #[test]
400    fn point_in_polygon_result_from_clipper() {
401        let r: PointInPolygonResult = ClipperPointInPolygonResult::IsInside.into();
402        assert!(matches!(r, PointInPolygonResult::Inside));
403    }
404}