Skip to main content

clipper2_sys/
paths_blob.rs

1//! Conversions between flat `PathsBlob*` and Rust [`crate::Path64`]/[`crate::PathD`], plus iterators.
2//!
3//! `PathsBlob64` / `PathsBlobD` 与 Rust 路径类型互转及惰性迭代器。
4
5use crate::cxx_bridge::clipper2_sys_cxx::{P64, PD, PathsBlob64, PathsBlobD};
6use crate::{Path64, PathD, Paths64, PathsD};
7
8/// Default decimal precision when no `ClipperD` instance sets it (`PathD` → `Path64`).
9///
10/// 无 `ClipperD` 实例时,`PathD` ↔ `Path64` 等转换使用的默认小数精度。
11pub(crate) const DEFAULT_PATHD_PRECISION: i32 = 4;
12
13/// Converts a flat point slice to [`Path64`] (memcpy). / 扁平淡点切片 → `Path64`。
14#[inline]
15pub(crate) fn path64_from_p64_slice(points: &[P64]) -> Path64 {
16    Path64::from_vec(points.to_vec())
17}
18
19/// Converts a flat point slice to [`PathD`]. / 扁平淡点切片 → `PathD`。
20#[inline]
21pub(crate) fn pathd_from_pd_slice(points: &[PD]) -> PathD {
22    PathD::from_vec(points.to_vec())
23}
24
25define_paths_blob_iter! {
26    /// Iterator over each path inside a `PathsBlob64` (allocates one [`Path64`] per step).
27    ///
28    /// 遍历 `PathsBlob64` 中每条路径(每步分配一条 [`Path64`])。
29    pub PathsBlob64Iter,
30    point = P64,
31    path = Path64,
32    blob = PathsBlob64,
33    conv_slice = path64_from_p64_slice,
34}
35
36define_paths_blob_iter! {
37    /// Iterator over paths in a `PathsBlobD`. / 遍历 `PathsBlobD` 中的路径。
38    pub PathsBlobDIter,
39    point = PD,
40    path = PathD,
41    blob = PathsBlobD,
42    conv_slice = pathd_from_pd_slice,
43}
44
45/// Single-path `Path64` → cxx blob (`path_starts = [0, n]`). / 单路径 → cxx blob。
46#[inline]
47pub(crate) fn path64_to_blob(p: &Path64) -> PathsBlob64 {
48    let n = p.0.len();
49    PathsBlob64 {
50        points: p.0.clone(),
51        path_starts: vec![0, n],
52    }
53}
54
55/// Many `Path64` → one blob. / 多路径扁平化。
56pub(crate) fn paths64_to_blob(p: &Paths64) -> PathsBlob64 {
57    let n_paths = p.0.len();
58    let total_pts: usize = p.0.iter().map(|path| path.0.len()).sum();
59    let mut points = Vec::with_capacity(total_pts);
60    let mut path_starts = Vec::with_capacity(n_paths + 1);
61    path_starts.push(0);
62    for path in &p.0 {
63        points.extend_from_slice(&path.0);
64        path_starts.push(points.len());
65    }
66    PathsBlob64 { points, path_starts }
67}
68
69/// First polygon in `PolyPath` blob → one [`Path64`]. / 多边形树单轮廓 blob → `Path64`。
70pub(crate) fn path64_from_single_paths_blob(b: PathsBlob64) -> Path64 {
71    if b.path_starts.len() < 2 {
72        return Path64::default();
73    }
74    let a = b.path_starts[0];
75    let e = b.path_starts[1];
76    path64_from_p64_slice(&b.points[a..e])
77}
78
79/// Materialize all paths from a blob. / 由 blob 物化全部路径。
80pub(crate) fn paths64_from_blob(b: PathsBlob64) -> Paths64 {
81    if b.path_starts.len() < 2 {
82        return Paths64::default();
83    }
84    let n_paths = b.path_starts.len() - 1;
85    let mut paths = Vec::with_capacity(n_paths);
86    for w in b.path_starts.windows(2) {
87        let (a, e) = (w[0], w[1]);
88        paths.push(path64_from_p64_slice(&b.points[a..e]));
89    }
90    Paths64::from_vec(paths)
91}
92
93#[inline]
94pub(crate) fn pathd_to_blob(p: &PathD) -> PathsBlobD {
95    let n = p.0.len();
96    PathsBlobD {
97        points: p.0.clone(),
98        path_starts: vec![0, n],
99    }
100}
101
102pub(crate) fn pathsd_to_blob(p: &PathsD) -> PathsBlobD {
103    let n_paths = p.0.len();
104    let total_pts: usize = p.0.iter().map(|path| path.0.len()).sum();
105    let mut points = Vec::with_capacity(total_pts);
106    let mut path_starts = Vec::with_capacity(n_paths + 1);
107    path_starts.push(0);
108    for path in &p.0 {
109        points.extend_from_slice(&path.0);
110        path_starts.push(points.len());
111    }
112    PathsBlobD { points, path_starts }
113}
114
115pub(crate) fn pathd_from_single_paths_blob(b: PathsBlobD) -> PathD {
116    if b.path_starts.len() < 2 {
117        return PathD::default();
118    }
119    let a = b.path_starts[0];
120    let e = b.path_starts[1];
121    pathd_from_pd_slice(&b.points[a..e])
122}
123
124pub(crate) fn pathsd_from_blob(b: PathsBlobD) -> PathsD {
125    if b.path_starts.len() < 2 {
126        return PathsD::default();
127    }
128    let n_paths = b.path_starts.len() - 1;
129    let mut paths = Vec::with_capacity(n_paths);
130    for w in b.path_starts.windows(2) {
131        let (a, e) = (w[0], w[1]);
132        paths.push(pathd_from_pd_slice(&b.points[a..e]));
133    }
134    PathsD::from_vec(paths)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::{Path64, PathD, Paths64, PathsD, Point64, PointD};
141
142    #[test]
143    fn path64_single_blob_roundtrip() {
144        let p = Path64::new(vec![
145            Point64::new(0, 0),
146            Point64::new(10, 0),
147            Point64::new(10, 10),
148        ]);
149        let blob = path64_to_blob(&p);
150        let q = path64_from_single_paths_blob(blob);
151        assert_eq!(q.len(), p.len());
152        assert_eq!(q.get_point(0), p.get_point(0));
153        assert_eq!(q.get_point(2), p.get_point(2));
154    }
155
156    #[test]
157    fn paths64_multi_blob_roundtrip() {
158        let a = Path64::new(vec![Point64::new(0, 0), Point64::new(1, 0)]);
159        let b = Path64::new(vec![
160            Point64::new(5, 5),
161            Point64::new(6, 5),
162            Point64::new(6, 6),
163        ]);
164        let paths = Paths64::new(vec![a, b]);
165        let blob = paths64_to_blob(&paths);
166        let out = paths64_from_blob(blob);
167        assert_eq!(out.len(), 2);
168        assert_eq!(out.path(0).len(), 2);
169        assert_eq!(out.path(1).len(), 3);
170    }
171
172    #[test]
173    fn paths64_from_blob_short_starts_is_empty() {
174        let b = PathsBlob64 {
175            points: vec![],
176            path_starts: vec![0],
177        };
178        assert!(paths64_from_blob(b).is_empty());
179        let b2 = PathsBlob64 {
180            points: vec![],
181            path_starts: vec![],
182        };
183        assert!(paths64_from_blob(b2).is_empty());
184    }
185
186    #[test]
187    fn path64_from_single_paths_blob_short_starts_is_default() {
188        let b = PathsBlob64 {
189            points: vec![],
190            path_starts: vec![0],
191        };
192        assert!(path64_from_single_paths_blob(b).is_empty());
193    }
194
195    #[test]
196    fn paths_blob64_iter_exact_len_and_fused() {
197        let paths = Paths64::new(vec![
198            Path64::new(vec![Point64::new(0, 0), Point64::new(1, 0)]),
199            Path64::new(vec![Point64::new(2, 2)]),
200        ]);
201        let blob = paths64_to_blob(&paths);
202        let mut it = PathsBlob64Iter::new(&blob);
203        assert_eq!(it.len(), 2);
204        assert_eq!(it.next().map(|p| p.len()), Some(2));
205        assert_eq!(it.len(), 1);
206        assert_eq!(it.next().map(|p| p.len()), Some(1));
207        assert!(it.next().is_none());
208        assert!(it.next().is_none());
209    }
210
211    #[test]
212    fn pathd_single_and_paths_roundtrip() {
213        let p = PathD::new(vec![
214            PointD::new(0.0, 0.0),
215            PointD::new(3.0, 0.0),
216            PointD::new(3.0, 4.0),
217        ]);
218        let blob = pathd_to_blob(&p);
219        let q = pathd_from_single_paths_blob(blob);
220        assert_eq!(q.len(), 3);
221
222        let paths = PathsD::new(vec![p]);
223        let blob2 = pathsd_to_blob(&paths);
224        let out = pathsd_from_blob(blob2);
225        assert_eq!(out.len(), 1);
226        assert_eq!(out.path(0).len(), 3);
227    }
228}