zeno/
path_data.rs

1//! Path data.
2
3use super::command::{Command, PointsCommands, Verb};
4use super::geometry::{Point, Transform};
5use super::path_builder::PathBuilder;
6use super::segment::segments;
7use super::svg_parser::SvgCommands;
8
9#[cfg(feature = "eval")]
10use super::stroke::stroke_into;
11
12#[cfg(feature = "eval")]
13use super::style::*;
14
15#[cfg(feature = "eval")]
16use super::geometry::{Bounds, BoundsBuilder};
17
18#[cfg(feature = "eval")]
19use super::path_builder::TransformSink;
20
21use crate::lib::Vec;
22
23/// Trait for types that represent path data.
24///
25/// A primary design goal for this crate is to be agnostic with regard to
26/// storage of path data. This trait provides the abstraction to make that
27/// possible.
28///
29/// All path data is consumed internally as an iterator over path
30/// [commands](Command) and as such, this trait is similar to
31/// the `IntoIterator` trait, but restricted to iterators of commands and
32/// without consuming itself.
33///
34/// Implementations of this trait are provided for SVG path data (in the form
35/// of strings), slices/vectors of commands, and the common point and
36/// verb list structure (as the tuple `(&[Point], &[Verb])`).
37///
38/// As such, these paths are all equivalent:
39///
40/// ```rust
41/// use zeno::{Command, PathData, Point, Verb};
42///
43/// // SVG path data
44/// let path1 = "M1,2 L3,4";
45///
46/// // Slice of commands
47/// let path2 = &[
48///     Command::MoveTo(Point::new(1.0, 2.0)),
49///     Command::LineTo(Point::new(3.0, 4.0)),
50/// ][..];
51///
52/// // Tuple of slices to points and verbs
53/// let path3 = (
54///     &[Point::new(1.0, 2.0), Point::new(3.0, 4.0)][..],
55///     &[Verb::MoveTo, Verb::LineTo][..],
56/// );
57///
58/// assert!(path1.commands().eq(path2.commands()));
59/// assert!(path2.commands().eq(path3.commands()));
60/// ```
61///
62/// Implementing `PathData` is similar to implementing `IntoIterator`:
63///
64/// ```rust
65/// use zeno::{Command, PathData};
66///
67/// pub struct MyPath {
68///     data: Vec<Command>
69/// }
70///
71/// impl<'a> PathData for &'a MyPath {
72///     // Copied here because PathData expects Commands by value
73///     type Commands = std::iter::Copied<std::slice::Iter<'a, Command>>;
74///
75///     fn commands(&self) -> Self::Commands {
76///         self.data.iter().copied()
77///     }
78/// }
79/// ```
80///
81/// The provided `copy_into()` method evaluates the command iterator and
82/// submits the commands to a sink. You should also implement this if you
83/// have a more direct method of dispatching to a sink as rasterizer
84/// performance can be sensitive to latencies here.
85pub trait PathData {
86    /// Command iterator.
87    type Commands: Iterator<Item = Command> + Clone;
88
89    /// Returns an iterator over the commands described by the path data.
90    fn commands(&self) -> Self::Commands;
91
92    /// Copies the path data into the specified sink.
93    fn copy_to(&self, sink: &mut impl PathBuilder) {
94        for cmd in self.commands() {
95            use Command::*;
96            match cmd {
97                MoveTo(p) => sink.move_to(p),
98                LineTo(p) => sink.line_to(p),
99                QuadTo(c, p) => sink.quad_to(c, p),
100                CurveTo(c1, c2, p) => sink.curve_to(c1, c2, p),
101                Close => sink.close(),
102            };
103        }
104    }
105}
106
107/// Computes the total length of the path.
108pub fn length(data: impl PathData, transform: Option<Transform>) -> f32 {
109    let data = data.commands();
110    let mut length = 0.;
111    if let Some(transform) = transform {
112        for s in segments(data.map(|cmd| cmd.transform(&transform)), false) {
113            length += s.length();
114        }
115    } else {
116        for s in segments(data, false) {
117            length += s.length();
118        }
119    }
120    length
121}
122
123/// Computes the bounding box of the path.
124#[cfg(feature = "eval")]
125pub fn bounds<'a>(
126    data: impl PathData,
127    style: impl Into<Style<'a>>,
128    transform: Option<Transform>,
129) -> Bounds {
130    let style = style.into();
131    let mut bounds = BoundsBuilder::new();
132    apply(data, style, transform, &mut bounds);
133    bounds.build()
134}
135
136/// Applies the style and transform to the path and emits the result to the
137/// specified sink.
138#[cfg(feature = "eval")]
139pub fn apply<'a>(
140    data: impl PathData,
141    style: impl Into<Style<'a>>,
142    transform: Option<Transform>,
143    sink: &mut impl PathBuilder,
144) -> Fill {
145    let style = style.into();
146    match style {
147        Style::Fill(fill) => {
148            if let Some(transform) = transform {
149                let mut transform_sink = TransformSink { sink, transform };
150                data.copy_to(&mut transform_sink);
151                fill
152            } else {
153                data.copy_to(sink);
154                fill
155            }
156        }
157        Style::Stroke(stroke) => {
158            if let Some(transform) = transform {
159                if stroke.scale {
160                    let mut transform_sink = TransformSink { sink, transform };
161                    stroke_into(data.commands(), &stroke, &mut transform_sink);
162                } else {
163                    stroke_into(
164                        data.commands().map(|cmd| cmd.transform(&transform)),
165                        &stroke,
166                        sink,
167                    );
168                }
169            } else {
170                stroke_into(data.commands(), &stroke, sink);
171            }
172            Fill::NonZero
173        }
174    }
175}
176
177impl<T> PathData for &'_ T
178where
179    T: PathData,
180{
181    type Commands = T::Commands;
182
183    fn commands(&self) -> Self::Commands {
184        T::commands(*self)
185    }
186
187    #[inline(always)]
188    fn copy_to(&self, sink: &mut impl PathBuilder) {
189        T::copy_to(*self, sink)
190    }
191}
192
193impl<'a> PathData for &'a str {
194    type Commands = SvgCommands<'a>;
195
196    fn commands(&self) -> Self::Commands {
197        SvgCommands::new(self)
198    }
199}
200
201impl<'a> PathData for (&'a [Point], &'a [Verb]) {
202    type Commands = PointsCommands<'a>;
203
204    fn commands(&self) -> Self::Commands {
205        PointsCommands::new(self.0, self.1)
206    }
207
208    #[inline(always)]
209    fn copy_to(&self, sink: &mut impl PathBuilder) {
210        self.commands().copy_to(sink);
211    }
212}
213
214impl<'a> PathData for &'a [Command] {
215    type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;
216
217    fn commands(&self) -> Self::Commands {
218        self.iter().copied()
219    }
220}
221
222impl<'a> PathData for &'a Vec<Command> {
223    type Commands = core::iter::Copied<core::slice::Iter<'a, Command>>;
224
225    fn commands(&self) -> Self::Commands {
226        self.iter().copied()
227    }
228}