Skip to main content

dioxuscut_paths/
length.rs

1//! SVG path length calculation.
2
3use crate::parser::parse_path;
4use crate::types::Instruction;
5
6/// Calculates total length of an SVG path string in pixels.
7pub 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
16/// Calculates total length of a list of [`Instruction`]s.
17pub 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        }
65    }
66
67    total_length
68}
69
70fn cubic_bezier_length(
71    (x0, y0): (f64, f64),
72    (x1, y1): (f64, f64),
73    (x2, y2): (f64, f64),
74    (x3, y3): (f64, f64),
75) -> f64 {
76    let steps = 16;
77    let mut length = 0.0;
78    let mut prev_x = x0;
79    let mut prev_y = y0;
80
81    for i in 1..=steps {
82        let t = i as f64 / steps as f64;
83        let mt = 1.0 - t;
84
85        let px =
86            mt * mt * mt * x0 + 3.0 * mt * mt * t * x1 + 3.0 * mt * t * t * x2 + t * t * t * x3;
87
88        let py =
89            mt * mt * mt * y0 + 3.0 * mt * mt * t * y1 + 3.0 * mt * t * t * y2 + t * t * t * y3;
90
91        let dx = px - prev_x;
92        let dy = py - prev_y;
93        length += (dx * dx + dy * dy).sqrt();
94
95        prev_x = px;
96        prev_y = py;
97    }
98
99    length
100}
101
102fn quad_bezier_length(x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
103    let steps = 16;
104    let mut length = 0.0;
105    let mut prev_x = x0;
106    let mut prev_y = y0;
107
108    for i in 1..=steps {
109        let t = i as f64 / steps as f64;
110        let mt = 1.0 - t;
111
112        let px = mt * mt * x0 + 2.0 * mt * t * x1 + t * t * x2;
113        let py = mt * mt * y0 + 2.0 * mt * t * y1 + t * t * y2;
114
115        let dx = px - prev_x;
116        let dy = py - prev_y;
117        length += (dx * dx + dy * dy).sqrt();
118
119        prev_x = px;
120        prev_y = py;
121    }
122
123    length
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_rect_path_length() {
132        let d = "M 0 0 L 100 0 L 100 100 L 0 100 Z";
133        let len = get_length(d);
134        assert_eq!(len, 400.0);
135    }
136}