silkenweb/elements/svg/
path.rs

1//! Tools to construct an SVG path.
2use std::fmt::Write;
3
4use include_doc::function_body;
5use silkenweb_signals_ext::value::Value;
6
7use crate::attribute::{AsAttribute, Attribute};
8
9/// Absolute or relative indicator for a path component.
10#[derive(Copy, Clone)]
11pub enum Offset {
12    Abs,
13    Rel,
14}
15
16/// Construct path data for the [`d`] attribute
17///
18/// # Example
19///
20/// ```
21#[doc = function_body!("tests/doc/elements/svg.rs", path_data, [])]
22/// ```
23/// [`d`]: `super::attributes::Presentation::d`
24#[derive(Default, Clone)]
25pub struct Data(String);
26
27impl Data {
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    pub fn new_with_capacity(capacity: usize) -> Self {
33        Self(String::with_capacity(capacity))
34    }
35
36    /// The `m/M` commands ([MDN Docs])
37    ///
38    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#moveto_path_commands
39    pub fn move_to(self, offset: Offset, x: f64, y: f64) -> Self {
40        self.cmd2(offset, 'm', [(x, y)])
41    }
42
43    /// The `l/L` commands ([MDN Docs])
44    ///
45    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#lineto_path_commands
46    pub fn lines_to(self, offset: Offset, ends: impl IntoIterator<Item = (f64, f64)>) -> Self {
47        self.cmd2(offset, 'l', ends)
48    }
49
50    /// The `h/H` commands ([MDN Docs])
51    ///
52    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#lineto_path_commands
53    pub fn horizontal_lines_to(self, offset: Offset, ends: impl IntoIterator<Item = f64>) -> Self {
54        self.cmd1(offset, 'h', ends)
55    }
56
57    /// The `v/V` commands ([MDN Docs])
58    ///
59    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#lineto_path_commands
60    pub fn vertical_lines_to(self, offset: Offset, ends: impl IntoIterator<Item = f64>) -> Self {
61        self.cmd1(offset, 'v', ends)
62    }
63
64    /// The `c/C` commands ([MDN Docs])
65    ///
66    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#cubic_b%C3%A9zier_curve
67    pub fn cubic_bezier_curves(
68        self,
69        offset: Offset,
70        args: impl IntoIterator<Item = (f64, f64, f64, f64, f64, f64)>,
71    ) -> Self {
72        self.cmd6(offset, 'c', args)
73    }
74
75    /// The `s/S` commands ([MDN Docs])
76    ///
77    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#cubic_b%C3%A9zier_curve
78    pub fn smooth_cubic_bezier_curves(
79        self,
80        offset: Offset,
81        args: impl IntoIterator<Item = (f64, f64, f64, f64)>,
82    ) -> Self {
83        self.cmd4(offset, 's', args)
84    }
85
86    /// The `q/Q` commands ([MDN Docs])
87    ///
88    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#quadratic_b%C3%A9zier_curve
89    pub fn quadradic_bezier_curves(
90        self,
91        offset: Offset,
92        args: impl IntoIterator<Item = (f64, f64, f64, f64)>,
93    ) -> Self {
94        self.cmd4(offset, 'q', args)
95    }
96
97    /// The `t/T` commands ([MDN Docs])
98    ///
99    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#quadratic_b%C3%A9zier_curve
100    pub fn smooth_quadradic_bezier_curves(
101        self,
102        offset: Offset,
103        end_points: impl IntoIterator<Item = (f64, f64)>,
104    ) -> Self {
105        self.cmd2(offset, 't', end_points)
106    }
107
108    /// The `a/A` commands ([MDN Docs])
109    ///
110    /// [MDN Docs]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#elliptical_arc_curve
111    pub fn elliptical_arc_curves(
112        self,
113        offset: Offset,
114        args: impl IntoIterator<Item = (f64, f64, f64, f64, f64, f64, f64)>,
115    ) -> Self {
116        self.cmd7(offset, 'a', args)
117    }
118
119    fn cmd1(self, offset: Offset, cmd: char, cmds: impl IntoIterator<Item = f64>) -> Self {
120        self.cmd(offset, cmd, cmds.into_iter().map(|x| [x]))
121    }
122
123    fn cmd2(self, offset: Offset, cmd: char, cmds: impl IntoIterator<Item = (f64, f64)>) -> Self {
124        self.cmd(offset, cmd, cmds.into_iter().map(|(x0, x1)| [x0, x1]))
125    }
126
127    fn cmd4(
128        self,
129        offset: Offset,
130        cmd: char,
131        cmds: impl IntoIterator<Item = (f64, f64, f64, f64)>,
132    ) -> Self {
133        self.cmd(
134            offset,
135            cmd,
136            cmds.into_iter().map(|(x0, x1, x2, x3)| [x0, x1, x2, x3]),
137        )
138    }
139
140    fn cmd6(
141        self,
142        offset: Offset,
143        cmd: char,
144        cmds: impl IntoIterator<Item = (f64, f64, f64, f64, f64, f64)>,
145    ) -> Self {
146        self.cmd(
147            offset,
148            cmd,
149            cmds.into_iter()
150                .map(|(x0, x1, x2, x3, x4, x5)| [x0, x1, x2, x3, x4, x5]),
151        )
152    }
153
154    fn cmd7(
155        self,
156        offset: Offset,
157        cmd: char,
158        cmds: impl IntoIterator<Item = (f64, f64, f64, f64, f64, f64, f64)>,
159    ) -> Self {
160        self.cmd(
161            offset,
162            cmd,
163            cmds.into_iter()
164                .map(|(x0, x1, x2, x3, x4, x5, x6)| [x0, x1, x2, x3, x4, x5, x6]),
165        )
166    }
167
168    fn cmd<const COUNT: usize>(
169        mut self,
170        offset: Offset,
171        cmd: char,
172        cmds: impl IntoIterator<Item = [f64; COUNT]>,
173    ) -> Self {
174        let cmd = match offset {
175            Offset::Abs => cmd.to_ascii_uppercase(),
176            Offset::Rel => cmd.to_ascii_lowercase(),
177        };
178        let mut cmds = cmds.into_iter().peekable();
179
180        if cmds.peek().is_some() {
181            if !self.0.is_empty() {
182                self.0.push(' ');
183            }
184
185            self.0.push(cmd);
186
187            for args in cmds {
188                let mut args = args.into_iter();
189                write!(&mut self.0, " {}", args.next().unwrap()).unwrap();
190
191                for arg in args {
192                    write!(&mut self.0, ",{arg}").unwrap();
193                }
194            }
195        }
196
197        self
198    }
199}
200
201impl AsAttribute<Data> for Data {}
202impl AsAttribute<Data> for String {}
203impl AsAttribute<Data> for &str {}
204
205impl Attribute for Data {
206    type Text<'a> = &'a str;
207
208    fn text(&self) -> Option<Self::Text<'_>> {
209        Some(self.0.as_str())
210    }
211}
212
213impl Value for Data {}