Skip to main content

ferroui_vello/
stream_geometry_builder.rs

1use crate::geometry_impl::{try_get_geometry_impl, FillPath, VelloPath};
2use crate::simple_geometry_impl::SimpleGeometryImpl;
3use crate::vello_extensions::{to_kurbo_point, PATH_TOLERANCE};
4use ferroui_base::media::{FillRule, SweepDirection};
5use ferroui_base::platform::{IGeometryContext, IGeometryImpl, IStreamGeometryBuilder};
6use ferroui_base::{Point, Size};
7use kurbo::{BezPath, SvgArc, Vec2};
8use std::sync::Arc;
9
10/// A path under construction with the rule it will be filled by.
11///
12/// The commands are those of the path builder of Skia, with its rules for a
13/// command that has no point to start from: it starts at the origin.
14#[derive(Clone)]
15struct PathBuilder {
16    path: BezPath,
17    fill_rule: FillRule,
18}
19
20impl PathBuilder {
21    /// A builder of an empty path, filled by the non-zero rule.
22    fn new() -> Self {
23        Self { path: BezPath::new(), fill_rule: FillRule::NonZero }
24    }
25
26    fn from_path(path: &VelloPath) -> Self {
27        Self { path: path.path().clone(), fill_rule: path.fill_rule() }
28    }
29
30    fn snapshot(&self) -> VelloPath {
31        VelloPath::new(self.path.clone(), self.fill_rule)
32    }
33
34    /// The point the next command starts from.
35    fn current_point(&mut self) -> kurbo::Point {
36        // (A path of kurbo is "empty" until it has a segment: the elements
37        // are what tells whether a figure was begun.)
38        if self.path.elements().is_empty() {
39            self.path.move_to((0.0, 0.0));
40        }
41        self.path.current_position().unwrap_or_default()
42    }
43
44    fn move_to(&mut self, point: kurbo::Point) {
45        self.path.move_to(point);
46    }
47
48    fn line_to(&mut self, point: kurbo::Point) {
49        self.current_point();
50        self.path.line_to(point);
51    }
52
53    fn quad_to(&mut self, p1: kurbo::Point, p2: kurbo::Point) {
54        self.current_point();
55        self.path.quad_to(p1, p2);
56    }
57
58    fn cubic_to(&mut self, p1: kurbo::Point, p2: kurbo::Point, p3: kurbo::Point) {
59        self.current_point();
60        self.path.curve_to(p1, p2, p3);
61    }
62
63    fn close(&mut self) {
64        if !self.path.elements().is_empty() {
65            self.path.close_path();
66        }
67    }
68
69    /// Adds an elliptical arc from the current point to `end`, with the
70    /// endpoint parameters of an arc of SVG (which the contract describes):
71    /// radii that are too small to reach the end point are scaled up, and
72    /// an arc without a radius or without a length is a line to the end
73    /// point. The arc is added as cubic Béziers.
74    fn arc_to(&mut self, radii: Vec2, rotation_angle: f64, is_large_arc: bool, clockwise: bool, end: kurbo::Point) {
75        let from = self.current_point();
76        let arc = SvgArc {
77            from,
78            to: end,
79            radii,
80            x_rotation: rotation_angle.to_radians(),
81            large_arc: is_large_arc,
82            // The sweep flag of SVG: the arc is drawn in the direction of
83            // growing angles, which is clockwise with the y axis down.
84            sweep: clockwise,
85        };
86
87        match kurbo::Arc::from_svg_arc(&arc) {
88            Some(arc) => {
89                let mut curves = Vec::new();
90                arc.to_cubic_beziers(PATH_TOLERANCE, |p1, p2, p3| curves.push((p1, p2, p3)));
91                // The last Bézier ends at the end of the arc as it was
92                // computed; the figure continues from the end point as it
93                // was given.
94                if let Some(last) = curves.last_mut() {
95                    last.2 = end;
96                }
97                for (p1, p2, p3) in curves {
98                    self.path.curve_to(p1, p2, p3);
99                }
100            }
101            None => self.path.line_to(end),
102        }
103    }
104}
105
106/// The fill being built by a [`StreamGeometryBuilder`].
107enum FillBuilder {
108    /// No fill path exists yet.
109    None,
110    /// The fill is the stroke.
111    SameAsStroke,
112    /// The fill is built separately from the stroke.
113    Separate(PathBuilder),
114}
115
116/// Builds a kurbo geometry from drawing commands.
117///
118/// The paths of a geometry are immutable, so the builder works on path
119/// builders seeded from the paths of its source geometry, and creates the
120/// geometry from their snapshots.
121pub struct StreamGeometryBuilder {
122    stroke: PathBuilder,
123    fill: FillBuilder,
124    is_filled: bool,
125    start_point: Point,
126    is_figure_broken: bool,
127    built: bool,
128}
129
130impl StreamGeometryBuilder {
131    /// Creates a builder that starts with the figures of `source`, a geometry
132    /// of this backend, or with an empty geometry when `source` is `None`.
133    ///
134    /// Panics if `source` was not created by the Vello render interface.
135    pub fn new(source: Option<&dyn IGeometryImpl>) -> Self {
136        let (stroke, fill) = match source {
137            None => (PathBuilder::from_path(&VelloPath::empty()), FillBuilder::SameAsStroke),
138            Some(source) => {
139                let geometry = try_get_geometry_impl(source)
140                    .unwrap_or_else(|| panic!("The geometry wasn't created by the Vello render interface."));
141                let stroke_path = geometry.stroke_path();
142                let fill = match geometry.fill() {
143                    FillPath::SameAsStroke => FillBuilder::SameAsStroke,
144                    FillPath::Separate(fill_path) => FillBuilder::Separate(PathBuilder::from_path(&fill_path)),
145                    // Without a stroke path either, the fill is the (empty) stroke.
146                    FillPath::None if stroke_path.is_none() => FillBuilder::SameAsStroke,
147                    FillPath::None => FillBuilder::None,
148                };
149                let stroke = PathBuilder::from_path(&stroke_path.unwrap_or_else(VelloPath::empty));
150                (stroke, fill)
151            }
152        };
153
154        Self { stroke, fill, is_filled: false, start_point: Point::default(), is_figure_broken: false, built: false }
155    }
156
157    /// The fill builder, created on first use.
158    fn fill(&mut self) -> &mut PathBuilder {
159        if matches!(self.fill, FillBuilder::None) {
160            self.fill = FillBuilder::Separate(PathBuilder::new());
161        }
162
163        match &mut self.fill {
164            FillBuilder::SameAsStroke => &mut self.stroke,
165            FillBuilder::Separate(fill) => fill,
166            FillBuilder::None => unreachable!("the fill builder was just created"),
167        }
168    }
169
170    /// Whether commands have to be repeated on a separate fill path.
171    fn duplicate(&self) -> bool {
172        self.is_filled && !matches!(self.fill, FillBuilder::SameAsStroke)
173    }
174
175    fn ensure_separate_fill_path(&mut self) {
176        if matches!(self.fill, FillBuilder::SameAsStroke) {
177            self.fill = FillBuilder::Separate(self.stroke.clone());
178        }
179    }
180
181    fn break_figure(&mut self) {
182        if !self.is_figure_broken {
183            self.is_figure_broken = true;
184            self.ensure_separate_fill_path();
185        }
186    }
187}
188
189impl IStreamGeometryBuilder for StreamGeometryBuilder {
190    fn to_geometry(&mut self) -> Arc<dyn IGeometryImpl> {
191        if self.built {
192            panic!("The geometry has already been built.");
193        }
194
195        self.built = true;
196
197        let stroke = self.stroke.snapshot();
198        let fill = match &self.fill {
199            FillBuilder::None => FillPath::None,
200            FillBuilder::SameAsStroke => FillPath::SameAsStroke,
201            FillBuilder::Separate(fill) => FillPath::Separate(fill.snapshot()),
202        };
203
204        SimpleGeometryImpl::new(stroke, fill, None)
205    }
206}
207
208impl IGeometryContext for StreamGeometryBuilder {
209    fn arc_to(
210        &mut self,
211        point: Point,
212        size: Size,
213        rotation_angle: f64,
214        is_large_arc: bool,
215        sweep_direction: SweepDirection,
216        is_stroked: bool,
217    ) {
218        let clockwise = sweep_direction == SweepDirection::Clockwise;
219        let radii = Vec2::new(size.width, size.height);
220        let end = to_kurbo_point(point);
221
222        if is_stroked {
223            self.stroke.arc_to(radii, rotation_angle, is_large_arc, clockwise, end);
224        } else {
225            self.break_figure();
226            self.stroke.move_to(end);
227        }
228
229        if self.duplicate() {
230            self.fill().arc_to(radii, rotation_angle, is_large_arc, clockwise, end);
231        }
232    }
233
234    fn begin_figure(&mut self, start_point: Point, is_filled: bool) {
235        if !is_filled {
236            self.ensure_separate_fill_path();
237        }
238
239        self.is_filled = is_filled;
240        self.start_point = start_point;
241        self.is_figure_broken = false;
242        self.stroke.move_to(to_kurbo_point(start_point));
243
244        if self.duplicate() {
245            self.fill().move_to(to_kurbo_point(start_point));
246        }
247    }
248
249    fn cubic_bezier_to(&mut self, control_point1: Point, control_point2: Point, end_point: Point, is_stroked: bool) {
250        let (p1, p2, p3) =
251            (to_kurbo_point(control_point1), to_kurbo_point(control_point2), to_kurbo_point(end_point));
252
253        if is_stroked {
254            self.stroke.cubic_to(p1, p2, p3);
255        } else {
256            self.break_figure();
257            self.stroke.move_to(p3);
258        }
259
260        if self.duplicate() {
261            self.fill().cubic_to(p1, p2, p3);
262        }
263    }
264
265    fn quadratic_bezier_to(&mut self, control_point: Point, end_point: Point, is_stroked: bool) {
266        let (p1, p2) = (to_kurbo_point(control_point), to_kurbo_point(end_point));
267
268        if is_stroked {
269            self.stroke.quad_to(p1, p2);
270        } else {
271            self.break_figure();
272            self.stroke.move_to(p2);
273        }
274
275        if self.duplicate() {
276            self.fill().quad_to(p1, p2);
277        }
278    }
279
280    fn line_to(&mut self, point: Point, is_stroked: bool) {
281        let point = to_kurbo_point(point);
282
283        if is_stroked {
284            self.stroke.line_to(point);
285        } else {
286            self.break_figure();
287            self.stroke.move_to(point);
288        }
289
290        if self.duplicate() {
291            self.fill().line_to(point);
292        }
293    }
294
295    fn end_figure(&mut self, is_closed: bool) {
296        if is_closed {
297            if self.is_figure_broken {
298                self.stroke.line_to(to_kurbo_point(self.start_point));
299                self.is_figure_broken = false;
300            } else {
301                self.stroke.close();
302            }
303
304            if self.duplicate() {
305                self.fill().close();
306            }
307        }
308    }
309
310    fn set_fill_rule(&mut self, fill_rule: FillRule) {
311        self.fill().fill_rule = fill_rule;
312    }
313
314    fn dispose(&mut self) {}
315}