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}