flo_canvas/conversion_streams/
path_stream.rs

1use crate::draw::*;
2use crate::path::*;
3use crate::color::*;
4use crate::gradient::*;
5use crate::transform2d::*;
6
7use flo_stream::*;
8use flo_curves::geo::*;
9use flo_curves::bezier::path::*;
10
11use futures::prelude::*;
12
13use std::mem;
14
15///
16/// Attributes used to render a bezier path
17///
18#[derive(Copy, Clone, Debug)]
19pub enum PathAttribute {
20    /// Path is drawn as a stroke with the specified width and colour
21    Stroke(f32, Color),
22
23    /// Path is drawn as a stroke with the specified pixel width and colour
24    StrokePixels(f32, Color),
25
26    /// Path is filled with the specified colour
27    Fill(Color),
28
29    /// Path is filled with the specified texture
30    FillTexture(TextureId, (f32, f32), (f32, f32), Option<Transform2D>),
31
32    /// Path is filled with the specified gradient
33    FillGradient(GradientId, (f32, f32), (f32, f32), Option<Transform2D>)
34}
35
36///
37/// Converts a stream of drawing instructions into a stream of bezier paths (stripping any other attributes from the stream)
38///
39/// This can generate any path structure that implements the `BezierPathFactory` trait as its result. Bezier paths can't
40/// contain 'move' instructions, so the result is a list of path segments that make up the overall path.
41///
42/// This will return a list of all the paths defined in the specified stream, regardless of if they're actually drawn or
43/// used as clipping paths or anything else.
44///
45pub fn drawing_to_paths<BezierPath, InStream>(draw_stream: InStream) -> impl Send+Unpin+Stream<Item=Vec<BezierPath>>
46where
47InStream:           'static+Send+Unpin+Stream<Item=Draw>,
48BezierPath:         'static+Send+BezierPathFactory,
49BezierPath::Point:  Send+Coordinate2D {
50    generator_stream(move |yield_value| async move {
51        use self::PathOp::*;
52
53        // The path that is currently being created by the path builder (or 'None' if no path has been generated yet)
54        let mut current_path: Option<BezierPathBuilder<_>>  = None;
55        let mut current_components                          = vec![];
56        let mut start_point                                 = None;
57
58        // Read to the end of the stream
59        let mut draw_stream                                 = draw_stream;
60
61        while let Some(draw) = draw_stream.next().await {
62            match draw {
63                Draw::Path(NewPath)                                             => { 
64                    // Add the last path to the list of path components
65                    if let Some(path) = current_path.take() {
66                        current_components.push(path.build());
67                    }
68
69                    // If there are any components to the current path, return them
70                    if current_components.len() > 0 {
71                        let mut next_path = vec![];
72                        mem::swap(&mut next_path, &mut current_components);
73
74                        yield_value(next_path).await;
75                    }
76                }
77
78                Draw::Path(Move(x, y))                                          => {
79                    // Finish the current path if there is one
80                    if let Some(path) = current_path.take() {
81                        current_components.push(path.build());
82                    }
83
84                    // Start a new path
85                    current_path = Some(BezierPathBuilder::start(BezierPath::Point::from_components(&[x as _, y as _])));
86                    start_point  = Some(BezierPath::Point::from_components(&[x as _, y as _]));
87                }
88
89                Draw::Path(Line(x, y))                                          => {
90                    // Add the line to the current path
91                    current_path = current_path.map(|current_path| {
92                        current_path.line_to(BezierPath::Point::from_components(&[x as _, y as _]))
93                    });
94                }
95
96                Draw::Path(BezierCurve(((cp1x, cp1y), (cp2x, cp2y)), (x1, y1))) => {
97                    // Add the curve to the current path
98                    current_path = current_path.map(|current_path| {
99                        current_path.curve_to(
100                            (BezierPath::Point::from_components(&[cp1x as _, cp1y as _]), BezierPath::Point::from_components(&[cp2x as _, cp2y as _])),
101                            BezierPath::Point::from_components(&[x1 as _, y1 as _])
102                        )
103                    });
104                }
105
106                Draw::Path(ClosePath)                                           => {
107                    if let Some(start_point) = &start_point {
108                        current_path = current_path.map(|current_path| {
109                            current_path.line_to(start_point.clone())
110                        })
111                    }
112                }
113
114                // Ignore other instructions
115                _ => { }
116            }
117        }
118
119        // Return the last path once the stream has finished
120        if let Some(path) = current_path.take() {
121            current_components.push(path.build());
122        }
123
124        // If there are any components to the current path, return them
125        if current_components.len() > 0 {
126            let mut next_path = vec![];
127            mem::swap(&mut next_path, &mut current_components);
128
129            yield_value(next_path).await;
130        }
131    })
132}
133///
134/// Converts a stream of drawing instructions into a stream of bezier paths with attributes that specify how they're rendered.
135///
136/// This can generate any path structure that implements the `BezierPathFactory` trait as its result. Bezier paths can't
137/// contain 'move' instructions, so the result is a list of path segments that make up the overall path.
138///
139pub fn drawing_to_attributed_paths<BezierPath, InStream>(draw_stream: InStream) -> impl Send+Unpin+Stream<Item=(Vec<PathAttribute>, Vec<BezierPath>)>
140where
141InStream:           'static+Send+Unpin+Stream<Item=Draw>,
142BezierPath:         'static+Send+BezierPathFactory,
143BezierPath::Point:  Send+Coordinate2D {
144    generator_stream(move |yield_value| async move {
145        use self::PathOp::*;
146
147        // The path that is currently being created by the path builder (or 'None' if no path has been generated yet)
148        let mut current_path: Option<BezierPathBuilder<_>>  = None;
149        let mut current_components                          = vec![];
150        let mut current_attributes                          = vec![];
151        let mut start_point                                 = None;
152
153        let mut fill_color                                  = PathAttribute::Fill(Color::Rgba(0.0, 0.0, 0.0, 1.0));
154        let mut stroke_color                                = Color::Rgba(0.0, 0.0, 0.0, 1.0);
155        let mut line_width                                  = None;
156        let mut line_width_pixels                           = None;
157
158        let mut state_stack                                 = vec![];
159
160        // Read to the end of the stream
161        let mut draw_stream                                 = draw_stream;
162
163        while let Some(draw) = draw_stream.next().await {
164            match draw {
165                Draw::Path(NewPath)                                             => { 
166                    // Add the last path to the list of path components
167                    if let Some(path) = current_path.take() {
168                        current_components.push(path.build());
169                    }
170
171                    // If there are any components to the current path, return them
172                    if current_components.len() > 0 && current_attributes.len() > 0 {
173                        let next_path       = mem::take(&mut current_components);
174                        let next_attributes = mem::take(&mut current_attributes);
175
176                        yield_value((next_attributes, next_path)).await;
177                    }
178
179                    current_attributes = vec![];
180                    current_components = vec![];
181                }
182
183                Draw::Path(Move(x, y))                                          => {
184                    // Finish the current path if there is one
185                    if let Some(path) = current_path.take() {
186                        current_components.push(path.build());
187                    }
188
189                    // Start a new path
190                    current_path = Some(BezierPathBuilder::start(BezierPath::Point::from_components(&[x as _, y as _])));
191                    start_point  = Some(BezierPath::Point::from_components(&[x as _, y as _]));
192                }
193
194                Draw::Path(Line(x, y))                                          => {
195                    // Add the line to the current path
196                    current_path = current_path.map(|current_path| {
197                        current_path.line_to(BezierPath::Point::from_components(&[x as _, y as _]))
198                    });
199                }
200
201                Draw::Path(BezierCurve(((cp1x, cp1y), (cp2x, cp2y)), (x1, y1))) => {
202                    // Add the curve to the current path
203                    current_path = current_path.map(|current_path| {
204                        current_path.curve_to(
205                            (BezierPath::Point::from_components(&[cp1x as _, cp1y as _]), BezierPath::Point::from_components(&[cp2x as _, cp2y as _])),
206                            BezierPath::Point::from_components(&[x1 as _, y1 as _])
207                        )
208                    });
209                }
210
211                Draw::Path(ClosePath)                                           => {
212                    if let Some(start_point) = &start_point {
213                        current_path = current_path.map(|current_path| {
214                            current_path.line_to(start_point.clone())
215                        })
216                    }
217                }
218
219                Draw::FillColor(new_fill_color)                                 => {
220                    fill_color = PathAttribute::Fill(new_fill_color);
221                }
222
223                Draw::FillGradient(gradient, (x1, y1), (x2, y2))                => {
224                    fill_color = PathAttribute::FillGradient(gradient, (x1, y1), (x2, y2), None);
225                }
226
227                Draw::FillTexture(texture, (x1, y1), (x2, y2))                  => {
228                    fill_color = PathAttribute::FillTexture(texture, (x1, y1), (x2, y2), None);
229                }
230
231                Draw::FillTransform(transform)                                  => {
232                    fill_color = match fill_color {
233                        PathAttribute::FillGradient(gradient, coord1, coord2, None)                     => PathAttribute::FillGradient(gradient, coord1, coord2, Some(transform)),
234                        PathAttribute::FillTexture(texture, coord1, coord2, None)                       => PathAttribute::FillTexture(texture, coord1, coord2, Some(transform)),
235                        PathAttribute::FillGradient(gradient, coord1, coord2, Some(existing_transform)) => PathAttribute::FillGradient(gradient, coord1, coord2, Some(existing_transform * transform)),
236                        PathAttribute::FillTexture(texture, coord1, coord2, Some(existing_transform))   => PathAttribute::FillTexture(texture, coord1, coord2, Some(existing_transform * transform)),
237
238                        other_fill_color                                                                => other_fill_color
239                    };
240                }
241
242                Draw::StrokeColor(new_stroke_color)                             => {
243                    stroke_color = new_stroke_color;
244                }
245
246                Draw::LineWidth(new_line_width)                                 => {
247                    line_width_pixels   = None;
248                    line_width          = Some(new_line_width);
249                }
250
251                Draw::LineWidthPixels(new_line_width)                           => {
252                    line_width_pixels   = Some(new_line_width);
253                    line_width          = None;
254                }
255
256                Draw::PushState                                                 => {
257                    state_stack.push((fill_color, stroke_color, line_width, line_width_pixels));
258                }
259
260                Draw::PopState                                                  => {
261                    if let Some((new_fill_color, new_stroke_color, new_line_width, new_line_width_pixels)) = state_stack.pop() {
262                        fill_color          = new_fill_color;
263                        stroke_color        = new_stroke_color;
264                        line_width          = new_line_width;
265                        line_width_pixels   = new_line_width_pixels;
266                    }
267                }
268
269                Draw::Fill                                                      => {
270                    current_attributes.push(fill_color);
271                }
272
273                Draw::Stroke                                                    => {
274                    if let Some(line_width) = line_width {
275                        current_attributes.push(PathAttribute::Stroke(line_width, stroke_color));
276                    }
277                    if let Some(line_width_pixels) = line_width_pixels {
278                        current_attributes.push(PathAttribute::StrokePixels(line_width_pixels, stroke_color));
279                    }
280                }
281
282                // Ignore other instructions
283                _                                                               => { }
284            }
285        }
286
287        // Return the last path once the stream has finished
288        if let Some(path) = current_path.take() {
289            current_components.push(path.build());
290        }
291
292        // If there are any components to the current path, return them
293        if current_components.len() > 0 && current_attributes.len() > 0 {
294            let next_path       = mem::take(&mut current_components);
295            let next_attributes = mem::take(&mut current_attributes);
296
297            yield_value((next_attributes, next_path)).await;
298        }
299    })
300}
301
302#[cfg(test)]
303mod test {
304    use super::*;
305    use futures::stream;
306    use futures::executor;
307
308    #[test]
309    pub fn square_path() {
310        executor::block_on(async {
311            // Describe a square
312            let square          = vec![
313                Draw::Path(PathOp::NewPath),
314                Draw::Path(PathOp::Move(0.0, 0.0)), 
315                Draw::Path(PathOp::Line(100.0, 0.0)), 
316                Draw::Path(PathOp::Line(100.0, 100.0)), 
317                Draw::Path(PathOp::Line(0.0, 100.0)), 
318                Draw::Path(PathOp::ClosePath)
319            ];
320
321            // Stream it through drawing_to_paths
322            let square_stream   = stream::iter(square);
323            let path_stream     = drawing_to_paths::<SimpleBezierPath, _>(square_stream);
324            
325            // Collect the paths that result
326            let paths           = path_stream.collect::<Vec<_>>().await;
327
328            // Should contain our square
329            assert!(paths.len() == 1);
330            assert!(paths[0].len() == 1);
331
332            let (start, curves) = &paths[0][0];
333
334            assert!(start == &Coord2(0.0, 0.0));
335            assert!(curves[0].2 == Coord2(100.0, 0.0));
336            assert!(curves[1].2 == Coord2(100.0, 100.0));
337            assert!(curves[2].2 == Coord2(0.0, 100.0));
338            assert!(curves[3].2 == Coord2(0.0, 0.0));
339
340            assert!(curves.len() == 4);
341        });
342    }
343}