motion_core/path2d/
editing.rs1use super::{MAX_PATH_VERBS, Path2D, PathVerb, invalid};
4use crate::{Point, ValidationError, Vector2};
5use std::sync::Arc;
6
7impl Path2D {
8 pub fn move_to(&mut self, point: Point) -> &mut Self {
10 self.author(PathVerb::MoveTo(point))
11 }
12
13 pub fn try_move_to(&mut self, point: Point) -> Result<(), ValidationError> {
15 self.push(PathVerb::MoveTo(point))
16 }
17
18 pub fn line_to(&mut self, point: Point) -> &mut Self {
20 self.author(PathVerb::LineTo(point))
21 }
22
23 pub fn try_line_to(&mut self, point: Point) -> Result<(), ValidationError> {
25 self.push(PathVerb::LineTo(point))
26 }
27
28 pub fn quadratic_to(&mut self, control: Point, end: Point) -> &mut Self {
30 self.author(PathVerb::QuadraticTo(control, end))
31 }
32
33 pub fn try_quadratic_to(&mut self, control: Point, end: Point) -> Result<(), ValidationError> {
35 self.push(PathVerb::QuadraticTo(control, end))
36 }
37
38 pub fn cubic_to(&mut self, first: Point, second: Point, end: Point) -> &mut Self {
40 self.author(PathVerb::CubicTo(first, second, end))
41 }
42
43 pub fn try_cubic_to(
45 &mut self,
46 first: Point,
47 second: Point,
48 end: Point,
49 ) -> Result<(), ValidationError> {
50 self.push(PathVerb::CubicTo(first, second, end))
51 }
52
53 pub fn arc_to(
55 &mut self,
56 center: Point,
57 radii: Vector2,
58 start_angle: f64,
59 sweep_angle: f64,
60 ) -> &mut Self {
61 if self.data.error.is_none()
62 && let Err(error) = self.try_arc_to(center, radii, start_angle, sweep_angle)
63 {
64 Arc::make_mut(&mut self.data).error = Some(error);
65 }
66 self
67 }
68
69 pub fn try_arc_to(
89 &mut self,
90 center: Point,
91 radii: Vector2,
92 start_angle: f64,
93 sweep_angle: f64,
94 ) -> Result<(), ValidationError> {
95 self.elliptical_arc(center, radii, start_angle, sweep_angle, 0.0)
96 }
97
98 pub(super) fn elliptical_arc(
99 &mut self,
100 center: Point,
101 radii: Vector2,
102 start_angle: f64,
103 sweep_angle: f64,
104 rotation: f64,
105 ) -> Result<(), ValidationError> {
106 if ![
107 center.x,
108 center.y,
109 radii.x,
110 radii.y,
111 start_angle,
112 sweep_angle,
113 rotation,
114 ]
115 .iter()
116 .all(|value| value.is_finite())
117 || radii.x < 0.0
118 || radii.y < 0.0
119 {
120 return Err(invalid(
121 "arc",
122 "Arc parameters must be finite with nonnegative radii",
123 ));
124 }
125 let count = libm::ceil(sweep_angle.abs() / std::f64::consts::FRAC_PI_8);
126 if count > MAX_PATH_VERBS.saturating_sub(self.verbs().len() + 1) as f64 {
127 return Err(ValidationError::limit(
128 "path2d",
129 "verbs",
130 "Arc exceeds the verb allowance",
131 ));
132 }
133 let start = start_angle.rem_euclid(std::f64::consts::TAU);
134 let rotate = |vector: Vector2| vector.rotate(rotation);
135 let direction = |angle: f64| {
136 let angle = angle.rem_euclid(std::f64::consts::TAU);
137 Vector2::from_angle(if angle > std::f64::consts::PI {
138 angle - std::f64::consts::TAU
139 } else {
140 angle
141 })
142 };
143 let position = |angle: f64| {
144 let vector = direction(angle);
145 center + rotate(Vector2::new(radii.x * vector.x, radii.y * vector.y))
146 };
147 let tangent = |angle: f64| {
148 let vector = direction(angle);
149 rotate(Vector2::new(-radii.x * vector.y, radii.y * vector.x))
150 };
151 let mut result = self.clone();
152 let first = position(start);
153 match result.data.current {
154 None => result.try_move_to(first)?,
155 Some(current) if current != first => result.try_line_to(first)?,
156 _ => {}
157 }
158 if count > 0.0 {
159 let step = sweep_angle / count;
160 let factor = 4.0 / 3.0 * libm::tan(step / 4.0);
161 for index in 0..count as usize {
162 let angle = start + step * index as f64;
163 let next = start + step * (index + 1) as f64;
164 result.try_cubic_to(
165 position(angle) + tangent(angle) * factor,
166 position(next) - tangent(next) * factor,
167 position(next),
168 )?;
169 }
170 }
171 *self = result;
172 Ok(())
173 }
174
175 pub fn close(&mut self) -> &mut Self {
177 self.author(PathVerb::Close)
178 }
179
180 pub fn try_close(&mut self) -> Result<(), ValidationError> {
182 self.push(PathVerb::Close)
183 }
184
185 pub fn closed(mut self) -> Self {
187 self.close();
188 self
189 }
190
191 fn author(&mut self, verb: PathVerb) -> &mut Self {
192 if let Err(error) = self.push(verb)
193 && self.data.error.is_none()
194 {
195 Arc::make_mut(&mut self.data).error = Some(error);
196 }
197 self
198 }
199}