path_offset/path/conversions/flo_curves.rs
1//! Provides conversions to and from `flo_curves` path types.
2//!
3//! This module allows for interoperability with the `flo_curves` library by converting
4//! between this crate's [`Path`](crate::path::Path) and `flo_curves`'s `SimpleBezierPath`
5//! and `Vec<Curve<Coord2>>`. This is essential for leveraging `flo_curves`'s path
6//! manipulation algorithms.
7
8use flo_curves::{
9 BezierCurve, Coord2, Coordinate,
10 bezier::{
11 Curve,
12 path::{BezierPathBuilder, SimpleBezierPath},
13 },
14};
15use lyon::path::Event;
16
17use crate::path::point::PointConvert;
18
19/// Converts a reference to a [`Path`](crate::path::Path) into a `flo_curves::SimpleBezierPath`.
20///
21/// This conversion processes the `lyon::path::Event` stream of the input path:
22/// - `Event::Line`, `Event::Cubic`: Translated directly to `flo_curves` equivalents.
23/// - `Event::Quadratic`: Mathematically converted into a cubic Bézier curve, as
24/// `flo_curves` primarily works with cubic curves.
25/// - `Event::End`: If the path is not marked as closed by `lyon`, a closing line segment
26/// is added to ensure the `flo_curves` path is properly closed, which is often a
27/// requirement for path algorithms.
28impl From<&crate::path::Path> for SimpleBezierPath {
29 fn from(path: &crate::path::Path) -> SimpleBezierPath {
30 let mut builder = BezierPathBuilder::<SimpleBezierPath>::start(Coord2::from((0.0, 0.0)));
31 let mut current_pos = Coord2::from((0.0, 0.0)); // Track current position
32
33 for event in path.inner.iter() {
34 match event {
35 Event::Begin { at } => {
36 let start_point = at.use_as();
37 builder = BezierPathBuilder::start(start_point);
38 current_pos = start_point;
39 }
40 Event::Line { to, .. } => {
41 let to_point = to.use_as();
42 builder = builder.line_to(to_point);
43 current_pos = to_point;
44 }
45 Event::Quadratic { ctrl, to, .. } => {
46 // Convert quadratic Bézier to cubic control points
47 let cp1: Coord2 =
48 current_pos + (ctrl.use_as::<Coord2>() - current_pos) * (2.0 / 3.0);
49 let cp2: Coord2 = to.use_as::<Coord2>()
50 + (ctrl.use_as::<Coord2>() - to.use_as::<Coord2>()) * (2.0 / 3.0);
51
52 let to_point = to.use_as();
53 builder = builder.curve_to((cp1, cp2), to_point);
54 current_pos = to_point;
55 }
56 Event::Cubic {
57 ctrl1, ctrl2, to, ..
58 } => {
59 let to_point = to.use_as();
60 builder = builder.curve_to((ctrl1.use_as(), ctrl2.use_as()), to_point);
61 current_pos = to_point;
62 }
63 Event::End { first, close, .. } => {
64 // Manually add a closing line segment only if lyon reports the path as open.
65 if !close {
66 // Also check to avoid adding a minuscule line due to floating point errors.
67 if current_pos.distance_to(&first.use_as()) > 1e-6 {
68 builder = builder.line_to(first.use_as());
69 }
70 }
71 // If `close` is true, do nothing, as the path is already perfectly closed.
72 }
73 }
74 }
75
76 builder.build()
77 }
78}
79
80/// Converts a vector of `flo_curves::Curve`s into a [`Path`](crate::path::Path).
81///
82/// Each `Curve` is assumed to be a cubic Bézier segment. The conversion creates a
83/// new `Path` where each curve becomes a separate, unclosed subpath consisting of a
84/// single cubic Bézier segment.
85impl From<&Vec<Curve<Coord2>>> for crate::path::Path {
86 fn from(value: &Vec<Curve<Coord2>>) -> Self {
87 let mut builder = lyon::path::Path::builder();
88
89 let mut points = vec![];
90 for curve in value {
91 let start_point = curve.start_point();
92 let end_point = curve.end_point();
93 let (ctrl1, ctrl2) = curve.control_points();
94
95 points.push((
96 start_point.use_as(),
97 ctrl1.use_as(),
98 ctrl2.use_as(),
99 end_point.use_as(),
100 ));
101 }
102
103 for (start, ctrl1, ctrl2, end) in points {
104 builder.begin(start);
105 builder.cubic_bezier_to(ctrl1, ctrl2, end);
106 builder.end(false);
107 }
108
109 Self {
110 inner: builder.build(),
111 }
112 }
113}
114
115/// Converts a `flo_curves::SimpleBezierPath` back into a [`Path`](crate::path::Path).
116///
117/// This reconstructs a `lyon` path from the `flo_curves` representation. It handles
118/// both lines and cubic curves. The resulting path is explicitly closed by adding a
119/// line segment back to the start point and calling `close()`.
120impl From<&SimpleBezierPath> for crate::path::Path {
121 fn from(value: &SimpleBezierPath) -> Self {
122 let (start_point, segments) = value;
123 let mut builder = lyon::path::Path::builder();
124
125 // Begin path at the start point
126 builder.begin(start_point.use_as());
127
128 // Track last point for later closure
129 let mut last_point = start_point;
130
131 for (ctrl1, ctrl2, to) in segments {
132 if ctrl1.is_nan() || ctrl2.is_nan() || to.is_nan() {
133 continue;
134 }
135
136 // A line is represented in SimpleBezierPath where control points align with endpoints.
137 let is_line = ctrl1 == last_point && ctrl2 == to;
138
139 if is_line {
140 builder.line_to(to.use_as());
141 } else {
142 builder.cubic_bezier_to(ctrl1.use_as(), ctrl2.use_as(), to.use_as());
143 }
144
145 last_point = to;
146 }
147
148 // Close the path by returning to the start point.
149 builder.line_to(start_point.use_as());
150 builder.close();
151
152 Self {
153 inner: builder.build(),
154 }
155 }
156}