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