zeno/
path_data.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Path data.

use super::command::{Command, PointsCommands, Verb};
use super::geometry::{Point, Transform};
use super::path_builder::PathBuilder;
use super::segment::segments;
use super::svg_parser::SvgCommands;

#[cfg(feature = "eval")]
use super::stroke::stroke_into;

#[cfg(feature = "eval")]
use super::style::*;

#[cfg(feature = "eval")]
use super::geometry::{Bounds, BoundsBuilder};

#[cfg(feature = "eval")]
use super::path_builder::TransformSink;

use crate::lib::Vec;

/// Trait for types that represent path data.
///
/// A primary design goal for this crate is to be agnostic with regard to
/// storage of path data. This trait provides the abstraction to make that
/// possible.
///
/// All path data is consumed internally as an iterator over path
/// [commands](enum.Command.html) and as such, this trait is similar to
/// the IntoIterator trait, but restricted to iterators of commands and
/// without consuming itself.
///
/// Implementations of this trait are provided for SVG path data (in the form
/// of strings), slices/vectors of commands, and the common point and
/// verb list structure (as the tuple `(&[Point], &[Verb])`).
///
/// As such, these paths are all equivalent:
///
/// ```rust
/// use zeno::{Command, PathData, Point, Verb};
///
/// // SVG path data
/// let path1 = "M1,2 L3,4";
///
/// // Slice of commands
/// let path2 = &[
///     Command::MoveTo(Point::new(1.0, 2.0)),
///     Command::LineTo(Point::new(3.0, 4.0)),
/// ][..];
///
/// // Tuple of slices to points and verbs
/// let path3 = (
///     &[Point::new(1.0, 2.0), Point::new(3.0, 4.0)][..],
///     &[Verb::MoveTo, Verb::LineTo][..],
/// );
///
/// assert!(path1.commands().eq(path2.commands()));
/// assert!(path2.commands().eq(path3.commands()));
/// ```
///
/// Implementing PathData is similar to implementing IntoIterator:
///
/// ```rust
/// use zeno::{Command, PathData};
///
/// pub struct MyPath {
///     data: Vec<Command>
/// }
///
/// impl<'a> PathData for &'a MyPath {
///     // Copied here because PathData expects Commands by value
///     type Commands = std::iter::Copied<std::slice::Iter<'a, Command>>;
///
///     fn commands(&self) -> Self::Commands {
///         self.data.iter().copied()
///     }
/// }
/// ```
///
/// The provided copy_into() method evaluates the command iterator and
/// submits the commands to a sink. You should also implement this if you
/// have a more direct method of dispatching to a sink as rasterizer
/// performance can be sensitive to latencies here.
pub trait PathData {
    /// Command iterator.
    type Commands: Iterator<Item = Command> + Clone;

    /// Returns an iterator over the commands described by the path data.
    fn commands(&self) -> Self::Commands;

    /// Copies the path data into the specified sink.
    fn copy_to(&self, sink: &mut impl PathBuilder) {
        for cmd in self.commands() {
            use Command::*;
            match cmd {
                MoveTo(p) => sink.move_to(p),
                LineTo(p) => sink.line_to(p),
                QuadTo(c, p) => sink.quad_to(c, p),
                CurveTo(c1, c2, p) => sink.curve_to(c1, c2, p),
                Close => sink.close(),
            };
        }
    }
}

/// Computes the total length of the path.
pub fn length(data: impl PathData, transform: Option<Transform>) -> f32 {
    let data = data.commands();
    let mut length = 0.;
    if let Some(transform) = transform {
        for s in segments(data.map(|cmd| cmd.transform(&transform)), false) {
            length += s.length();
        }
    } else {
        for s in segments(data, false) {
            length += s.length();
        }
    }
    length
}

/// Computes the bounding box of the path.
#[cfg(feature = "eval")]
pub fn bounds<'a>(
    data: impl PathData,
    style: impl Into<Style<'a>>,
    transform: Option<Transform>,
) -> Bounds {
    let style = style.into();
    let mut bounds = BoundsBuilder::new();
    apply(data, style, transform, &mut bounds);
    bounds.build()
}

/// Applies the style and transform to the path and emits the result to the
/// specified sink.
#[cfg(feature = "eval")]
pub fn apply<'a>(
    data: impl PathData,
    style: impl Into<Style<'a>>,
    transform: Option<Transform>,
    sink: &mut impl PathBuilder,
) -> Fill {
    let style = style.into();
    match style {
        Style::Fill(fill) => {
            if let Some(transform) = transform {
                let mut transform_sink = TransformSink { sink, transform };
                data.copy_to(&mut transform_sink);
                fill
            } else {
                data.copy_to(sink);
                fill
            }
        }
        Style::Stroke(stroke) => {
            if let Some(transform) = transform {
                if stroke.scale {
                    let mut transform_sink = TransformSink { sink, transform };
                    stroke_into(data.commands(), &stroke, &mut transform_sink);
                } else {
                    stroke_into(
                        data.commands().map(|cmd| cmd.transform(&transform)),
                        &stroke,
                        sink,
                    );
                }
            } else {
                stroke_into(data.commands(), &stroke, sink);
            }
            Fill::NonZero
        }
    }
}

impl<T> PathData for &'_ T
where
    T: PathData,
{
    type Commands = T::Commands;

    fn commands(&self) -> Self::Commands {
        T::commands(*self)
    }

    #[inline(always)]
    fn copy_to(&self, sink: &mut impl PathBuilder) {
        T::copy_to(*self, sink)
    }
}

impl<'a> PathData for &'a str {
    type Commands = SvgCommands<'a>;

    fn commands(&self) -> Self::Commands {
        SvgCommands::new(self)
    }
}

impl<'a> PathData for (&'a [Point], &'a [Verb]) {
    type Commands = PointsCommands<'a>;

    fn commands(&self) -> Self::Commands {
        PointsCommands::new(self.0, self.1)
    }

    #[inline(always)]
    fn copy_to(&self, sink: &mut impl PathBuilder) {
        self.commands().copy_to(sink);
    }
}

impl<'a> PathData for &'a [Command] {
    type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;

    fn commands(&self) -> Self::Commands {
        self.iter().copied()
    }
}

impl<'a> PathData for &'a Vec<Command> {
    type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;

    fn commands(&self) -> Self::Commands {
        self.iter().copied()
    }
}