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