1use crate::{prelude::*, scalar, ContourMeasure, Matrix, Path, Point, Vector};
2use skia_bindings::{self as sb, SkPathMeasure};
3use std::fmt;
4
5pub type PathMeasure = Handle<SkPathMeasure>;
6
7impl NativeDrop for SkPathMeasure {
8 fn drop(&mut self) {
9 unsafe { sb::C_SkPathMeasure_destruct(self) }
10 }
11}
12
13bitflags! {
14 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15 pub struct MatrixFlags : u32 {
16 const GET_POSITION = sb::SkPathMeasure_MatrixFlags_kGetPosition_MatrixFlag as _;
17 const GET_TANGENT = sb::SkPathMeasure_MatrixFlags_kGetTangent_MatrixFlag as _;
18 const GET_POS_AND_TAN = Self::GET_POSITION.bits() | Self::GET_TANGENT.bits();
19 }
20}
21
22impl Default for MatrixFlags {
23 fn default() -> Self {
24 Self::GET_POS_AND_TAN
25 }
26}
27
28impl Default for PathMeasure {
29 fn default() -> Self {
30 Self::from_native_c(unsafe { SkPathMeasure::new() })
31 }
32}
33
34impl fmt::Debug for PathMeasure {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f&mut DebugStruct<'_, '_>.debug_struct("PathMeasure")
37 // TODO: self must be mut
38 // .field("length", &self.length())
39 // .field("is_closed", &self.is_closed())
40 // .field("next_contour", &self.next_contour())
41 .field(name:"current_measure", &self.current_measure())
42 .finish()
43 }
44}
45
46/// Warning: Even if you pass in a `PathMeasure` with multiple contours, most of this struct's functions, including `length` only return the value for the first contour on the path (which is why they aren't `const`). You must exhaust `PathMeasure::next_contour`.
47///
48/// ```
49/// use skia_safe::{PathMeasure, Point, Path};
50/// use std::f64::consts::PI;
51/// let mut path = Path::circle((0., 0.), 10.0, None);
52/// path.add_path(&Path::circle((100., 100.), 27.0, None), Point::default(), None);
53/// let mut measure = PathMeasure::new(&path, false, None);
54/// let mut lengths = vec![measure.length()];
55/// while measure.next_contour() {
56/// lengths.push(measure.length());
57/// }
58/// assert_eq!(*lengths.first().unwrap() as i64, (2. * PI * 10.0) as i64);
59/// assert_eq!(*lengths.get(1).unwrap() as i64, (2. * PI * 27.0) as i64);
60/// eprintln!("Circle lengths: {:?}", &lengths);
61/// ```
62impl PathMeasure {
63 pub fn new(path: &Path, force_closed: bool, res_scale: impl Into<Option<scalar>>) -> Self {
64 Self::from_native_c(unsafe {
65 SkPathMeasure::new1(path.native(), force_closed, res_scale.into().unwrap_or(1.0))
66 })
67 }
68
69 #[deprecated(since = "0.48.0", note = "Use PathMeasure::new")]
70 pub fn from_path(
71 path: &Path,
72 force_closed: bool,
73 res_scale: impl Into<Option<scalar>>,
74 ) -> Self {
75 Self::new(path, force_closed, res_scale)
76 }
77
78 pub fn set_path(&mut self, path: &Path, force_closed: bool) -> &mut Self {
79 unsafe { self.native_mut().setPath(path.native(), force_closed) }
80 self
81 }
82
83 pub fn length(&mut self) -> scalar {
84 unsafe { self.native_mut().getLength() }
85 }
86
87 // TODO: rename to get_pos_tan(), because the function expects arguments?
88 #[must_use]
89 pub fn pos_tan(&mut self, distance: scalar) -> Option<(Point, Vector)> {
90 let mut position = Point::default();
91 let mut tangent = Vector::default();
92 unsafe {
93 self.native_mut()
94 .getPosTan(distance, position.native_mut(), tangent.native_mut())
95 }
96 .if_true_some((position, tangent))
97 }
98
99 // TODO: rename to get_matrix(), because the function expects arguments?
100 #[must_use]
101 pub fn matrix(
102 &mut self,
103 distance: scalar,
104 flags: impl Into<Option<MatrixFlags>>,
105 ) -> Option<Matrix> {
106 let mut m = Matrix::default();
107 unsafe {
108 self.native_mut().getMatrix(
109 distance,
110 m.native_mut(),
111 // note: depending on the OS, different representation types are generated for MatrixFlags
112 #[allow(clippy::useless_conversion)]
113 flags.into().unwrap_or_default().bits().try_into().unwrap(),
114 )
115 }
116 .if_true_some(m)
117 }
118
119 // TODO: rename to get_segment(), because the function has arguments?
120 pub fn segment(
121 &mut self,
122 start_d: scalar,
123 stop_d: scalar,
124 start_with_move_to: bool,
125 ) -> Option<Path> {
126 let mut p = Path::default();
127 unsafe {
128 self.native_mut()
129 .getSegment(start_d, stop_d, p.native_mut(), start_with_move_to)
130 }
131 .if_true_some(p)
132 }
133
134 #[allow(clippy::wrong_self_convention)]
135 pub fn is_closed(&mut self) -> bool {
136 unsafe { self.native_mut().isClosed() }
137 }
138
139 // TODO: rename to has_next_contour()?
140 pub fn next_contour(&mut self) -> bool {
141 unsafe { self.native_mut().nextContour() }
142 }
143
144 pub fn current_measure(&self) -> &Option<ContourMeasure> {
145 ContourMeasure::from_unshared_ptr_ref(&self.native().fContour.fPtr)
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use crate::{Path, PathMeasure, Point};
152
153 #[test]
154 fn current_measure() {
155 let mut path = Path::circle((0., 0.), 10.0, None);
156 path.add_path(
157 &Path::circle((100., 100.), 27.0, None),
158 Point::default(),
159 None,
160 );
161 let mut measure = PathMeasure::new(&path, false, None);
162 while measure.next_contour() {
163 eprintln!("contour: {:?}", measure.current_measure());
164 }
165 assert!(measure.current_measure().is_none());
166 }
167}
168