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