1 use {
2     Path,
3     PathCommand,
4     PathSegment,
5     WriteBuffer,
6     WriteOptions,
7 };
8 
9 struct PrevCmd {
10     cmd: PathCommand,
11     absolute: bool,
12     implicit: bool,
13 }
14 
15 impl WriteBuffer for Path {
write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>)16     fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
17         if self.is_empty() {
18             return;
19         }
20 
21         let mut prev_cmd: Option<PrevCmd> = None;
22         let mut prev_coord_has_dot = false;
23 
24         for seg in self.iter() {
25             let is_written = write_cmd(seg, &mut prev_cmd, opt, buf);
26             write_segment(seg, is_written, &mut prev_coord_has_dot, opt, buf);
27         }
28 
29         if !opt.use_compact_path_notation {
30             let len = buf.len();
31             buf.truncate(len - 1);
32         }
33     }
34 }
35 
write_cmd( seg: &PathSegment, prev_cmd: &mut Option<PrevCmd>, opt: &WriteOptions, buf: &mut Vec<u8> ) -> bool36 fn write_cmd(
37     seg: &PathSegment,
38     prev_cmd: &mut Option<PrevCmd>,
39     opt: &WriteOptions,
40     buf: &mut Vec<u8>
41 ) -> bool {
42     let mut print_cmd = true;
43     if opt.remove_duplicated_path_commands {
44         // check that previous command is the same as current
45         if let Some(ref pcmd) = *prev_cmd {
46             // MoveTo commands can't be skipped
47             if pcmd.cmd != PathCommand::MoveTo {
48                 if seg.cmd() == pcmd.cmd && seg.is_absolute() == pcmd.absolute {
49                     print_cmd = false;
50                 }
51             }
52         }
53     }
54 
55     let mut is_implicit = false;
56     if opt.use_implicit_lineto_commands {
57 
58         let check_implicit = || {
59             if let Some(ref pcmd) = *prev_cmd {
60                 if seg.is_absolute() != pcmd.absolute {
61                     return false;
62                 }
63 
64                 if pcmd.implicit {
65                     if seg.cmd() == PathCommand::LineTo {
66                         return true;
67                     }
68                 } else if    pcmd.cmd  == PathCommand::MoveTo
69                           && seg.cmd() == PathCommand::LineTo {
70                     // if current segment is LineTo and previous was MoveTo
71                     return true;
72                 }
73             }
74 
75             false
76         };
77 
78         if check_implicit() {
79             is_implicit = true;
80             print_cmd = false;
81         }
82     }
83 
84     *prev_cmd = Some(PrevCmd {
85         cmd: seg.cmd(),
86         absolute: seg.is_absolute(),
87         implicit: is_implicit,
88     });
89 
90     if !print_cmd {
91         // we do not update 'prev_cmd' if we do not wrote it
92         return false;
93     }
94 
95     write_cmd_char(seg, buf);
96 
97     if !(seg.cmd() == PathCommand::ClosePath || opt.use_compact_path_notation) {
98         buf.push(b' ');
99     }
100 
101     true
102 }
103 
write_cmd_char(seg: &PathSegment, buf: &mut Vec<u8>)104 pub fn write_cmd_char(seg: &PathSegment, buf: &mut Vec<u8>) {
105     let cmd: u8 = if seg.is_absolute() {
106         match seg.cmd() {
107             PathCommand::MoveTo => b'M',
108             PathCommand::LineTo => b'L',
109             PathCommand::HorizontalLineTo => b'H',
110             PathCommand::VerticalLineTo => b'V',
111             PathCommand::CurveTo => b'C',
112             PathCommand::SmoothCurveTo => b'S',
113             PathCommand::Quadratic => b'Q',
114             PathCommand::SmoothQuadratic => b'T',
115             PathCommand::EllipticalArc => b'A',
116             PathCommand::ClosePath => b'Z',
117         }
118     } else {
119         match seg.cmd() {
120             PathCommand::MoveTo => b'm',
121             PathCommand::LineTo => b'l',
122             PathCommand::HorizontalLineTo => b'h',
123             PathCommand::VerticalLineTo => b'v',
124             PathCommand::CurveTo => b'c',
125             PathCommand::SmoothCurveTo => b's',
126             PathCommand::Quadratic => b'q',
127             PathCommand::SmoothQuadratic => b't',
128             PathCommand::EllipticalArc => b'a',
129             PathCommand::ClosePath => b'z',
130         }
131     };
132     buf.push(cmd);
133 }
134 
write_segment( data: &PathSegment, is_written: bool, prev_coord_has_dot: &mut bool, opt: &WriteOptions, buf: &mut Vec<u8> )135 pub fn write_segment(
136     data: &PathSegment,
137     is_written: bool,
138     prev_coord_has_dot: &mut bool,
139     opt: &WriteOptions,
140     buf: &mut Vec<u8>
141 ) {
142     match *data {
143           PathSegment::MoveTo { x, y, .. }
144         | PathSegment::LineTo { x, y, .. }
145         | PathSegment::SmoothQuadratic { x, y, .. } => {
146             write_coords(&[x, y], is_written, prev_coord_has_dot, opt, buf);
147         }
148 
149         PathSegment::HorizontalLineTo { x, .. } => {
150             write_coords(&[x], is_written, prev_coord_has_dot, opt, buf);
151         }
152 
153         PathSegment::VerticalLineTo { y, .. } => {
154             write_coords(&[y], is_written, prev_coord_has_dot, opt, buf);
155         }
156 
157         PathSegment::CurveTo { x1, y1, x2, y2, x, y, .. } => {
158             write_coords(&[x1, y1, x2, y2, x, y], is_written,
159                          prev_coord_has_dot, opt, buf);
160         }
161 
162         PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
163             write_coords(&[x2, y2, x, y], is_written, prev_coord_has_dot, opt, buf);
164         }
165 
166         PathSegment::Quadratic { x1, y1, x, y, .. } => {
167             write_coords(&[x1, y1, x, y], is_written, prev_coord_has_dot, opt, buf);
168         }
169 
170         PathSegment::EllipticalArc { rx, ry, x_axis_rotation, large_arc, sweep, x, y, .. } => {
171             write_coords(&[rx, ry, x_axis_rotation], is_written,
172                          prev_coord_has_dot, opt, buf);
173 
174             if opt.use_compact_path_notation {
175                 // flags must always have a space before it
176                 buf.push(b' ');
177             }
178 
179             write_flag(large_arc, buf);
180             if !opt.join_arc_to_flags {
181                 buf.push(b' ');
182             }
183             write_flag(sweep, buf);
184             if !opt.join_arc_to_flags {
185                 buf.push(b' ');
186             }
187 
188             // reset, because flags can't have dots
189             *prev_coord_has_dot = false;
190 
191             // 'is_explicit_cmd' is always 'true'
192             // because it's relevant only for first coordinate of the segment
193             write_coords(&[x, y], true, prev_coord_has_dot, opt, buf);
194         }
195         PathSegment::ClosePath { .. } => {
196             if !opt.use_compact_path_notation {
197                 buf.push(b' ');
198             }
199         }
200     }
201 }
202 
write_coords( coords: &[f64], is_explicit_cmd: bool, prev_coord_has_dot: &mut bool, opt: &WriteOptions, buf: &mut Vec<u8> )203 fn write_coords(
204     coords: &[f64],
205     is_explicit_cmd: bool,
206     prev_coord_has_dot: &mut bool,
207     opt: &WriteOptions,
208     buf: &mut Vec<u8>
209 ) {
210     if opt.use_compact_path_notation {
211         for (i, num) in coords.iter().enumerate() {
212             let start_pos = buf.len() - 1;
213 
214             num.write_buf_opt(opt, buf);
215 
216             let c = buf[start_pos + 1];
217 
218             let write_space = if !*prev_coord_has_dot && c == b'.' {
219                 !(i == 0 && is_explicit_cmd)
220             } else if i == 0 && is_explicit_cmd {
221                 false
222             } else if (c as char).is_digit(10) {
223                 true
224             } else {
225                 false
226             };
227 
228             if write_space {
229                 buf.insert(start_pos + 1, b' ');
230             }
231 
232             *prev_coord_has_dot = false;
233             for c in buf.iter().skip(start_pos) {
234                 if *c == b'.' {
235                     *prev_coord_has_dot = true;
236                     break;
237                 }
238             }
239         }
240     } else {
241         for num in coords.iter() {
242             num.write_buf_opt(opt, buf);
243             buf.push(b' ');
244         }
245     }
246 }
247 
write_flag(flag: bool, buf: &mut Vec<u8>)248 fn write_flag(flag: bool, buf: &mut Vec<u8>) {
249     buf.push(if flag { b'1' } else { b'0' });
250 }
251 
252 impl_display!(Path);
253 impl_debug_from_display!(Path);
254 
255 #[cfg(test)]
256 mod tests {
257     use std::str::FromStr;
258 
259     use super::*;
260     use WriteOptions;
261 
262     #[test]
write_1()263     fn write_1() {
264         let mut path = Path::new();
265         path.push(PathSegment::MoveTo { abs: true, x: 10.0, y: 20.0 });
266         path.push(PathSegment::LineTo { abs: true, x: 10.0, y: 20.0 });
267         assert_eq!(path.to_string(), "M 10 20 L 10 20");
268     }
269 
270     #[test]
write_2()271     fn write_2() {
272         let path = Path::from_str("M 10 20 l 10 20").unwrap();
273         assert_eq!(path.to_string(), "M 10 20 l 10 20");
274     }
275 
276     #[test]
write_3()277     fn write_3() {
278         let path = Path::from_str(
279             "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 \
280              S 130 140 150 160 Q 170 180 190 200 T 210 220 \
281              A 50 50 30 1 1 230 240 Z").unwrap();
282         assert_eq!(path.to_string(),
283             "M 10 20 L 30 40 H 50 V 60 C 70 80 90 100 110 120 \
284              S 130 140 150 160 Q 170 180 190 200 T 210 220 \
285              A 50 50 30 1 1 230 240 Z");
286     }
287 
288     #[test]
write_4()289     fn write_4() {
290         let path = Path::from_str(
291             "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 \
292              s 130 140 150 160 q 170 180 190 200 t 210 220 \
293              a 50 50 30 1 1 230 240 z").unwrap();
294         assert_eq!(path.to_string(),
295             "m 10 20 l 30 40 h 50 v 60 c 70 80 90 100 110 120 \
296              s 130 140 150 160 q 170 180 190 200 t 210 220 \
297              a 50 50 30 1 1 230 240 z");
298     }
299 
300     #[test]
write_5()301     fn write_5() {
302         let path = Path::from_str("").unwrap();
303         assert_eq!(path.to_string(), "");
304     }
305 
306     macro_rules! test_write_opt {
307         ($name:ident, $in_text:expr, $out_text:expr, $flag:ident) => (
308             #[test]
309             fn $name() {
310                 let path = Path::from_str($in_text).unwrap();
311 
312                 let mut opt = WriteOptions::default();
313                 opt.$flag = true;
314 
315                 assert_eq!(path.with_write_opt(&opt).to_string(), $out_text);
316             }
317         )
318     }
319 
320     test_write_opt!(write_6,
321         "M 10 20 L 30 40 L 50 60 l 70 80",
322         "M 10 20 L 30 40 50 60 l 70 80",
323         remove_duplicated_path_commands);
324 
325     test_write_opt!(write_7,
326         "M 10 20 30 40 50 60",
327         "M 10 20 L 30 40 50 60",
328         remove_duplicated_path_commands);
329 
330     test_write_opt!(write_8,
331         "M 10 20 L 30 40",
332         "M10 20L30 40",
333         use_compact_path_notation);
334 
335     test_write_opt!(write_9,
336         "M 10 20 V 30 H 40 V 50 H 60 Z",
337         "M10 20V30H40V50H60Z",
338         use_compact_path_notation);
339 
340     #[test]
write_10()341     fn write_10() {
342         let path = Path::from_str("M 10 -20 A 5.5 0.3 -4 1 1 0 -0.1").unwrap();
343 
344         let mut opt = WriteOptions::default();
345         opt.use_compact_path_notation = true;
346         opt.join_arc_to_flags = true;
347         opt.remove_leading_zero = true;
348 
349         assert_eq!(path.with_write_opt(&opt).to_string(), "M10-20A5.5.3-4 110-.1");
350     }
351 
352     test_write_opt!(write_11,
353         "M 10-10 a 1 1 0 1 1 -1 1",
354         "M10-10a1 1 0 1 1 -1 1",
355         use_compact_path_notation);
356 
357     test_write_opt!(write_12,
358         "M 10-10 a 1 1 0 1 1 0.1 1",
359         "M10-10a1 1 0 1 1 0.1 1",
360         use_compact_path_notation);
361 
362     test_write_opt!(write_13,
363         "M 10 20 L 30 40 L 50 60 H 10",
364         "M 10 20 30 40 50 60 H 10",
365         use_implicit_lineto_commands);
366 
367     // should be ignored, because of different 'absolute' values
368     test_write_opt!(write_14,
369         "M 10 20 l 30 40 L 50 60",
370         "M 10 20 l 30 40 L 50 60",
371         use_implicit_lineto_commands);
372 
373     test_write_opt!(write_15,
374         "M 10 20 L 30 40 l 50 60 L 50 60",
375         "M 10 20 30 40 l 50 60 L 50 60",
376         use_implicit_lineto_commands);
377 
378     test_write_opt!(write_16,
379         "M 10 20 L 30 40 l 50 60",
380         "M 10 20 30 40 l 50 60",
381         use_implicit_lineto_commands);
382 
383     test_write_opt!(write_17,
384         "M 10 20 L 30 40 L 50 60 M 10 20 L 30 40 L 50 60",
385         "M 10 20 30 40 50 60 M 10 20 30 40 50 60",
386         use_implicit_lineto_commands);
387 
388     #[test]
write_18()389     fn write_18() {
390         let path = Path::from_str("M 10 20 L 30 40 L 50 60 M 10 20 L 30 40 L 50 60").unwrap();
391 
392         let mut opt = WriteOptions::default();
393         opt.use_implicit_lineto_commands = true;
394         opt.remove_duplicated_path_commands = true;
395 
396         assert_eq!(path.with_write_opt(&opt).to_string(), "M 10 20 30 40 50 60 M 10 20 30 40 50 60");
397     }
398 
399     #[test]
write_19()400     fn write_19() {
401         let path = Path::from_str("m10 20 A 10 10 0 1 0 0 0 A 2 2 0 1 0 2 0").unwrap();
402 
403         let mut opt = WriteOptions::default();
404         opt.use_compact_path_notation = true;
405         opt.remove_duplicated_path_commands = true;
406         opt.remove_leading_zero = true;
407 
408         // may generate as 'm10 20A10 10 0 1 0 0 0 2 2 0 1 0  2 0' <- two spaces
409 
410         assert_eq!(path.with_write_opt(&opt).to_string(), "m10 20A10 10 0 1 0 0 0 2 2 0 1 0 2 0");
411     }
412 
413     #[test]
write_20()414     fn write_20() {
415         let path = Path::from_str("M 0.1 0.1 L 1 0.1 2 -0.1").unwrap();
416 
417         let mut opt = WriteOptions::default();
418         opt.use_compact_path_notation = true;
419         opt.remove_duplicated_path_commands = true;
420         opt.remove_leading_zero = true;
421 
422         assert_eq!(path.with_write_opt(&opt).to_string(), "M.1.1L1 .1 2-.1");
423     }
424 
425     test_write_opt!(write_21,
426         "M 10 20 M 30 40 M 50 60 L 30 40",
427         "M 10 20 M 30 40 M 50 60 L 30 40",
428         remove_duplicated_path_commands);
429 }
430