1use crate::parser::parse_path;
4use crate::types::Instruction;
5
6pub fn get_length(path: &str) -> f64 {
8 let instructions = match parse_path(path) {
9 Ok(insts) => insts,
10 Err(_) => return 0.0,
11 };
12
13 get_instructions_length(&instructions)
14}
15
16pub fn get_instructions_length(instructions: &[Instruction]) -> f64 {
18 let mut total_length = 0.0;
19 let mut current_x = 0.0;
20 let mut current_y = 0.0;
21 let mut start_x = 0.0;
22 let mut start_y = 0.0;
23
24 for inst in instructions {
25 match inst {
26 Instruction::MoveTo { x, y } => {
27 current_x = *x;
28 current_y = *y;
29 start_x = *x;
30 start_y = *y;
31 }
32 Instruction::LineTo { x, y } => {
33 let dx = x - current_x;
34 let dy = y - current_y;
35 total_length += (dx * dx + dy * dy).sqrt();
36 current_x = *x;
37 current_y = *y;
38 }
39 Instruction::ClosePath => {
40 let dx = start_x - current_x;
41 let dy = start_y - current_y;
42 total_length += (dx * dx + dy * dy).sqrt();
43 current_x = start_x;
44 current_y = start_y;
45 }
46 Instruction::CubicCurveTo {
47 x1,
48 y1,
49 x2,
50 y2,
51 x,
52 y,
53 } => {
54 total_length +=
55 cubic_bezier_length((current_x, current_y), (*x1, *y1), (*x2, *y2), (*x, *y));
56 current_x = *x;
57 current_y = *y;
58 }
59 Instruction::QuadCurveTo { x1, y1, x, y } => {
60 total_length += quad_bezier_length(current_x, current_y, *x1, *y1, *x, *y);
61 current_x = *x;
62 current_y = *y;
63 }
64 Instruction::ArcTo {
65 rx,
66 ry,
67 x_axis_rotation,
68 large_arc_flag,
69 sweep_flag,
70 x,
71 y,
72 } => {
73 total_length += arc_segment_length(
74 (current_x, current_y),
75 (*x, *y),
76 *rx,
77 *ry,
78 *x_axis_rotation,
79 *large_arc_flag,
80 *sweep_flag,
81 );
82 current_x = *x;
83 current_y = *y;
84 }
85 }
86 }
87
88 total_length
89}
90
91fn cubic_bezier_length(
92 (x0, y0): (f64, f64),
93 (x1, y1): (f64, f64),
94 (x2, y2): (f64, f64),
95 (x3, y3): (f64, f64),
96) -> f64 {
97 let steps = 16;
98 let mut length = 0.0;
99 let mut prev_x = x0;
100 let mut prev_y = y0;
101
102 for i in 1..=steps {
103 let t = i as f64 / steps as f64;
104 let mt = 1.0 - t;
105
106 let px =
107 mt * mt * mt * x0 + 3.0 * mt * mt * t * x1 + 3.0 * mt * t * t * x2 + t * t * t * x3;
108
109 let py =
110 mt * mt * mt * y0 + 3.0 * mt * mt * t * y1 + 3.0 * mt * t * t * y2 + t * t * t * y3;
111
112 let dx = px - prev_x;
113 let dy = py - prev_y;
114 length += (dx * dx + dy * dy).sqrt();
115
116 prev_x = px;
117 prev_y = py;
118 }
119
120 length
121}
122
123fn quad_bezier_length(x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
124 let steps = 16;
125 let mut length = 0.0;
126 let mut prev_x = x0;
127 let mut prev_y = y0;
128
129 for i in 1..=steps {
130 let t = i as f64 / steps as f64;
131 let mt = 1.0 - t;
132
133 let px = mt * mt * x0 + 2.0 * mt * t * x1 + t * t * x2;
134 let py = mt * mt * y0 + 2.0 * mt * t * y1 + t * t * y2;
135
136 let dx = px - prev_x;
137 let dy = py - prev_y;
138 length += (dx * dx + dy * dy).sqrt();
139
140 prev_x = px;
141 prev_y = py;
142 }
143
144 length
145}
146
147pub fn arc_segment_length(
149 start: (f64, f64),
150 end: (f64, f64),
151 mut rx: f64,
152 mut ry: f64,
153 x_axis_rotation: f64,
154 large_arc_flag: bool,
155 sweep_flag: bool,
156) -> f64 {
157 let (x1, y1) = start;
158 let (x2, y2) = end;
159
160 if (x1 - x2).abs() < 1e-9 && (y1 - y2).abs() < 1e-9 {
161 return 0.0;
162 }
163
164 rx = rx.abs();
165 ry = ry.abs();
166 if rx < 1e-9 || ry < 1e-9 {
167 let dx = x2 - x1;
168 let dy = y2 - y1;
169 return (dx * dx + dy * dy).sqrt();
170 }
171
172 let phi = x_axis_rotation.to_radians();
173 let cos_phi = phi.cos();
174 let sin_phi = phi.sin();
175
176 let dx = (x1 - x2) / 2.0;
178 let dy = (y1 - y2) / 2.0;
179 let x1_prime = cos_phi * dx + sin_phi * dy;
180 let y1_prime = -sin_phi * dx + cos_phi * dy;
181
182 let lambda = (x1_prime * x1_prime) / (rx * rx) + (y1_prime * y1_prime) / (ry * ry);
184 if lambda > 1.0 {
185 let sqrt_lambda = lambda.sqrt();
186 rx *= sqrt_lambda;
187 ry *= sqrt_lambda;
188 }
189
190 let rx_sq = rx * rx;
192 let ry_sq = ry * ry;
193 let x1_p_sq = x1_prime * x1_prime;
194 let y1_p_sq = y1_prime * y1_prime;
195
196 let num = (rx_sq * ry_sq - rx_sq * y1_p_sq - ry_sq * x1_p_sq).max(0.0);
197 let den = rx_sq * y1_p_sq + ry_sq * x1_p_sq;
198 let sq = if den < 1e-9 { 0.0 } else { num / den };
199 let mut s = sq.sqrt();
200 if large_arc_flag == sweep_flag {
201 s = -s;
202 }
203 let cx_prime = s * (rx * y1_prime / ry);
204 let cy_prime = s * (-ry * x1_prime / rx);
205
206 let cx = cos_phi * cx_prime - sin_phi * cy_prime + (x1 + x2) / 2.0;
208 let cy = sin_phi * cx_prime + cos_phi * cy_prime + (y1 + y2) / 2.0;
209
210 let v1 = ((x1_prime - cx_prime) / rx, (y1_prime - cy_prime) / ry);
212 let v2 = ((-x1_prime - cx_prime) / rx, (-y1_prime - cy_prime) / ry);
213
214 let theta1 = vector_angle((1.0, 0.0), v1);
215 let mut delta_theta = vector_angle(v1, v2);
216
217 let two_pi = std::f64::consts::TAU;
218 if !sweep_flag && delta_theta > 0.0 {
219 delta_theta -= two_pi;
220 } else if sweep_flag && delta_theta < 0.0 {
221 delta_theta += two_pi;
222 }
223
224 let num_steps =
226 ((delta_theta.abs() / (std::f64::consts::PI / 64.0)).ceil() as usize).clamp(32, 256);
227 let mut length = 0.0;
228 let mut prev_x = x1;
229 let mut prev_y = y1;
230
231 for i in 1..=num_steps {
232 let t = i as f64 / num_steps as f64;
233 let theta = theta1 + t * delta_theta;
234 let cos_theta = theta.cos();
235 let sin_theta = theta.sin();
236
237 let px = cx + rx * cos_theta * cos_phi - ry * sin_theta * sin_phi;
238 let py = cy + rx * cos_theta * sin_phi + ry * sin_theta * cos_phi;
239
240 let seg_dx = px - prev_x;
241 let seg_dy = py - prev_y;
242 length += (seg_dx * seg_dx + seg_dy * seg_dy).sqrt();
243
244 prev_x = px;
245 prev_y = py;
246 }
247
248 length
249}
250
251pub(crate) fn vector_angle((ux, uy): (f64, f64), (vx, vy): (f64, f64)) -> f64 {
252 let dot = ux * vx + uy * vy;
253 let len_u = (ux * ux + uy * uy).sqrt();
254 let len_v = (vx * vx + vy * vy).sqrt();
255 if len_u < 1e-9 || len_v < 1e-9 {
256 return 0.0;
257 }
258 let cos_val = (dot / (len_u * len_v)).clamp(-1.0, 1.0);
259 let angle = cos_val.acos();
260 if ux * vy - uy * vx < 0.0 {
261 -angle
262 } else {
263 angle
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use std::f64::consts::PI;
271
272 #[test]
273 fn test_rect_path_length() {
274 let d = "M 0 0 L 100 0 L 100 100 L 0 100 Z";
275 let len = get_length(d);
276 assert_eq!(len, 400.0);
277 }
278
279 #[test]
280 fn test_quarter_circle_arc_length() {
281 let len = arc_segment_length((100.0, 0.0), (0.0, 100.0), 100.0, 100.0, 0.0, false, true);
283 let expected = 0.5 * PI * 100.0;
284 assert!(
285 (len - expected).abs() < 0.1,
286 "expected ~{expected}, got {len}"
287 );
288 }
289
290 #[test]
291 fn test_semi_circle_arc_length() {
292 let len = arc_segment_length((50.0, 0.0), (-50.0, 0.0), 50.0, 50.0, 0.0, false, true);
294 let expected = PI * 50.0;
295 assert!(
296 (len - expected).abs() < 0.1,
297 "expected ~{expected}, got {len}"
298 );
299 }
300
301 #[test]
302 fn test_cubic_bezier_length() {
303 let d = "M 0 0 C 0 50 100 50 100 0";
304 let len = get_length(d);
305 assert!(
306 len > 100.0,
307 "Cubic bezier should be longer than chord (100.0), got {len}"
308 );
309 }
310
311 #[test]
312 fn test_quad_bezier_length() {
313 let d = "M 0 0 Q 50 100 100 0";
314 let len = get_length(d);
315 assert!(
316 len > 100.0,
317 "Quad bezier should be longer than chord (100.0), got {len}"
318 );
319 }
320
321 #[test]
322 fn test_full_circle_arc_length() {
323 let d = "M 100 0 A 100 100 0 1 0 -100 0 A 100 100 0 1 0 100 0 Z";
325 let len = get_length(d);
326 let expected = 2.0 * PI * 100.0;
327 assert!(
328 (len - expected).abs() < 1.0,
329 "expected ~{expected}, got {len}"
330 );
331 }
332
333 #[test]
334 fn test_invalid_path_length_returns_zero() {
335 let len = get_length("invalid path string !!!");
336 assert_eq!(len, 0.0);
337 }
338
339 #[test]
340 fn test_instructions_length_empty() {
341 let len = get_instructions_length(&[]);
342 assert_eq!(len, 0.0);
343 }
344}