1 use { 2 FuzzyEq, 3 }; 4 5 /// List of all path commands. 6 #[derive(Clone, Copy, PartialEq, Debug)] 7 #[allow(missing_docs)] 8 pub enum PathCommand { 9 MoveTo, 10 LineTo, 11 HorizontalLineTo, 12 VerticalLineTo, 13 CurveTo, 14 SmoothCurveTo, 15 Quadratic, 16 SmoothQuadratic, 17 EllipticalArc, 18 ClosePath, 19 } 20 21 /// Representation of the path segment. 22 /// 23 /// If you want to change the segment type (for example MoveTo to LineTo) 24 /// you should create a new segment. 25 /// But you still can change points or make segment relative or absolute. 26 #[allow(missing_docs)] 27 #[derive(Clone, Copy, PartialEq, Debug)] 28 pub enum PathSegment { 29 MoveTo { 30 abs: bool, 31 x: f64, 32 y: f64, 33 }, 34 LineTo { 35 abs: bool, 36 x: f64, 37 y: f64, 38 }, 39 HorizontalLineTo { 40 abs: bool, 41 x: f64, 42 }, 43 VerticalLineTo { 44 abs: bool, 45 y: f64, 46 }, 47 CurveTo { 48 abs: bool, 49 x1: f64, 50 y1: f64, 51 x2: f64, 52 y2: f64, 53 x: f64, 54 y: f64, 55 }, 56 SmoothCurveTo { 57 abs: bool, 58 x2: f64, 59 y2: f64, 60 x: f64, 61 y: f64, 62 }, 63 Quadratic { 64 abs: bool, 65 x1: f64, 66 y1: f64, 67 x: f64, 68 y: f64, 69 }, 70 SmoothQuadratic { 71 abs: bool, 72 x: f64, 73 y: f64, 74 }, 75 EllipticalArc { 76 abs: bool, 77 rx: f64, 78 ry: f64, 79 x_axis_rotation: f64, 80 large_arc: bool, 81 sweep: bool, 82 x: f64, 83 y: f64, 84 }, 85 ClosePath { 86 abs: bool, 87 }, 88 } 89 90 impl PathSegment { 91 /// Sets the segment absolute value. set_absolute(&mut self, new_abs: bool)92 pub fn set_absolute(&mut self, new_abs: bool) { 93 match *self { 94 PathSegment::MoveTo { ref mut abs, .. } 95 | PathSegment::LineTo { ref mut abs, .. } 96 | PathSegment::HorizontalLineTo { ref mut abs, .. } 97 | PathSegment::VerticalLineTo { ref mut abs, .. } 98 | PathSegment::CurveTo { ref mut abs, .. } 99 | PathSegment::SmoothCurveTo { ref mut abs, .. } 100 | PathSegment::Quadratic { ref mut abs, .. } 101 | PathSegment::SmoothQuadratic { ref mut abs, .. } 102 | PathSegment::EllipticalArc { ref mut abs, .. } 103 | PathSegment::ClosePath { ref mut abs, .. } => { *abs = new_abs; } 104 } 105 } 106 107 /// Returns a segment type. cmd(&self) -> PathCommand108 pub fn cmd(&self) -> PathCommand { 109 match *self { 110 PathSegment::MoveTo { .. } => PathCommand::MoveTo, 111 PathSegment::LineTo { .. } => PathCommand::LineTo, 112 PathSegment::HorizontalLineTo { .. } => PathCommand::HorizontalLineTo, 113 PathSegment::VerticalLineTo { .. } => PathCommand::VerticalLineTo, 114 PathSegment::CurveTo { .. } => PathCommand::CurveTo, 115 PathSegment::SmoothCurveTo { .. } => PathCommand::SmoothCurveTo, 116 PathSegment::Quadratic { .. } => PathCommand::Quadratic, 117 PathSegment::SmoothQuadratic { .. } => PathCommand::SmoothQuadratic, 118 PathSegment::EllipticalArc { .. } => PathCommand::EllipticalArc, 119 PathSegment::ClosePath { .. } => PathCommand::ClosePath, 120 } 121 } 122 123 /// Returns `true` if the segment is absolute. 124 #[inline] is_absolute(&self) -> bool125 pub fn is_absolute(&self) -> bool { 126 match *self { 127 PathSegment::MoveTo { abs, .. } 128 | PathSegment::LineTo { abs, .. } 129 | PathSegment::HorizontalLineTo { abs, .. } 130 | PathSegment::VerticalLineTo { abs, .. } 131 | PathSegment::CurveTo { abs, .. } 132 | PathSegment::SmoothCurveTo { abs, .. } 133 | PathSegment::Quadratic { abs, .. } 134 | PathSegment::SmoothQuadratic { abs, .. } 135 | PathSegment::EllipticalArc { abs, .. } 136 | PathSegment::ClosePath { abs, .. } => { abs } 137 } 138 } 139 140 #[inline] 141 /// Returns `true` if the segment is relative. is_relative(&self) -> bool142 pub fn is_relative(&self) -> bool { 143 !self.is_absolute() 144 } 145 146 /// Returns the `x` coordinate of the segment if it has one. x(&self) -> Option<f64>147 pub fn x(&self) -> Option<f64> { 148 match *self { 149 PathSegment::MoveTo { x, .. } 150 | PathSegment::LineTo { x, .. } 151 | PathSegment::HorizontalLineTo { x, .. } 152 | PathSegment::CurveTo { x, .. } 153 | PathSegment::SmoothCurveTo { x, .. } 154 | PathSegment::Quadratic { x, .. } 155 | PathSegment::SmoothQuadratic { x, .. } 156 | PathSegment::EllipticalArc { x, .. } => Some(x), 157 158 PathSegment::VerticalLineTo { .. } 159 | PathSegment::ClosePath { .. } => None, 160 } 161 } 162 163 /// Returns the `y` coordinate of the segment if it has one. y(&self) -> Option<f64>164 pub fn y(&self) -> Option<f64> { 165 match *self { 166 PathSegment::MoveTo { y, .. } 167 | PathSegment::LineTo { y, .. } 168 | PathSegment::VerticalLineTo { y, .. } 169 | PathSegment::CurveTo { y, .. } 170 | PathSegment::SmoothCurveTo { y, .. } 171 | PathSegment::Quadratic { y, .. } 172 | PathSegment::SmoothQuadratic { y, .. } 173 | PathSegment::EllipticalArc { y, .. } => Some(y), 174 175 PathSegment::HorizontalLineTo { .. } 176 | PathSegment::ClosePath { .. } => None, 177 } 178 } 179 } 180 181 impl FuzzyEq for PathSegment { fuzzy_eq(&self, other: &Self) -> bool182 fn fuzzy_eq(&self, other: &Self) -> bool { 183 use self::PathSegment as Seg; 184 185 // TODO: find a way to wrap it in macro 186 match (*self, *other) { 187 (Seg::MoveTo { abs, x, y }, Seg::MoveTo { abs: oabs, x: ox, y: oy }) | 188 (Seg::LineTo { abs, x, y }, Seg::LineTo { abs: oabs, x: ox, y: oy }) | 189 (Seg::SmoothQuadratic { abs, x, y }, Seg::SmoothQuadratic { abs: oabs, x: ox, y: oy }) => { 190 abs == oabs && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy) 191 } 192 (Seg::HorizontalLineTo { abs, x }, Seg::HorizontalLineTo { abs: oabs, x: ox }) => { 193 abs == oabs && x.fuzzy_eq(&ox) 194 } 195 (Seg::VerticalLineTo { abs, y }, Seg::VerticalLineTo { abs: oabs, y: oy }) => { 196 abs == oabs && y.fuzzy_eq(&oy) 197 } 198 (Seg::CurveTo { abs, x1, y1, x2, y2, x, y }, 199 Seg::CurveTo { abs: oabs, x1: ox1, y1: oy1, x2: ox2, y2: oy2, x: ox, y: oy }) => { 200 abs == oabs 201 && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy) 202 && x1.fuzzy_eq(&ox1) && y1.fuzzy_eq(&oy1) 203 && x2.fuzzy_eq(&ox2) && y2.fuzzy_eq(&oy2) 204 } 205 (Seg::SmoothCurveTo { abs, x2, y2, x, y }, 206 Seg::SmoothCurveTo { abs: oabs, x2: ox2, y2: oy2, x: ox, y: oy }) => { 207 abs == oabs 208 && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy) 209 && x2.fuzzy_eq(&ox2) && y2.fuzzy_eq(&oy2) 210 } 211 (Seg::Quadratic { abs, x1, y1, x, y }, 212 Seg::Quadratic { abs: oabs, x1: ox1, y1: oy1, x: ox, y: oy }) => { 213 abs == oabs 214 && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy) 215 && x1.fuzzy_eq(&ox1) && y1.fuzzy_eq(&oy1) 216 } 217 (Seg::EllipticalArc { abs, rx, ry, x_axis_rotation, large_arc, sweep, x, y }, 218 Seg::EllipticalArc { abs: oabs, rx: orx, ry: ory, x_axis_rotation: ox_axis_rotation, 219 large_arc: olarge_arc, sweep: osweep, x: ox, y: oy }) => { 220 abs == oabs 221 && x.fuzzy_eq(&ox) && y.fuzzy_eq(&oy) 222 && rx.fuzzy_eq(&orx) && ry.fuzzy_eq(&ory) 223 && x_axis_rotation.fuzzy_eq(&ox_axis_rotation) 224 && large_arc == olarge_arc 225 && sweep == osweep 226 } 227 (Seg::ClosePath { abs }, Seg::ClosePath { abs: oabs }) => { 228 abs == oabs 229 } 230 _ => false, 231 } 232 } 233 } 234 235 #[cfg(test)] 236 mod fuzzy_eq_tests { 237 use super::*; 238 239 macro_rules! test { 240 ($name:ident, $seg1:expr, $seg2:expr) => ( 241 #[test] 242 fn $name() { 243 assert!($seg1 != $seg2); 244 assert!($seg1.fuzzy_eq(&$seg2)); 245 }) 246 } 247 248 // TODO: find a better way 249 250 test!(m, 251 PathSegment::MoveTo { abs: true, x: 10.0, y: 10.1 + 10.2 }, 252 PathSegment::MoveTo { abs: true, x: 10.0, y: 20.3 } 253 ); 254 255 test!(l, 256 PathSegment::LineTo { abs: true, x: 10.0, y: 10.1 + 10.2 }, 257 PathSegment::LineTo { abs: true, x: 10.0, y: 20.3 } 258 ); 259 260 test!(h, 261 PathSegment::HorizontalLineTo { abs: true, x: 10.1 + 10.2 }, 262 PathSegment::HorizontalLineTo { abs: true, x: 20.3 } 263 ); 264 265 test!(v, 266 PathSegment::VerticalLineTo { abs: true, y: 10.1 + 10.2 }, 267 PathSegment::VerticalLineTo { abs: true, y: 20.3 } 268 ); 269 270 test!(c, 271 PathSegment::CurveTo { abs: true, x1: 10.0, y1: 10.1 + 10.2, x2: 10.0, y2: 10.0, x: 10.0, y: 10.0 }, 272 PathSegment::CurveTo { abs: true, x1: 10.0, y1: 20.3, x2: 10.0, y2: 10.0, x: 10.0, y: 10.0 } 273 ); 274 275 test!(s, 276 PathSegment::SmoothCurveTo { abs: true, x2: 10.0, y2: 10.1 + 10.2, x: 10.0, y: 10.0 }, 277 PathSegment::SmoothCurveTo { abs: true, x2: 10.0, y2: 20.3, x: 10.0, y: 10.0 } 278 ); 279 280 test!(q, 281 PathSegment::Quadratic { abs: true, x1: 10.0, y1: 10.1 + 10.2, x: 10.0, y: 10.0 }, 282 PathSegment::Quadratic { abs: true, x1: 10.0, y1: 20.3, x: 10.0, y: 10.0 } 283 ); 284 285 test!(t, 286 PathSegment::SmoothQuadratic { abs: true, x: 10.0, y: 10.1 + 10.2 }, 287 PathSegment::SmoothQuadratic { abs: true, x: 10.0, y: 20.3 } 288 ); 289 290 test!(a, 291 PathSegment::EllipticalArc { 292 abs: true, 293 rx: 100.0, 294 ry: 100.0, 295 x_axis_rotation: 0.0, 296 large_arc: true, 297 sweep: true, 298 x: 10.1 + 10.2, 299 y: 10.0, 300 }, 301 PathSegment::EllipticalArc { 302 abs: true, 303 rx: 100.0, 304 ry: 100.0, 305 x_axis_rotation: 0.0, 306 large_arc: true, 307 sweep: true, 308 x: 20.3, 309 y: 10.0, 310 } 311 ); 312 } 313