1use crate::bezier::PathEvaluate;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct DrawValues {
13 pub dash_array: f32,
15 pub dash_offset: f32,
17}
18
19impl DrawValues {
20 pub fn progress(&self) -> f32 {
22 if self.dash_array <= f32::EPSILON {
23 return 1.0;
24 }
25 (1.0 - self.dash_offset / self.dash_array).clamp(0.0, 1.0)
26 }
27
28 #[cfg(any(feature = "std", feature = "alloc"))]
34 pub fn to_css(&self) -> alloc::string::String {
35 alloc::format!(
36 "stroke-dasharray: {:.3}; stroke-dashoffset: {:.3}",
37 self.dash_array,
38 self.dash_offset
39 )
40 }
41}
42
43pub trait DrawSvg {
57 fn total_length(&self) -> f32;
59
60 fn draw_on(&self, progress: f32) -> DrawValues {
65 let p = progress.clamp(0.0, 1.0);
66 let length = self.total_length();
67 DrawValues {
68 dash_array: length,
69 dash_offset: length * (1.0 - p),
70 }
71 }
72
73 fn draw_on_reverse(&self, progress: f32) -> DrawValues {
77 let p = progress.clamp(0.0, 1.0);
78 let length = self.total_length();
79 DrawValues {
80 dash_array: length,
81 dash_offset: length * p,
82 }
83 }
84}
85
86impl<T: PathEvaluate> DrawSvg for T {
88 #[inline]
89 fn total_length(&self) -> f32 {
90 self.arc_length()
91 }
92}
93
94#[cfg(all(test, any(feature = "std", feature = "alloc")))]
95mod tests {
96 use super::*;
97 use crate::poly::LineSegment;
98
99 #[test]
100 fn draw_on_zero_is_invisible() {
101 let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
102 let v = line.draw_on(0.0);
103 assert_eq!(v.dash_array, 100.0);
104 assert_eq!(v.dash_offset, 100.0);
105 assert_eq!(v.progress(), 0.0);
106 }
107
108 #[test]
109 fn draw_on_one_is_fully_visible() {
110 let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
111 let v = line.draw_on(1.0);
112 assert_eq!(v.dash_offset, 0.0);
113 assert_eq!(v.progress(), 1.0);
114 }
115
116 #[test]
117 fn draw_on_half() {
118 let line = LineSegment::new([0.0, 0.0], [100.0, 0.0]);
119 let v = line.draw_on(0.5);
120 assert!((v.dash_array - 100.0).abs() < 0.001);
121 assert!((v.dash_offset - 50.0).abs() < 0.001);
122 assert!((v.progress() - 0.5).abs() < 0.001);
123 }
124
125 #[test]
126 fn draw_on_reverse_inverts_progress() {
127 let line = LineSegment::new([0.0, 0.0], [200.0, 0.0]);
128 let fwd = line.draw_on(0.3);
129 let rev = line.draw_on_reverse(0.7);
130 assert!((fwd.dash_offset - rev.dash_offset).abs() < 0.001);
132 }
133
134 #[cfg(any(feature = "std", feature = "alloc"))]
135 #[test]
136 fn to_css_formats_correctly() {
137 let v = DrawValues {
138 dash_array: 314.159,
139 dash_offset: 157.080,
140 };
141 let css = v.to_css();
142 assert!(css.contains("stroke-dasharray"));
143 assert!(css.contains("stroke-dashoffset"));
144 }
145}