1use lyon_path::{
2 geom::{euclid, Angle, Vector},
3 traits::PathBuilder,
4 ArcFlags, Attributes, Polygon, Winding,
5};
6
7pub type Point = euclid::default::Point2D<f32>;
8
9/// Adds a sub-path from a polygon but rounds the corners.
10///
11/// There must be no sub-path in progress when this method is called.
12/// No sub-path is in progress after the method is called.
13pub fn add_rounded_polygon<B: PathBuilder>(
14 builder: &mut B,
15 polygon: Polygon<Point>,
16 radius: f32,
17 attributes: Attributes,
18) {
19 if polygon.points.len() < 2 {
20 return;
21 }
22
23 //p points are original polygon points
24 //q points are the actual points we will draw lines and arcs between
25 let clamped_radius = clamp_radius(
26 radius,
27 polygon.points[polygon.points.len() - 1],
28 polygon.points[0],
29 polygon.points[1],
30 );
31 let q_first = get_point_between(polygon.points[0], polygon.points[1], clamped_radius);
32
33 //We begin on the line just after the first point
34 builder.begin(q_first, attributes);
35
36 for index in 0..polygon.points.len() {
37 let p_current = polygon.points[index];
38 let p_next = polygon.points[(index + 1) % polygon.points.len()];
39 let p_after_next = polygon.points[(index + 2) % polygon.points.len()];
40
41 let clamped_radius = clamp_radius(radius, p_current, p_next, p_after_next);
42
43 //q1 is the second point on the line between p_current and p_next
44 let q1 = get_point_between(p_next, p_current, clamped_radius);
45 //q2 is the first point on the line between p_next and p_after_next
46 let q2 = get_point_between(p_next, p_after_next, clamped_radius);
47
48 builder.line_to(q1, attributes);
49 let turn_winding = get_winding(p_current, p_next, p_after_next);
50
51 //Draw the arc near p_next
52 arc(
53 builder,
54 Vector::new(clamped_radius, clamped_radius),
55 Angle { radians: 0.0 },
56 ArcFlags {
57 large_arc: false,
58 sweep: turn_winding == Winding::Negative,
59 },
60 q1,
61 q2,
62 attributes,
63 );
64 }
65
66 builder.end(polygon.closed);
67}
68
69fn clamp_radius(radius: f32, p_previous: Point, p_current: Point, p_next: Point) -> f32 {
70 let shorter_edge: f32 = ((p_current - p_next).length()).min((p_previous - p_current).length());
71
72 radius.min(shorter_edge * 0.5)
73}
74
75fn get_point_between(p1: Point, p2: Point, radius: f32) -> Point {
76 let dist: f32 = p1.distance_to(p2);
77 let ratio: f32 = radius / dist;
78
79 p1.lerp(other:p2, t:ratio)
80}
81
82fn get_winding(p0: Point, p1: Point, p2: Point) -> Winding {
83 let cross: f32 = (p2 - p0).cross(p1 - p0);
84 if cross.is_sign_positive() {
85 Winding::Positive
86 } else {
87 Winding::Negative
88 }
89}
90
91fn arc<B: PathBuilder>(
92 builder: &mut B,
93 radii: Vector<f32>,
94 x_rotation: Angle<f32>,
95 flags: ArcFlags,
96 from: Point,
97 to: Point,
98 attributes: Attributes,
99) {
100 let svg_arc: SvgArc = lyon_path::geom::SvgArc {
101 from,
102 to,
103 radii,
104 x_rotation,
105 flags,
106 };
107
108 if svg_arc.is_straight_line() {
109 builder.line_to(to, custom_attributes:attributes);
110 } else {
111 let geom_arc: Arc = svg_arc.to_arc();
112 geom_arc.for_each_quadratic_bezier(&mut |curve: &QuadraticBezierSegment| {
113 builder.quadratic_bezier_to(curve.ctrl, curve.to, custom_attributes:attributes);
114 });
115 }
116}
117
118#[test]
119fn rounded_polygon() {
120 use crate::geom::point;
121 use crate::rounded_polygon::*;
122 use alloc::vec::Vec;
123 use euclid::approxeq::ApproxEq;
124
125 type Point = euclid::Point2D<f32, euclid::UnknownUnit>;
126 type Event = path::Event<Point, Point>;
127 let arrow_points = [
128 point(-1.0, -0.3),
129 point(0.0, -0.3),
130 point(0.0, -1.0),
131 point(1.5, 0.0),
132 point(0.0, 1.0),
133 point(0.0, 0.3),
134 point(-1.0, 0.3),
135 ];
136
137 let arrow_polygon = Polygon {
138 points: &arrow_points,
139 closed: true,
140 };
141
142 let mut builder = lyon_path::Path::builder();
143 add_rounded_polygon(&mut builder, arrow_polygon, 0.2, lyon_path::NO_ATTRIBUTES);
144 let arrow_path = builder.build();
145
146 //check that we have the right ordering of event types
147 let actual_events: alloc::vec::Vec<_> = arrow_path.into_iter().collect();
148
149 let actual_event_types = actual_events
150 .iter()
151 .map(|x| match x {
152 Event::Begin { at: _ } => "b",
153 Event::Line { from: _, to: _ } => "l",
154 Event::Quadratic {
155 from: _,
156 ctrl: _,
157 to: _,
158 } => "q",
159 Event::Cubic {
160 from: _,
161 ctrl1: _,
162 ctrl2: _,
163 to: _,
164 } => "c",
165 Event::End {
166 last: _,
167 first: _,
168 close: _,
169 } => "e",
170 })
171 .collect::<alloc::vec::Vec<_>>()
172 .concat();
173
174 assert_eq!(actual_event_types, "blqqlqqlqqlqqlqqlqqlqqe");
175
176 let expected_lines = std::vec![
177 (point(-0.8, -0.3), point(-0.2, -0.3)),
178 (point(0.0, -0.5), point(0.0, -0.8)),
179 (point(0.166, -0.889), point(1.333, -0.111)),
180 (point(1.334, 0.111), point(0.166, 0.889)),
181 (point(0.0, 0.8), point(0.0, 0.5)),
182 (point(-0.2, 0.3), point(-0.8, 0.3)),
183 (point(-1.0, 0.1), point(-1.0, -0.1))
184 ];
185
186 //Check that the lines are approximately correct
187 let actual_lines: Vec<_> = arrow_path
188 .into_iter()
189 .filter_map(|event| match event {
190 Event::Line { from, to } => Some((from, to)),
191 _ => None,
192 })
193 .collect();
194
195 for (actual, expected) in actual_lines.into_iter().zip(expected_lines.into_iter()) {
196 for (actual_point, expected_point) in [(actual.0, expected.0), (actual.1, expected.1)] {
197 assert!(actual_point.approx_eq_eps(&expected_point, &Point::new(0.01, 0.01)))
198 }
199 }
200
201 //Check that each event goes from the end of the previous event
202
203 let mut previous = actual_events[0].to();
204
205 for e in actual_events {
206 e.from().approx_eq(&previous);
207 previous = e.to();
208 }
209}
210