1use std::{iter::FusedIterator, ptr};
4
5use crate::{ffi, PathDataType};
6
7#[derive(Debug)]
8#[doc(alias = "cairo_path_t")]
9pub struct Path(ptr::NonNull<ffi::cairo_path_t>);
10
11impl Path {
12 #[inline]
13 pub fn as_ptr(&self) -> *mut ffi::cairo_path_t {
14 self.0.as_ptr()
15 }
16
17 #[inline]
18 pub unsafe fn from_raw_full(pointer: *mut ffi::cairo_path_t) -> Path {
19 debug_assert!(!pointer.is_null());
20 Path(ptr::NonNull::new_unchecked(pointer))
21 }
22
23 pub fn iter(&self) -> PathSegments {
24 use std::slice;
25
26 unsafe {
27 let ptr: *mut ffi::cairo_path_t = self.as_ptr();
28 let length = (*ptr).num_data as usize;
29 let data_ptr = (*ptr).data;
30 let data_vec = if length != 0 && !data_ptr.is_null() {
31 slice::from_raw_parts(data_ptr, length)
32 } else {
33 &[]
34 };
35
36 PathSegments {
37 data: data_vec,
38 i: 0,
39 num_data: length,
40 }
41 }
42 }
43}
44
45impl Drop for Path {
46 #[inline]
47 fn drop(&mut self) {
48 unsafe {
49 ffi::cairo_path_destroy(self.as_ptr());
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq)]
55pub enum PathSegment {
56 MoveTo((f64, f64)),
57 LineTo((f64, f64)),
58 CurveTo((f64, f64), (f64, f64), (f64, f64)),
59 ClosePath,
60}
61
62pub struct PathSegments<'a> {
63 data: &'a [ffi::cairo_path_data],
64 i: usize,
65 num_data: usize,
66}
67
68impl Iterator for PathSegments<'_> {
69 type Item = PathSegment;
70
71 fn next(&mut self) -> Option<PathSegment> {
72 if self.i >= self.num_data {
73 return None;
74 }
75
76 unsafe {
77 let res = match PathDataType::from(self.data[self.i].header.data_type) {
78 PathDataType::MoveTo => PathSegment::MoveTo(to_tuple(&self.data[self.i + 1].point)),
79 PathDataType::LineTo => PathSegment::LineTo(to_tuple(&self.data[self.i + 1].point)),
80 PathDataType::CurveTo => PathSegment::CurveTo(
81 to_tuple(&self.data[self.i + 1].point),
82 to_tuple(&self.data[self.i + 2].point),
83 to_tuple(&self.data[self.i + 3].point),
84 ),
85 PathDataType::ClosePath => PathSegment::ClosePath,
86 PathDataType::__Unknown(x) => panic!("Unknown value: {x}"),
87 };
88
89 self.i += self.data[self.i].header.length as usize;
90
91 Some(res)
92 }
93 }
94}
95
96impl FusedIterator for PathSegments<'_> {}
97
98fn to_tuple(pair: &[f64; 2]) -> (f64, f64) {
99 (pair[0], pair[1])
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::{context::*, enums::Format, image_surface::*};
106
107 fn make_cr() -> Context {
108 let surface = ImageSurface::create(Format::Rgb24, 1, 1).unwrap();
109
110 Context::new(&surface).expect("Can't create a Cairo context")
111 }
112
113 fn assert_path_equals_segments(expected: &Path, actual: &[PathSegment]) {
114 let expected_iter = expected.iter();
117 let actual_iter = actual.iter();
118
119 assert_eq!(expected_iter.count(), actual_iter.count());
120
121 let expected_iter = expected.iter();
124 let actual_iter = actual.iter();
125
126 let iter = expected_iter.zip(actual_iter);
127 for (e, a) in iter {
128 assert_eq!(e, *a);
129 }
130 }
131
132 #[test]
133 fn empty_path_doesnt_iter() {
134 let cr = make_cr();
135
136 let path = cr.copy_path().expect("Invalid context");
137
138 assert!(path.iter().next().is_none());
139 }
140
141 #[test]
142 fn moveto() {
143 let cr = make_cr();
144
145 cr.move_to(1.0, 2.0);
146
147 let path = cr.copy_path().expect("Invalid path");
148
149 assert_path_equals_segments(&path, &[PathSegment::MoveTo((1.0, 2.0))]);
150 }
151
152 #[test]
153 fn moveto_lineto_moveto() {
154 let cr = make_cr();
155
156 cr.move_to(1.0, 2.0);
157 cr.line_to(3.0, 4.0);
158 cr.move_to(5.0, 6.0);
159
160 let path = cr.copy_path().expect("Invalid path");
161
162 assert_path_equals_segments(
163 &path,
164 &[
165 PathSegment::MoveTo((1.0, 2.0)),
166 PathSegment::LineTo((3.0, 4.0)),
167 PathSegment::MoveTo((5.0, 6.0)),
168 ],
169 );
170 }
171
172 #[test]
173 fn moveto_closepath() {
174 let cr = make_cr();
175
176 cr.move_to(1.0, 2.0);
177 cr.close_path();
178
179 let path = cr.copy_path().expect("Invalid path");
180
181 assert_path_equals_segments(
185 &path,
186 &[
187 PathSegment::MoveTo((1.0, 2.0)),
188 PathSegment::ClosePath,
189 PathSegment::MoveTo((1.0, 2.0)),
190 ],
191 );
192 }
193 #[test]
194 fn curveto_closed_subpath_lineto() {
195 let cr = make_cr();
196
197 cr.move_to(1.0, 2.0);
198 cr.curve_to(3.0, 4.0, 5.0, 6.0, 7.0, 8.0);
199 cr.close_path();
200 cr.line_to(9.0, 10.0);
201
202 let path = cr.copy_path().expect("Invalid path");
203
204 assert_path_equals_segments(
205 &path,
206 &[
207 PathSegment::MoveTo((1.0, 2.0)),
208 PathSegment::CurveTo((3.0, 4.0), (5.0, 6.0), (7.0, 8.0)),
209 PathSegment::ClosePath,
210 PathSegment::MoveTo((1.0, 2.0)),
211 PathSegment::LineTo((9.0, 10.0)),
212 ],
213 );
214 }
215}