svg/node/element/path/
data.rs

1use std::ops::Deref;
2
3use super::{Command, Number, Parameters, Position};
4use crate::node::Value;
5use crate::parser::{Error, Reader, Result};
6
7/// A [data][1] attribute.
8///
9/// [1]: https://www.w3.org/TR/SVG/paths.html#PathData
10#[derive(Clone, Debug, Default)]
11pub struct Data(Vec<Command>);
12
13struct Parser<'l> {
14    reader: Reader<'l>,
15}
16
17impl Data {
18    /// Create a data attribute.
19    #[inline]
20    pub fn new() -> Self {
21        Default::default()
22    }
23
24    /// Parse a data attribute.
25    #[inline]
26    pub fn parse(content: &str) -> Result<Self> {
27        Parser::new(content).process()
28    }
29
30    /// Add a command.
31    #[inline]
32    pub fn add(mut self, command: Command) -> Self {
33        self.append(command);
34        self
35    }
36
37    /// Append a command.
38    #[inline]
39    pub fn append(&mut self, command: Command) {
40        self.0.push(command);
41    }
42}
43
44macro_rules! implement {
45    (@one #[$doc:meta] fn $method:ident($command:ident, $position:ident)) => (
46        #[$doc]
47        pub fn $method<T>(mut self, parameters: T) -> Self
48        where
49            T: Into<Parameters>,
50        {
51            self.0.push(Command::$command(Position::$position, parameters.into()));
52            self
53        }
54    );
55    (@one #[$doc:meta] fn $method:ident($command:ident)) => (
56        #[$doc]
57        pub fn $method(mut self) -> Self {
58            self.0.push(Command::$command);
59            self
60        }
61    );
62    ($(#[$doc:meta] fn $method:ident($($argument:tt)*))*) => (
63        impl Data {
64            $(implement! { @one #[$doc] fn $method($($argument)*) })*
65        }
66    );
67}
68
69implement! {
70    #[doc = "Add an absolute `Command::Move` command."]
71    fn move_to(Move, Absolute)
72
73    #[doc = "Add a relative `Command::Move` command."]
74    fn move_by(Move, Relative)
75
76    #[doc = "Add an absolute `Command::Line` command."]
77    fn line_to(Line, Absolute)
78
79    #[doc = "Add a relative `Command::Line` command."]
80    fn line_by(Line, Relative)
81
82    #[doc = "Add an absolute `Command::HorizontalLine` command."]
83    fn horizontal_line_to(HorizontalLine, Absolute)
84
85    #[doc = "Add a relative `Command::HorizontalLine` command."]
86    fn horizontal_line_by(HorizontalLine, Relative)
87
88    #[doc = "Add an absolute `Command::VerticalLine` command."]
89    fn vertical_line_to(VerticalLine, Absolute)
90
91    #[doc = "Add a relative `Command::VerticalLine` command."]
92    fn vertical_line_by(VerticalLine, Relative)
93
94    #[doc = "Add an absolute `Command::QuadraticCurve` command."]
95    fn quadratic_curve_to(QuadraticCurve, Absolute)
96
97    #[doc = "Add a relative `Command::QuadraticCurve` command."]
98    fn quadratic_curve_by(QuadraticCurve, Relative)
99
100    #[doc = "Add an absolute `Command::SmoothQuadraticCurve` command."]
101    fn smooth_quadratic_curve_to(SmoothQuadraticCurve, Absolute)
102
103    #[doc = "Add a relative `Command::SmoothQuadraticCurve` command."]
104    fn smooth_quadratic_curve_by(SmoothQuadraticCurve, Relative)
105
106    #[doc = "Add an absolute `Command::CubicCurve` command."]
107    fn cubic_curve_to(CubicCurve, Absolute)
108
109    #[doc = "Add a relative `Command::CubicCurve` command."]
110    fn cubic_curve_by(CubicCurve, Relative)
111
112    #[doc = "Add an absolute `Command::SmoothCubicCurve` command."]
113    fn smooth_cubic_curve_to(SmoothCubicCurve, Absolute)
114
115    #[doc = "Add a relative `Command::SmoothCubicCurve` command."]
116    fn smooth_cubic_curve_by(SmoothCubicCurve, Relative)
117
118    #[doc = "Add an absolute `Command::EllipticalArc` command."]
119    fn elliptical_arc_to(EllipticalArc, Absolute)
120
121    #[doc = "Add a relative `Command::EllipticalArc` command."]
122    fn elliptical_arc_by(EllipticalArc, Relative)
123
124    #[doc = "Add a `Command::Close` command."]
125    fn close(Close)
126}
127
128impl Deref for Data {
129    type Target = [Command];
130
131    #[inline]
132    fn deref(&self) -> &Self::Target {
133        &self.0
134    }
135}
136
137impl From<Vec<Command>> for Data {
138    #[inline]
139    fn from(commands: Vec<Command>) -> Self {
140        Data(commands)
141    }
142}
143
144impl From<Data> for Vec<Command> {
145    #[inline]
146    fn from(Data(commands): Data) -> Self {
147        commands
148    }
149}
150
151impl From<Data> for Value {
152    #[inline]
153    fn from(Data(mut inner): Data) -> Self {
154        inner
155            .drain(..)
156            .map(String::from)
157            .collect::<Vec<_>>()
158            .join(" ")
159            .into()
160    }
161}
162
163macro_rules! raise(
164    ($parser:expr, $($argument:tt)*) => (
165        return Err(Error::new($parser.reader.position(), format!($($argument)*)))
166    );
167);
168
169impl<'l> Parser<'l> {
170    #[inline]
171    fn new(content: &'l str) -> Self {
172        Parser {
173            reader: Reader::new(content),
174        }
175    }
176
177    fn process(&mut self) -> Result<Data> {
178        let mut commands = Vec::new();
179        loop {
180            self.reader.consume_whitespace();
181            match self.read_command()? {
182                Some(command) => commands.push(command),
183                _ => break,
184            }
185        }
186        Ok(Data(commands))
187    }
188
189    fn read_command(&mut self) -> Result<Option<Command>> {
190        use super::Command::*;
191        use super::Position::*;
192
193        let name = match self.reader.next() {
194            Some(name) => match name {
195                'A'..='Z' | 'a'..='z' => name,
196                _ => raise!(self, "expected a path command"),
197            },
198            _ => return Ok(None),
199        };
200        self.reader.consume_whitespace();
201        Ok(Some(match name {
202            'M' => Move(Absolute, self.read_parameters()?.into()),
203            'm' => Move(Relative, self.read_parameters()?.into()),
204
205            'L' => Line(Absolute, self.read_parameters()?.into()),
206            'l' => Line(Relative, self.read_parameters()?.into()),
207
208            'H' => HorizontalLine(Absolute, self.read_parameters()?.into()),
209            'h' => HorizontalLine(Relative, self.read_parameters()?.into()),
210
211            'V' => VerticalLine(Absolute, self.read_parameters()?.into()),
212            'v' => VerticalLine(Relative, self.read_parameters()?.into()),
213
214            'Q' => QuadraticCurve(Absolute, self.read_parameters()?.into()),
215            'q' => QuadraticCurve(Relative, self.read_parameters()?.into()),
216
217            'T' => SmoothQuadraticCurve(Absolute, self.read_parameters()?.into()),
218            't' => SmoothQuadraticCurve(Relative, self.read_parameters()?.into()),
219
220            'C' => CubicCurve(Absolute, self.read_parameters()?.into()),
221            'c' => CubicCurve(Relative, self.read_parameters()?.into()),
222
223            'S' => SmoothCubicCurve(Absolute, self.read_parameters()?.into()),
224            's' => SmoothCubicCurve(Relative, self.read_parameters()?.into()),
225
226            'A' => EllipticalArc(Absolute, self.read_parameters_elliptical_arc()?.into()),
227            'a' => EllipticalArc(Relative, self.read_parameters_elliptical_arc()?.into()),
228
229            'Z' | 'z' => Close,
230
231            _ => raise!(self, "found an unknown path command '{}'", name),
232        }))
233    }
234
235    fn read_parameters(&mut self) -> Result<Vec<Number>> {
236        let mut parameters = Vec::new();
237
238        while let Some(number) = self.read_number()? {
239            parameters.push(number);
240            self.reader.consume_whitespace();
241            self.reader.consume_char(',');
242        }
243        Ok(parameters)
244    }
245
246    fn read_parameters_elliptical_arc(&mut self) -> Result<Vec<Number>> {
247        let mut parameters = Vec::new();
248        let mut index: usize = 1;
249
250        while let Some(number) = match index % 7 {
251            i if i == 4 || i == 5 => self.read_flag()?,
252            _ => self.read_number()?,
253        } {
254            index += 1;
255            parameters.push(number);
256            self.reader.consume_whitespace();
257            self.reader.consume_char(',');
258        }
259        Ok(parameters)
260    }
261
262    fn read_flag(&mut self) -> Result<Option<Number>> {
263        self.reader.consume_whitespace();
264
265        match self.reader.next() {
266            Some('0') => Ok(Some(0.0)),
267            Some('1') => Ok(Some(1.0)),
268            _ => raise!(self, "failed to parse a flag in an elliptical arc"),
269        }
270    }
271
272    pub fn read_number(&mut self) -> Result<Option<Number>> {
273        self.reader.consume_whitespace();
274        let number = self
275            .reader
276            .capture(|reader| reader.consume_number())
277            .map(String::from);
278        match number {
279            Some(number) => match number.parse() {
280                Ok(number) => Ok(Some(number)),
281                _ => raise!(self, "failed to parse a number '{}'", number),
282            },
283            _ => Ok(None),
284        }
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use crate::node::element::path::data::Parser;
291    use crate::node::element::path::{Command, Data, Position};
292    use crate::node::Value;
293
294    #[test]
295    fn data_append() {
296        let mut data = Data::new();
297        data.append(Command::Line(Position::Absolute, (1, 2).into()));
298        data.append(Command::Close);
299        assert_eq!(Value::from(data).to_string(), "L1,2 z");
300    }
301
302    #[test]
303    fn data_into_value() {
304        let data = Data::new()
305            .line_to((1, 2))
306            .cubic_curve_by((1, 2.5, 3, 4, 5, 6))
307            .close();
308
309        assert_eq!(Value::from(data).to_string(), "L1,2 c1,2.5,3,4,5,6 z");
310    }
311
312    #[test]
313    fn data_parse() {
314        let data = Data::parse("M1,2 l3,4").unwrap();
315
316        assert_eq!(data.len(), 2);
317        match data[0] {
318            Command::Move(Position::Absolute, ref parameters) => {
319                assert_eq!(&parameters[..], &[1.0, 2.0])
320            }
321            _ => unreachable!(),
322        }
323        match data[1] {
324            Command::Line(Position::Relative, ref parameters) => {
325                assert_eq!(&parameters[..], &[3.0, 4.0])
326            }
327            _ => unreachable!(),
328        }
329    }
330
331    #[test]
332    fn parser_read_command() {
333        macro_rules! run(
334            ($content:expr) => ({
335                let mut parser = Parser::new($content);
336                parser.read_command().unwrap().unwrap()
337            });
338        );
339
340        macro_rules! test(
341            ($content:expr, $command:ident, $position:ident, $parameters:expr) => (
342                match run!($content) {
343                    Command::$command(Position::$position, parameters) => assert_eq!(&parameters[..], $parameters),
344                    _ => unreachable!(),
345                }
346            );
347            ($content:expr, $command:ident) => (
348                match run!($content) {
349                    Command::$command => {}
350                    _ => unreachable!(),
351                }
352            );
353        );
354
355        test!("M4,2", Move, Absolute, &[4.0, 2.0]);
356        test!("m4,\n2", Move, Relative, &[4.0, 2.0]);
357
358        test!("L7, 8  9", Line, Absolute, &[7.0, 8.0, 9.0]);
359        test!("l 7,8 \n9", Line, Relative, &[7.0, 8.0, 9.0]);
360
361        test!("H\t6,9", HorizontalLine, Absolute, &[6.0, 9.0]);
362        test!("h6,  \t9", HorizontalLine, Relative, &[6.0, 9.0]);
363
364        test!("V2.1,-3", VerticalLine, Absolute, &[2.1, -3.0]);
365        test!("v\n2.1 -3", VerticalLine, Relative, &[2.1, -3.0]);
366
367        test!("Q90.5 0", QuadraticCurve, Absolute, &[90.5, 0.0]);
368        test!("q90.5\n, 0", QuadraticCurve, Relative, &[90.5, 0.0]);
369
370        test!("T-1", SmoothQuadraticCurve, Absolute, &[-1.0]);
371        test!("t -1", SmoothQuadraticCurve, Relative, &[-1.0]);
372
373        test!("C0,1 0,2", CubicCurve, Absolute, &[0.0, 1.0, 0.0, 2.0]);
374        test!("c0 ,1 0,  2", CubicCurve, Relative, &[0.0, 1.0, 0.0, 2.0]);
375
376        test!("S42,0", SmoothCubicCurve, Absolute, &[42.0, 0.0]);
377        test!("s \t 42,0", SmoothCubicCurve, Relative, &[42.0, 0.0]);
378
379        test!(
380            "A1 1 2.6,0 0 0 -7",
381            EllipticalArc,
382            Absolute,
383            &[1.0, 1.0, 2.6, 0.0, 0.0, 0.0, -7.0]
384        );
385        test!(
386            "a1 1 2.6,0 0 0 -7",
387            EllipticalArc,
388            Relative,
389            &[1.0, 1.0, 2.6, 0.0, 0.0, 0.0, -7.0]
390        );
391        test!(
392            "a32 32 0 00.03-45.22",
393            EllipticalArc,
394            Relative,
395            &[32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22]
396        );
397        test!(
398            "a48 48 0 1148-48",
399            EllipticalArc,
400            Relative,
401            &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0]
402        );
403        test!(
404            "a82.6 82.6 0 0033.48-20.25",
405            EllipticalArc,
406            Relative,
407            &[82.6, 82.6, 0.0, 0.0, 0.0, 33.48, -20.25]
408        );
409        test!(
410            "a82.45 82.45 0 00-20.24 33.47",
411            EllipticalArc,
412            Relative,
413            &[82.45, 82.45, 0.0, 0.0, 0.0, -20.24, 33.47]
414        );
415        test!(
416            "a48 48 0 1148-48 48 48 0 01-48 48",
417            EllipticalArc,
418            Relative,
419            &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0, 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0]
420        );
421        test!(
422            "a48 48 0 1148-48 48 48 0 01-48 48 32 32 0 11.03-45.22",
423            EllipticalArc,
424            Relative,
425            &[
426                48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0, 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0,
427                32.0, 32.0, 0.0, 1.0, 1.0, 0.03, -45.22
428            ]
429        );
430        test!(
431            "a2.51 2.51 0 01.25.32",
432            EllipticalArc,
433            Relative,
434            &[2.51, 2.51, 0.0, 0.0, 1.0, 0.25, 0.32]
435        );
436        test!(
437            "a1 1 0 00.25.32",
438            EllipticalArc,
439            Relative,
440            &[1., 1., 0.0, 0.0, 0.0, 0.25, 0.32]
441        );
442        test!(
443            "a1 1 0 000.25.32",
444            EllipticalArc,
445            Relative,
446            &[1., 1., 0.0, 0.0, 0.0, 0.25, 0.32]
447        );
448
449        test!("Z", Close);
450        test!("z", Close);
451    }
452
453    #[test]
454    fn parser_read_parameters() {
455        macro_rules! test(
456            ($content:expr, $parameters:expr) => ({
457                let mut parser = Parser::new($content);
458                let parameters = parser.read_parameters().unwrap();
459                assert_eq!(&parameters[..], $parameters);
460            });
461        );
462
463        test!("1,2 3,4 5 6.7", &[1.0, 2.0, 3.0, 4.0, 5.0, 6.7]);
464        test!("4-3.1.3e2.4", &[4.0, -3.1, 0.3e2, 0.4]);
465    }
466
467    #[test]
468    fn parser_read_parameters_elliptical_arc() {
469        macro_rules! test(
470            ($content:expr, $parameters:expr) => ({
471                let mut parser = Parser::new($content);
472                let parameters = parser.read_parameters_elliptical_arc().unwrap();
473                assert_eq!(&parameters[..], $parameters);
474            });
475        );
476
477        test!(
478            "32 32 0 00.03-45.22",
479            &[32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22]
480        );
481        test!("48 48 0 1148-48", &[48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0]);
482    }
483
484    #[test]
485    fn parser_read_number() {
486        macro_rules! test(
487            ($content:expr, $value:expr) => ({
488                let mut parser = Parser::new($content);
489                assert_eq!(parser.read_number().unwrap().unwrap(), $value);
490            });
491        );
492
493        test!("0.30000000000000004", 0.3);
494        test!("1e-4", 1e-4);
495        test!("-1E2", -1e2);
496        test!("-0.00100E-002", -1e-5);
497    }
498}