1
//! Representation of Bézier paths.
2
//!
3
//! Path data can consume a significant amount of memory in complex SVG documents.  This
4
//! module deals with this as follows:
5
//!
6
//! * The path parser pushes commands into a [`PathBuilder`].  This is a mutable,
7
//!   temporary storage for path data.
8
//!
9
//! * Then, the [`PathBuilder`] gets turned into a long-term, immutable [`Path`] that has
10
//!   a more compact representation.
11
//!
12
//! The code tries to reduce work in the allocator, by using a [`TinyVec`] with space for at
13
//! least 32 commands on the stack for `PathBuilder`; most paths in SVGs in the wild have
14
//! fewer than 32 commands, and larger ones will spill to the heap.
15
//!
16
//! See these blog posts for details and profiles:
17
//!
18
//! * [Compact representation for path data](https://viruta.org/reducing-memory-consumption-in-librsvg-4.html)
19
//! * [Reducing slack space and allocator work](https://viruta.org/reducing-memory-consumption-in-librsvg-3.html)
20

            
21
use tinyvec::TinyVec;
22

            
23
use std::f64;
24
use std::f64::consts::*;
25
use std::slice;
26

            
27
use crate::float_eq_cairo::ApproxEqCairo;
28
use crate::path_parser::{ParseError, PathParser};
29
use crate::util::clamp;
30

            
31
/// Whether an arc's sweep should be >= 180 degrees, or smaller.
32
#[derive(Debug, Copy, Clone, PartialEq)]
33
pub struct LargeArc(pub bool);
34

            
35
/// Angular direction in which an arc is drawn.
36
#[derive(Debug, Copy, Clone, PartialEq)]
37
pub enum Sweep {
38
    Negative,
39
    Positive,
40
}
41

            
42
/// "c" command for paths; describes a cubic Bézier segment.
43
#[derive(Debug, Clone, PartialEq, Default)]
44
pub struct CubicBezierCurve {
45
    /// The (x, y) coordinates of the first control point.
46
    pub pt1: (f64, f64),
47
    /// The (x, y) coordinates of the second control point.
48
    pub pt2: (f64, f64),
49
    /// The (x, y) coordinates of the end point of this path segment.
50
    pub to: (f64, f64),
51
}
52

            
53
impl CubicBezierCurve {
54
    /// Consumes 6 coordinates and creates a curve segment.
55
309255
    fn from_coords(coords: &mut slice::Iter<'_, f64>) -> CubicBezierCurve {
56
309255
        let pt1 = take_two(coords);
57
309255
        let pt2 = take_two(coords);
58
309255
        let to = take_two(coords);
59
309255

            
60
309255
        CubicBezierCurve { pt1, pt2, to }
61
309255
    }
62

            
63
    /// Pushes 6 coordinates to `coords` and returns `PackedCommand::CurveTo`.
64
155431
    fn to_packed_and_coords(&self, coords: &mut Vec<f64>) -> PackedCommand {
65
155431
        coords.push(self.pt1.0);
66
155431
        coords.push(self.pt1.1);
67
155431
        coords.push(self.pt2.0);
68
155431
        coords.push(self.pt2.1);
69
155431
        coords.push(self.to.0);
70
155431
        coords.push(self.to.1);
71
155431
        PackedCommand::CurveTo
72
155431
    }
73
}
74

            
75
/// Conversion from endpoint parameterization to center parameterization.
76
///
77
/// SVG path data specifies elliptical arcs in terms of their endpoints, but
78
/// they are easier to process if they are converted to a center parameterization.
79
///
80
/// When attempting to compute the center parameterization of the arc,
81
/// out of range parameters may see an arc omitted or treated as a line.
82
pub enum ArcParameterization {
83
    /// Center parameterization of the arc.
84
    CenterParameters {
85
        /// Center of the ellipse.
86
        center: (f64, f64),
87
        /// Radii of the ellipse (corrected).
88
        radii: (f64, f64),
89
        /// Angle of the start point.
90
        theta1: f64,
91
        /// Delta angle to the end point.
92
        delta_theta: f64,
93
    },
94
    /// Treat the arc as a line to the end point.
95
    LineTo,
96
    /// Omit the arc.
97
    Omit,
98
}
99

            
100
/// "a" command for paths; describes  an elliptical arc in terms of its endpoints.
101
#[derive(Debug, Clone, PartialEq)]
102
pub struct EllipticalArc {
103
    /// The (x-axis, y-axis) radii for the ellipse.
104
    pub r: (f64, f64),
105
    /// The rotation angle in degrees for the ellipse's x-axis
106
    /// relative to the x-axis of the user coordinate system.
107
    pub x_axis_rotation: f64,
108
    /// Flag indicating whether the arc sweep should be
109
    /// greater than or equal to 180 degrees, or smaller than 180 degrees.
110
    pub large_arc: LargeArc,
111
    /// Flag indicating the angular direction in which the arc is drawn.
112
    pub sweep: Sweep,
113
    /// The (x, y) coordinates for the start point of this path segment.
114
    pub from: (f64, f64),
115
    /// The (x, y) coordinates for the end point of this path segment.
116
    pub to: (f64, f64),
117
}
118

            
119
impl EllipticalArc {
120
    /// Calculates a center parameterization from the endpoint parameterization.
121
    ///
122
    /// Radii may be adjusted if there is no solution.
123
    ///
124
    /// See section [B.2.4. Conversion from endpoint to center
125
    /// parameterization](https://www.w3.org/TR/SVG2/implnote.html#ArcConversionEndpointToCenter)
126
53542
    pub(crate) fn center_parameterization(&self) -> ArcParameterization {
127
53542
        let Self {
128
53542
            r: (mut rx, mut ry),
129
53542
            x_axis_rotation,
130
53542
            large_arc,
131
53542
            sweep,
132
53542
            from: (x1, y1),
133
53542
            to: (x2, y2),
134
53542
        } = *self;
135
53542

            
136
53542
        // Ensure radii are non-zero.
137
53542
        // Otherwise this arc is treated as a line segment joining the end points.
138
53542
        //
139
53542
        // A bit further down we divide by the square of the radii.
140
53542
        // Check that we won't divide by zero.
141
53542
        // See http://bugs.debian.org/508443
142
53542
        if rx * rx < f64::EPSILON || ry * ry < f64::EPSILON {
143
            return ArcParameterization::LineTo;
144
53542
        }
145
53542

            
146
53542
        let is_large_arc = large_arc.0;
147
53542
        let is_positive_sweep = sweep == Sweep::Positive;
148
53542

            
149
53542
        let phi = x_axis_rotation * PI / 180.0;
150
53542
        let (sin_phi, cos_phi) = phi.sin_cos();
151
53542

            
152
53542
        // Ensure radii are positive.
153
53542
        rx = rx.abs();
154
53542
        ry = ry.abs();
155
53542

            
156
53542
        // The equations simplify after a translation which places
157
53542
        // the origin at the midpoint of the line joining (x1, y1) to (x2, y2),
158
53542
        // followed by a rotation to line up the coordinate axes
159
53542
        // with the axes of the ellipse.
160
53542
        // All transformed coordinates will be written with primes.
161
53542
        //
162
53542
        // Compute (x1', y1').
163
53542
        let mid_x = (x1 - x2) / 2.0;
164
53542
        let mid_y = (y1 - y2) / 2.0;
165
53542
        let x1_ = cos_phi * mid_x + sin_phi * mid_y;
166
53542
        let y1_ = -sin_phi * mid_x + cos_phi * mid_y;
167
53542

            
168
53542
        // Ensure radii are large enough.
169
53542
        let lambda = (x1_ / rx).powi(2) + (y1_ / ry).powi(2);
170
53542
        if lambda > 1.0 {
171
1710
            // If not, scale up the ellipse uniformly
172
1710
            // until there is exactly one solution.
173
1710
            rx *= lambda.sqrt();
174
1710
            ry *= lambda.sqrt();
175
51832
        }
176

            
177
        // Compute the transformed center (cx', cy').
178
53542
        let d = (rx * y1_).powi(2) + (ry * x1_).powi(2);
179
53542
        if d == 0.0 {
180
            return ArcParameterization::Omit;
181
53542
        }
182
53542
        let k = {
183
53542
            let mut k = ((rx * ry).powi(2) / d - 1.0).abs().sqrt();
184
53542
            if is_positive_sweep == is_large_arc {
185
24738
                k = -k;
186
28804
            }
187
53542
            k
188
53542
        };
189
53542
        let cx_ = k * rx * y1_ / ry;
190
53542
        let cy_ = -k * ry * x1_ / rx;
191
53542

            
192
53542
        // Compute the center (cx, cy).
193
53542
        let cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2.0;
194
53542
        let cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2.0;
195
53542

            
196
53542
        // Compute the start angle θ1.
197
53542
        let ux = (x1_ - cx_) / rx;
198
53542
        let uy = (y1_ - cy_) / ry;
199
53542
        let u_len = (ux * ux + uy * uy).abs().sqrt();
200
53542
        if u_len == 0.0 {
201
            return ArcParameterization::Omit;
202
53542
        }
203
53542
        let cos_theta1 = clamp(ux / u_len, -1.0, 1.0);
204
53542
        let theta1 = {
205
53542
            let mut theta1 = cos_theta1.acos();
206
53542
            if uy < 0.0 {
207
21413
                theta1 = -theta1;
208
32129
            }
209
53542
            theta1
210
53542
        };
211
53542

            
212
53542
        // Compute the total delta angle Δθ.
213
53542
        let vx = (-x1_ - cx_) / rx;
214
53542
        let vy = (-y1_ - cy_) / ry;
215
53542
        let v_len = (vx * vx + vy * vy).abs().sqrt();
216
53542
        if v_len == 0.0 {
217
            return ArcParameterization::Omit;
218
53542
        }
219
53542
        let dp_uv = ux * vx + uy * vy;
220
53542
        let cos_delta_theta = clamp(dp_uv / (u_len * v_len), -1.0, 1.0);
221
53542
        let delta_theta = {
222
53542
            let mut delta_theta = cos_delta_theta.acos();
223
53542
            if ux * vy - uy * vx < 0.0 {
224
18202
                delta_theta = -delta_theta;
225
35340
            }
226
53542
            if is_positive_sweep && delta_theta < 0.0 {
227
1140
                delta_theta += PI * 2.0;
228
52402
            } else if !is_positive_sweep && delta_theta > 0.0 {
229
7448
                delta_theta -= PI * 2.0;
230
44954
            }
231
53542
            delta_theta
232
53542
        };
233
53542

            
234
53542
        ArcParameterization::CenterParameters {
235
53542
            center: (cx, cy),
236
53542
            radii: (rx, ry),
237
53542
            theta1,
238
53542
            delta_theta,
239
53542
        }
240
53542
    }
241

            
242
    /// Consumes 7 coordinates and creates an arc segment.
243
53646
    fn from_coords(
244
53646
        large_arc: LargeArc,
245
53646
        sweep: Sweep,
246
53646
        coords: &mut slice::Iter<'_, f64>,
247
53646
    ) -> EllipticalArc {
248
53646
        let r = take_two(coords);
249
53646
        let x_axis_rotation = take_one(coords);
250
53646
        let from = take_two(coords);
251
53646
        let to = take_two(coords);
252
53646

            
253
53646
        EllipticalArc {
254
53646
            r,
255
53646
            x_axis_rotation,
256
53646
            large_arc,
257
53646
            sweep,
258
53646
            from,
259
53646
            to,
260
53646
        }
261
53646
    }
262

            
263
    /// Pushes 7 coordinates to `coords` and returns one of `PackedCommand::Arc*`.
264
28756
    fn to_packed_and_coords(&self, coords: &mut Vec<f64>) -> PackedCommand {
265
28756
        coords.push(self.r.0);
266
28756
        coords.push(self.r.1);
267
28756
        coords.push(self.x_axis_rotation);
268
28756
        coords.push(self.from.0);
269
28756
        coords.push(self.from.1);
270
28756
        coords.push(self.to.0);
271
28756
        coords.push(self.to.1);
272
28756

            
273
28756
        match (self.large_arc, self.sweep) {
274
8570
            (LargeArc(false), Sweep::Negative) => PackedCommand::ArcSmallNegative,
275
13149
            (LargeArc(false), Sweep::Positive) => PackedCommand::ArcSmallPositive,
276
3687
            (LargeArc(true), Sweep::Negative) => PackedCommand::ArcLargeNegative,
277
3350
            (LargeArc(true), Sweep::Positive) => PackedCommand::ArcLargePositive,
278
        }
279
28756
    }
280
}
281

            
282
/// Turns an arc segment into a cubic bezier curve.
283
///
284
/// Takes the center, the radii and the x-axis rotation of the ellipse,
285
/// the angles of the start and end points,
286
/// and returns cubic bezier curve parameters.
287
79838
pub(crate) fn arc_segment(
288
79838
    c: (f64, f64),
289
79838
    r: (f64, f64),
290
79838
    x_axis_rotation: f64,
291
79838
    th0: f64,
292
79838
    th1: f64,
293
79838
) -> CubicBezierCurve {
294
79838
    let (cx, cy) = c;
295
79838
    let (rx, ry) = r;
296
79838
    let phi = x_axis_rotation * PI / 180.0;
297
79838
    let (sin_phi, cos_phi) = phi.sin_cos();
298
79838
    let (sin_th0, cos_th0) = th0.sin_cos();
299
79838
    let (sin_th1, cos_th1) = th1.sin_cos();
300
79838

            
301
79838
    let th_half = 0.5 * (th1 - th0);
302
79838
    let t = (8.0 / 3.0) * (th_half * 0.5).sin().powi(2) / th_half.sin();
303
79838
    let x1 = rx * (cos_th0 - t * sin_th0);
304
79838
    let y1 = ry * (sin_th0 + t * cos_th0);
305
79838
    let x3 = rx * cos_th1;
306
79838
    let y3 = ry * sin_th1;
307
79838
    let x2 = x3 + rx * (t * sin_th1);
308
79838
    let y2 = y3 + ry * (-t * cos_th1);
309
79838

            
310
79838
    CubicBezierCurve {
311
79838
        pt1: (
312
79838
            cx + cos_phi * x1 - sin_phi * y1,
313
79838
            cy + sin_phi * x1 + cos_phi * y1,
314
79838
        ),
315
79838
        pt2: (
316
79838
            cx + cos_phi * x2 - sin_phi * y2,
317
79838
            cy + sin_phi * x2 + cos_phi * y2,
318
79838
        ),
319
79838
        to: (
320
79838
            cx + cos_phi * x3 - sin_phi * y3,
321
79838
            cy + sin_phi * x3 + cos_phi * y3,
322
79838
        ),
323
79838
    }
324
79838
}
325

            
326
/// Long-form version of a single path command.
327
///
328
/// This is returned from iterators on paths and subpaths.
329
#[derive(Clone, Default, Debug, PartialEq)]
330
pub enum PathCommand {
331
    MoveTo(f64, f64),
332
    LineTo(f64, f64),
333
    CurveTo(CubicBezierCurve),
334
    Arc(EllipticalArc),
335

            
336
    // The #[default] is just so we can use TinyVec, whose type
337
    // parameter requires T: Default.  There is no actual default for
338
    // path commands in the SVG spec; this is just our implementation
339
    // detail.
340
    #[default]
341
    ClosePath,
342
}
343

            
344
impl PathCommand {
345
    /// Returns the number of coordinate values that this command will generate in a `Path`.
346
108406483
    fn num_coordinates(&self) -> usize {
347
108406483
        match *self {
348
18046131
            PathCommand::MoveTo(..) => 2,
349
72142206
            PathCommand::LineTo(..) => 2,
350
155431
            PathCommand::CurveTo(_) => 6,
351
28756
            PathCommand::Arc(_) => 7,
352
18033959
            PathCommand::ClosePath => 0,
353
        }
354
108406483
    }
355

            
356
    /// Pushes a command's coordinates to `coords` and returns the corresponding `PackedCommand`.
357
108406483
    fn to_packed(&self, coords: &mut Vec<f64>) -> PackedCommand {
358
108406483
        match *self {
359
18046131
            PathCommand::MoveTo(x, y) => {
360
18046131
                coords.push(x);
361
18046131
                coords.push(y);
362
18046131
                PackedCommand::MoveTo
363
            }
364

            
365
72142206
            PathCommand::LineTo(x, y) => {
366
72142206
                coords.push(x);
367
72142206
                coords.push(y);
368
72142206
                PackedCommand::LineTo
369
            }
370

            
371
155431
            PathCommand::CurveTo(ref c) => c.to_packed_and_coords(coords),
372

            
373
28756
            PathCommand::Arc(ref a) => a.to_packed_and_coords(coords),
374

            
375
18033959
            PathCommand::ClosePath => PackedCommand::ClosePath,
376
        }
377
108406483
    }
378

            
379
    /// Consumes a packed command's coordinates from the `coords` iterator and returns the rehydrated `PathCommand`.
380
216790360
    fn from_packed(packed: PackedCommand, coords: &mut slice::Iter<'_, f64>) -> PathCommand {
381
216790360
        match packed {
382
            PackedCommand::MoveTo => {
383
36091470
                let x = take_one(coords);
384
36091470
                let y = take_one(coords);
385
36091470
                PathCommand::MoveTo(x, y)
386
            }
387

            
388
            PackedCommand::LineTo => {
389
144268994
                let x = take_one(coords);
390
144268994
                let y = take_one(coords);
391
144268994
                PathCommand::LineTo(x, y)
392
            }
393

            
394
309255
            PackedCommand::CurveTo => PathCommand::CurveTo(CubicBezierCurve::from_coords(coords)),
395

            
396
36066995
            PackedCommand::ClosePath => PathCommand::ClosePath,
397

            
398
17158
            PackedCommand::ArcSmallNegative => PathCommand::Arc(EllipticalArc::from_coords(
399
17158
                LargeArc(false),
400
17158
                Sweep::Negative,
401
17158
                coords,
402
17158
            )),
403

            
404
21509
            PackedCommand::ArcSmallPositive => PathCommand::Arc(EllipticalArc::from_coords(
405
21509
                LargeArc(false),
406
21509
                Sweep::Positive,
407
21509
                coords,
408
21509
            )),
409

            
410
7373
            PackedCommand::ArcLargeNegative => PathCommand::Arc(EllipticalArc::from_coords(
411
7373
                LargeArc(true),
412
7373
                Sweep::Negative,
413
7373
                coords,
414
7373
            )),
415

            
416
7606
            PackedCommand::ArcLargePositive => PathCommand::Arc(EllipticalArc::from_coords(
417
7606
                LargeArc(true),
418
7606
                Sweep::Positive,
419
7606
                coords,
420
7606
            )),
421
        }
422
216790360
    }
423
}
424

            
425
/// Constructs a path out of commands.
426
///
427
/// Create this with `PathBuilder::default`; you can then add commands to it or call the
428
/// `parse` method.  When you are finished constructing a path builder, turn it into a
429
/// `Path` with `into_path`.  You can then iterate on that `Path`'s commands with its
430
/// methods.
431
#[derive(Default)]
432
pub struct PathBuilder {
433
    path_commands: TinyVec<[PathCommand; 32]>,
434
}
435

            
436
/// An immutable path with a compact representation.
437
///
438
/// This is constructed from a `PathBuilder` once it is finished.  You
439
/// can get an iterator for the path's commands with the `iter`
440
/// method, or an iterator for its subpaths (subsequences of commands that
441
/// start with a MoveTo) with the `iter_subpath` method.
442
///
443
/// The variants in `PathCommand` have different sizes, so a simple array of `PathCommand`
444
/// would have a lot of slack space.  We reduce this to a minimum by separating the
445
/// commands from their coordinates.  Then, we can have two dense arrays: one with a compact
446
/// representation of commands, and another with a linear list of the coordinates for each
447
/// command.
448
///
449
/// Both `PathCommand` and `PackedCommand` know how many coordinates they ought to
450
/// produce, with their `num_coordinates` methods.
451
///
452
/// This struct implements `Default`, and it yields an empty path.
453
#[derive(Default)]
454
pub struct Path {
455
    commands: Box<[PackedCommand]>,
456
    coords: Box<[f64]>,
457
}
458

            
459
/// Packed version of a `PathCommand`, used in `Path`.
460
///
461
/// MoveTo/LineTo/CurveTo have only pairs of coordinates, while ClosePath has no coordinates,
462
/// and EllipticalArc has a bunch of coordinates plus two flags.  Here we represent the flags
463
/// as four variants.
464
///
465
/// This is `repr(u8)` to keep it as small as possible.
466
#[repr(u8)]
467
#[derive(Debug, Clone, Copy)]
468
enum PackedCommand {
469
    MoveTo,
470
    LineTo,
471
    CurveTo,
472
    ArcSmallNegative,
473
    ArcSmallPositive,
474
    ArcLargeNegative,
475
    ArcLargePositive,
476
    ClosePath,
477
}
478

            
479
impl PackedCommand {
480
    // Returns the number of coordinate values that this command will generate in a `Path`.
481
216778716
    fn num_coordinates(&self) -> usize {
482
216778716
        match *self {
483
36086788
            PackedCommand::MoveTo => 2,
484
144263658
            PackedCommand::LineTo => 2,
485
308561
            PackedCommand::CurveTo => 6,
486
            PackedCommand::ArcSmallNegative
487
            | PackedCommand::ArcSmallPositive
488
            | PackedCommand::ArcLargeNegative
489
53466
            | PackedCommand::ArcLargePositive => 7,
490
36066243
            PackedCommand::ClosePath => 0,
491
        }
492
216778716
    }
493
}
494

            
495
impl PathBuilder {
496
32356
    pub fn parse(&mut self, path_str: &str) -> Result<(), ParseError> {
497
32356
        let mut parser = PathParser::new(self, path_str);
498
32356
        parser.parse()
499
32356
    }
500

            
501
    /// Consumes the `PathBuilder` and returns a compact, immutable representation as a `Path`.
502
18030748
    pub fn into_path(self) -> Path {
503
18030748
        let num_coords = self
504
18030748
            .path_commands
505
18030748
            .iter()
506
18030748
            .map(PathCommand::num_coordinates)
507
18030748
            .sum();
508
18030748

            
509
18030748
        let mut coords = Vec::with_capacity(num_coords);
510
18030748
        let packed_commands: Vec<_> = self
511
18030748
            .path_commands
512
18030748
            .iter()
513
108406483
            .map(|cmd| cmd.to_packed(&mut coords))
514
18030748
            .collect();
515
18030748

            
516
18030748
        Path {
517
18030748
            commands: packed_commands.into_boxed_slice(),
518
18030748
            coords: coords.into_boxed_slice(),
519
18030748
        }
520
18030748
    }
521

            
522
    /// Adds a MoveTo command to the path.
523
18046131
    pub fn move_to(&mut self, x: f64, y: f64) {
524
18046131
        self.path_commands.push(PathCommand::MoveTo(x, y));
525
18046131
    }
526

            
527
    /// Adds a LineTo command to the path.
528
72142206
    pub fn line_to(&mut self, x: f64, y: f64) {
529
72142206
        self.path_commands.push(PathCommand::LineTo(x, y));
530
72142206
    }
531

            
532
    /// Adds a CurveTo command to the path.
533
155431
    pub fn curve_to(&mut self, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) {
534
155431
        let curve = CubicBezierCurve {
535
155431
            pt1: (x2, y2),
536
155431
            pt2: (x3, y3),
537
155431
            to: (x4, y4),
538
155431
        };
539
155431
        self.path_commands.push(PathCommand::CurveTo(curve));
540
155431
    }
541

            
542
    /// Adds an EllipticalArc command to the path.
543
28756
    pub fn arc(
544
28756
        &mut self,
545
28756
        x1: f64,
546
28756
        y1: f64,
547
28756
        rx: f64,
548
28756
        ry: f64,
549
28756
        x_axis_rotation: f64,
550
28756
        large_arc: LargeArc,
551
28756
        sweep: Sweep,
552
28756
        x2: f64,
553
28756
        y2: f64,
554
28756
    ) {
555
28756
        let arc = EllipticalArc {
556
28756
            r: (rx, ry),
557
28756
            x_axis_rotation,
558
28756
            large_arc,
559
28756
            sweep,
560
28756
            from: (x1, y1),
561
28756
            to: (x2, y2),
562
28756
        };
563
28756
        self.path_commands.push(PathCommand::Arc(arc));
564
28756
    }
565

            
566
    /// Adds a ClosePath command to the path.
567
18033959
    pub fn close_path(&mut self) {
568
18033959
        self.path_commands.push(PathCommand::ClosePath);
569
18033959
    }
570
}
571

            
572
/// An iterator over the subpaths of a `Path`.
573
pub struct SubPathIter<'a> {
574
    path: &'a Path,
575
    commands_start: usize,
576
    coords_start: usize,
577
}
578

            
579
/// A slice of commands and coordinates with a single `MoveTo` at the beginning.
580
pub struct SubPath<'a> {
581
    commands: &'a [PackedCommand],
582
    coords: &'a [f64],
583
}
584

            
585
/// An iterator over the commands/coordinates of a subpath.
586
pub struct SubPathCommandsIter<'a> {
587
    commands_iter: slice::Iter<'a, PackedCommand>,
588
    coords_iter: slice::Iter<'a, f64>,
589
}
590

            
591
impl<'a> SubPath<'a> {
592
    /// Returns an iterator over the subpath's commands.
593
36087434
    pub fn iter_commands(&self) -> SubPathCommandsIter<'_> {
594
36087434
        SubPathCommandsIter {
595
36087434
            commands_iter: self.commands.iter(),
596
36087434
            coords_iter: self.coords.iter(),
597
36087434
        }
598
36087434
    }
599

            
600
    /// Each subpath starts with a MoveTo; this returns its `(x, y)` coordinates.
601
1301
    pub fn origin(&self) -> (f64, f64) {
602
1301
        let first = *self.commands.first().unwrap();
603
1301
        assert!(matches!(first, PackedCommand::MoveTo));
604
1301
        let command = PathCommand::from_packed(first, &mut self.coords.iter());
605
1301

            
606
1301
        match command {
607
1301
            PathCommand::MoveTo(x, y) => (x, y),
608
            _ => unreachable!(),
609
        }
610
1301
    }
611

            
612
    /// Returns whether the length of a subpath is approximately zero.
613
649
    pub fn is_zero_length(&self) -> bool {
614
649
        let (cur_x, cur_y) = self.origin();
615

            
616
649
        for cmd in self.iter_commands().skip(1) {
617
648
            let (end_x, end_y) = match cmd {
618
                PathCommand::MoveTo(_, _) => unreachable!(
619
                    "A MoveTo cannot appear in a subpath if it's not the first element"
620
                ),
621
609
                PathCommand::LineTo(x, y) => (x, y),
622
19
                PathCommand::CurveTo(curve) => curve.to,
623
19
                PathCommand::Arc(arc) => arc.to,
624
                // If we get a `ClosePath and haven't returned yet then we haven't moved at all making
625
                // it an empty subpath`
626
1
                PathCommand::ClosePath => return true,
627
            };
628

            
629
647
            if !end_x.approx_eq_cairo(cur_x) || !end_y.approx_eq_cairo(cur_y) {
630
628
                return false;
631
19
            }
632
        }
633

            
634
20
        true
635
649
    }
636
}
637

            
638
impl<'a> Iterator for SubPathIter<'a> {
639
    type Item = SubPath<'a>;
640

            
641
72144768
    fn next(&mut self) -> Option<Self::Item> {
642
72144768
        // If we ended on our last command in the previous iteration, we're done here
643
72144768
        if self.commands_start >= self.path.commands.len() {
644
36057980
            return None;
645
36086788
        }
646
36086788

            
647
36086788
        // Otherwise we have at least one command left, we setup the slice to be all the remaining
648
36086788
        // commands.
649
36086788
        let commands = &self.path.commands[self.commands_start..];
650
36086788

            
651
36086788
        assert!(matches!(commands.first().unwrap(), PackedCommand::MoveTo));
652
36086788
        let mut num_coords = PackedCommand::MoveTo.num_coordinates();
653

            
654
        // Skip over the initial MoveTo
655
180721078
        for (i, cmd) in commands.iter().enumerate().skip(1) {
656
            // If we encounter a MoveTo , we ended our current subpath, we
657
            // return the commands until this command and set commands_start to be the index of the
658
            // next command
659
180721078
            if let PackedCommand::MoveTo = cmd {
660
29150
                let subpath_coords_start = self.coords_start;
661
29150

            
662
29150
                self.commands_start += i;
663
29150
                self.coords_start += num_coords;
664
29150

            
665
29150
                return Some(SubPath {
666
29150
                    commands: &commands[..i],
667
29150
                    coords: &self.path.coords
668
29150
                        [subpath_coords_start..subpath_coords_start + num_coords],
669
29150
                });
670
180691928
            } else {
671
180691928
                num_coords += cmd.num_coordinates();
672
180691928
            }
673
        }
674

            
675
        // If we didn't find any MoveTo, we're done here. We return the rest of the path
676
        // and set commands_start so next iteration will return None.
677

            
678
36057638
        self.commands_start = self.path.commands.len();
679
36057638

            
680
36057638
        let subpath_coords_start = self.coords_start;
681
36057638
        assert!(subpath_coords_start + num_coords == self.path.coords.len());
682
36057638
        self.coords_start = self.path.coords.len();
683
36057638

            
684
36057638
        Some(SubPath {
685
36057638
            commands,
686
36057638
            coords: &self.path.coords[subpath_coords_start..],
687
36057638
        })
688
72144768
    }
689
}
690

            
691
impl<'a> Iterator for SubPathCommandsIter<'a> {
692
    type Item = PathCommand;
693

            
694
252866813
    fn next(&mut self) -> Option<Self::Item> {
695
252866813
        self.commands_iter
696
252866813
            .next()
697
252866813
            .map(|packed| PathCommand::from_packed(*packed, &mut self.coords_iter))
698
252866813
    }
699
}
700

            
701
impl Path {
702
    /// Get an iterator over a path `Subpath`s.
703
36057980
    pub fn iter_subpath(&self) -> SubPathIter<'_> {
704
36057980
        SubPathIter {
705
36057980
            path: self,
706
36057980
            commands_start: 0,
707
36057980
            coords_start: 0,
708
36057980
        }
709
36057980
    }
710

            
711
    /// Get an iterator over a path's commands.
712
2519
    pub fn iter(&self) -> impl Iterator<Item = PathCommand> + '_ {
713
2519
        let commands = self.commands.iter();
714
2519
        let mut coords = self.coords.iter();
715
2519

            
716
9051
        commands.map(move |cmd| PathCommand::from_packed(*cmd, &mut coords))
717
2519
    }
718

            
719
    /// Returns whether there are no commands in the path.
720
18029123
    pub fn is_empty(&self) -> bool {
721
18029123
        self.commands.is_empty()
722
18029123
    }
723
}
724

            
725
362951980
fn take_one(iter: &mut slice::Iter<'_, f64>) -> f64 {
726
362951980
    *iter.next().unwrap()
727
362951980
}
728

            
729
1088703
fn take_two(iter: &mut slice::Iter<'_, f64>) -> (f64, f64) {
730
1088703
    (take_one(iter), take_one(iter))
731
1088703
}
732

            
733
#[cfg(test)]
734
mod tests {
735
    use super::*;
736

            
737
    #[test]
738
1
    fn empty_builder() {
739
1
        let builder = PathBuilder::default();
740
1
        let path = builder.into_path();
741
1
        assert!(path.is_empty());
742
1
        assert_eq!(path.iter().count(), 0);
743
1
    }
744

            
745
    #[test]
746
1
    fn empty_path() {
747
1
        let path = Path::default();
748
1
        assert!(path.is_empty());
749
1
        assert_eq!(path.iter().count(), 0);
750
1
    }
751

            
752
    #[test]
753
1
    fn all_commands() {
754
1
        let mut builder = PathBuilder::default();
755
1
        builder.move_to(42.0, 43.0);
756
1
        builder.line_to(42.0, 43.0);
757
1
        builder.curve_to(42.0, 43.0, 44.0, 45.0, 46.0, 47.0);
758
1
        builder.arc(
759
1
            42.0,
760
1
            43.0,
761
1
            44.0,
762
1
            45.0,
763
1
            46.0,
764
1
            LargeArc(true),
765
1
            Sweep::Positive,
766
1
            47.0,
767
1
            48.0,
768
1
        );
769
1
        builder.close_path();
770
1
        let path = builder.into_path();
771
1
        assert!(path.iter().eq(vec![
772
1
            PathCommand::MoveTo(42.0, 43.0),
773
1
            PathCommand::LineTo(42.0, 43.0),
774
1
            PathCommand::CurveTo(CubicBezierCurve {
775
1
                pt1: (42.0, 43.0),
776
1
                pt2: (44.0, 45.0),
777
1
                to: (46.0, 47.0),
778
1
            }),
779
1
            PathCommand::Arc(EllipticalArc {
780
1
                from: (42.0, 43.0),
781
1
                r: (44.0, 45.0),
782
1
                to: (47.0, 48.0),
783
1
                x_axis_rotation: 46.0,
784
1
                large_arc: LargeArc(true),
785
1
                sweep: Sweep::Positive,
786
1
            }),
787
1
            PathCommand::ClosePath,
788
1
        ]));
789
1
    }
790

            
791
    #[test]
792
1
    fn subpath_iter() {
793
1
        let mut builder = PathBuilder::default();
794
1
        builder.move_to(42.0, 43.0);
795
1
        builder.line_to(42.0, 43.0);
796
1
        builder.close_path();
797
1

            
798
1
        builder.move_to(22.0, 22.0);
799
1
        builder.curve_to(22.0, 22.0, 44.0, 45.0, 46.0, 47.0);
800
1

            
801
1
        builder.move_to(69.0, 69.0);
802
1
        builder.line_to(42.0, 43.0);
803
1
        let path = builder.into_path();
804
1

            
805
1
        let subpaths = path
806
1
            .iter_subpath()
807
3
            .map(|subpath| {
808
3
                (
809
3
                    subpath.origin(),
810
3
                    subpath.iter_commands().collect::<Vec<PathCommand>>(),
811
3
                )
812
3
            })
813
1
            .collect::<Vec<((f64, f64), Vec<PathCommand>)>>();
814
1

            
815
1
        assert_eq!(
816
1
            subpaths,
817
1
            vec![
818
1
                (
819
1
                    (42.0, 43.0),
820
1
                    vec![
821
1
                        PathCommand::MoveTo(42.0, 43.0),
822
1
                        PathCommand::LineTo(42.0, 43.0),
823
1
                        PathCommand::ClosePath
824
1
                    ]
825
1
                ),
826
1
                (
827
1
                    (22.0, 22.0),
828
1
                    vec![
829
1
                        PathCommand::MoveTo(22.0, 22.0),
830
1
                        PathCommand::CurveTo(CubicBezierCurve {
831
1
                            pt1: (22.0, 22.0),
832
1
                            pt2: (44.0, 45.0),
833
1
                            to: (46.0, 47.0)
834
1
                        })
835
1
                    ]
836
1
                ),
837
1
                (
838
1
                    (69.0, 69.0),
839
1
                    vec![
840
1
                        PathCommand::MoveTo(69.0, 69.0),
841
1
                        PathCommand::LineTo(42.0, 43.0)
842
1
                    ]
843
1
                )
844
1
            ]
845
1
        );
846
1
    }
847

            
848
    #[test]
849
1
    fn zero_length_subpaths() {
850
1
        let mut builder = PathBuilder::default();
851
1
        builder.move_to(42.0, 43.0);
852
1
        builder.move_to(44.0, 45.0);
853
1
        builder.close_path();
854
1
        builder.move_to(46.0, 47.0);
855
1
        builder.line_to(48.0, 49.0);
856
1

            
857
1
        let path = builder.into_path();
858
1

            
859
1
        let subpaths = path
860
1
            .iter_subpath()
861
3
            .map(|subpath| (subpath.is_zero_length(), subpath.origin()))
862
1
            .collect::<Vec<(bool, (f64, f64))>>();
863
1

            
864
1
        assert_eq!(
865
1
            subpaths,
866
1
            vec![
867
1
                (true, (42.0, 43.0)),
868
1
                (true, (44.0, 45.0)),
869
1
                (false, (46.0, 47.0)),
870
1
            ]
871
1
        );
872
1
    }
873
}