1mod angle;
2mod error;
3mod length;
4mod options;
5mod parser;
6mod segment;
7mod stream;
8mod writer;
9
10pub use self::angle::*;
11pub use self::error::*;
12pub use self::length::*;
13pub use self::options::*;
14pub use self::parser::*;
15pub use self::segment::*;
16pub use self::stream::*;
17
18use float_cmp::ApproxEqUlps;
19use std::fmt;
20use std::io::Write;
21
22#[derive(Clone, PartialEq, Default)]
26pub struct Path(pub Vec<PathSegment>);
27
28impl Path {
29 #[inline]
31 pub fn new() -> Self {
32 Path(Vec::new())
33 }
34
35 #[inline]
37 pub fn with_capacity(capacity: usize) -> Self {
38 Path(Vec::with_capacity(capacity))
39 }
40
41 pub fn conv_to_absolute(&mut self) {
45 let mut prev_x = 0.0;
47 let mut prev_y = 0.0;
48
49 let mut prev_mx = 0.0;
53 let mut prev_my = 0.0;
54
55 let mut prev_cmd = PathCommand::MoveTo;
56 for seg in self.iter_mut() {
57 if seg.cmd() == PathCommand::ClosePath {
58 prev_x = prev_mx;
59 prev_y = prev_my;
60
61 seg.set_absolute(true);
62 continue;
63 }
64
65 let offset_x;
66 let offset_y;
67 if seg.is_relative() {
68 if seg.cmd() == PathCommand::MoveTo && prev_cmd == PathCommand::ClosePath {
69 offset_x = prev_mx;
70 offset_y = prev_my;
71 } else {
72 offset_x = prev_x;
73 offset_y = prev_y;
74 }
75 } else {
76 offset_x = 0.0;
77 offset_y = 0.0;
78 }
79
80 if seg.is_relative() {
81 shift_segment_data(seg, offset_x, offset_y);
82 }
83
84 if seg.cmd() == PathCommand::MoveTo {
85 prev_mx = seg.x().unwrap();
86 prev_my = seg.y().unwrap();
87 }
88
89 seg.set_absolute(true);
90
91 if seg.cmd() == PathCommand::HorizontalLineTo {
92 prev_x = seg.x().unwrap();
93 } else if seg.cmd() == PathCommand::VerticalLineTo {
94 prev_y = seg.y().unwrap();
95 } else {
96 prev_x = seg.x().unwrap();
97 prev_y = seg.y().unwrap();
98 }
99
100 prev_cmd = seg.cmd();
101 }
102 }
103
104 pub fn conv_to_relative(&mut self) {
108 let mut prev_x = 0.0;
112 let mut prev_y = 0.0;
113
114 let mut prev_mx = 0.0;
118 let mut prev_my = 0.0;
119
120 let mut prev_cmd = PathCommand::MoveTo;
121 for seg in self.iter_mut() {
122 if seg.cmd() == PathCommand::ClosePath {
123 prev_x = prev_mx;
124 prev_y = prev_my;
125
126 seg.set_absolute(false);
127 continue;
128 }
129
130 let offset_x;
131 let offset_y;
132 if seg.is_absolute() {
133 if seg.cmd() == PathCommand::MoveTo && prev_cmd == PathCommand::ClosePath {
134 offset_x = prev_mx;
135 offset_y = prev_my;
136 } else {
137 offset_x = prev_x;
138 offset_y = prev_y;
139 }
140 } else {
141 offset_x = 0.0;
142 offset_y = 0.0;
143 }
144
145 if seg.is_absolute() {
147 if seg.cmd() == PathCommand::HorizontalLineTo {
148 prev_x = seg.x().unwrap();
149 } else if seg.cmd() == PathCommand::VerticalLineTo {
150 prev_y = seg.y().unwrap();
151 } else {
152 prev_x = seg.x().unwrap();
153 prev_y = seg.y().unwrap();
154 }
155 } else {
156 if seg.cmd() == PathCommand::HorizontalLineTo {
157 prev_x += seg.x().unwrap();
158 } else if seg.cmd() == PathCommand::VerticalLineTo {
159 prev_y += seg.y().unwrap();
160 } else {
161 prev_x += seg.x().unwrap();
162 prev_y += seg.y().unwrap();
163 }
164 }
165
166 if seg.cmd() == PathCommand::MoveTo {
167 if seg.is_absolute() {
168 prev_mx = seg.x().unwrap();
169 prev_my = seg.y().unwrap();
170 } else {
171 prev_mx += seg.x().unwrap();
172 prev_my += seg.y().unwrap();
173 }
174 }
175
176 if seg.is_absolute() {
177 shift_segment_data(seg, -offset_x, -offset_y);
178 }
179
180 seg.set_absolute(false);
181
182 prev_cmd = seg.cmd();
183 }
184 }
185
186 #[inline]
188 pub fn push_move_to(&mut self, x: f64, y: f64) {
189 self.push(PathSegment::MoveTo { abs: true, x, y });
190 }
191
192 #[inline]
194 pub fn push_rel_move_to(&mut self, x: f64, y: f64) {
195 self.push(PathSegment::MoveTo { abs: false, x, y });
196 }
197
198 #[inline]
200 pub fn push_close_path(&mut self) {
201 self.push(PathSegment::ClosePath { abs: true });
202 }
203
204 #[inline]
206 pub fn push_rel_close_path(&mut self) {
207 self.push(PathSegment::ClosePath { abs: false });
208 }
209
210 #[inline]
212 pub fn push_line_to(&mut self, x: f64, y: f64) {
213 self.push(PathSegment::LineTo { abs: true, x, y });
214 }
215
216 #[inline]
218 pub fn push_rel_line_to(&mut self, x: f64, y: f64) {
219 self.push(PathSegment::LineTo { abs: false, x, y });
220 }
221
222 #[inline]
224 pub fn push_hline_to(&mut self, x: f64) {
225 self.push(PathSegment::HorizontalLineTo { abs: true, x });
226 }
227
228 #[inline]
230 pub fn push_rel_hline_to(&mut self, x: f64) {
231 self.push(PathSegment::HorizontalLineTo { abs: false, x });
232 }
233
234 #[inline]
236 pub fn push_vline_to(&mut self, y: f64) {
237 self.push(PathSegment::VerticalLineTo { abs: true, y });
238 }
239
240 #[inline]
242 pub fn push_rel_vline_to(&mut self, y: f64) {
243 self.push(PathSegment::VerticalLineTo { abs: false, y });
244 }
245
246 #[inline]
248 pub fn push_curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) {
249 self.push(PathSegment::CurveTo {
250 abs: true,
251 x1,
252 y1,
253 x2,
254 y2,
255 x,
256 y,
257 });
258 }
259
260 #[inline]
262 pub fn push_rel_curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) {
263 self.push(PathSegment::CurveTo {
264 abs: false,
265 x1,
266 y1,
267 x2,
268 y2,
269 x,
270 y,
271 });
272 }
273
274 #[inline]
276 pub fn push_smooth_curve_to(&mut self, x2: f64, y2: f64, x: f64, y: f64) {
277 self.push(PathSegment::SmoothCurveTo {
278 abs: true,
279 x2,
280 y2,
281 x,
282 y,
283 });
284 }
285
286 #[inline]
288 pub fn push_rel_smooth_curve_to(&mut self, x2: f64, y2: f64, x: f64, y: f64) {
289 self.push(PathSegment::SmoothCurveTo {
290 abs: false,
291 x2,
292 y2,
293 x,
294 y,
295 });
296 }
297
298 #[inline]
300 pub fn push_quad_to(&mut self, x1: f64, y1: f64, x: f64, y: f64) {
301 self.push(PathSegment::Quadratic {
302 abs: true,
303 x1,
304 y1,
305 x,
306 y,
307 });
308 }
309
310 #[inline]
312 pub fn push_rel_quad_to(&mut self, x1: f64, y1: f64, x: f64, y: f64) {
313 self.push(PathSegment::Quadratic {
314 abs: false,
315 x1,
316 y1,
317 x,
318 y,
319 });
320 }
321
322 #[inline]
324 pub fn push_smooth_quad_to(&mut self, x: f64, y: f64) {
325 self.push(PathSegment::SmoothQuadratic { abs: true, x, y });
326 }
327
328 #[inline]
330 pub fn push_rel_smooth_quad_to(&mut self, x: f64, y: f64) {
331 self.push(PathSegment::SmoothQuadratic { abs: false, x, y });
332 }
333
334 #[inline]
336 pub fn push_arc_to(
337 &mut self,
338 rx: f64,
339 ry: f64,
340 x_axis_rotation: f64,
341 large_arc: bool,
342 sweep: bool,
343 x: f64,
344 y: f64,
345 ) {
346 self.push(PathSegment::EllipticalArc {
347 abs: true,
348 rx,
349 ry,
350 x_axis_rotation,
351 large_arc,
352 sweep,
353 x,
354 y,
355 });
356 }
357
358 #[inline]
360 pub fn push_rel_arc_to(
361 &mut self,
362 rx: f64,
363 ry: f64,
364 x_axis_rotation: f64,
365 large_arc: bool,
366 sweep: bool,
367 x: f64,
368 y: f64,
369 ) {
370 self.push(PathSegment::EllipticalArc {
371 abs: false,
372 rx,
373 ry,
374 x_axis_rotation,
375 large_arc,
376 sweep,
377 x,
378 y,
379 });
380 }
381}
382
383fn shift_segment_data(d: &mut PathSegment, offset_x: f64, offset_y: f64) {
384 match *d {
385 PathSegment::MoveTo {
386 ref mut x,
387 ref mut y,
388 ..
389 } => {
390 *x += offset_x;
391 *y += offset_y;
392 }
393 PathSegment::LineTo {
394 ref mut x,
395 ref mut y,
396 ..
397 } => {
398 *x += offset_x;
399 *y += offset_y;
400 }
401 PathSegment::HorizontalLineTo { ref mut x, .. } => {
402 *x += offset_x;
403 }
404 PathSegment::VerticalLineTo { ref mut y, .. } => {
405 *y += offset_y;
406 }
407 PathSegment::CurveTo {
408 ref mut x1,
409 ref mut y1,
410 ref mut x2,
411 ref mut y2,
412 ref mut x,
413 ref mut y,
414 ..
415 } => {
416 *x1 += offset_x;
417 *y1 += offset_y;
418 *x2 += offset_x;
419 *y2 += offset_y;
420 *x += offset_x;
421 *y += offset_y;
422 }
423 PathSegment::SmoothCurveTo {
424 ref mut x2,
425 ref mut y2,
426 ref mut x,
427 ref mut y,
428 ..
429 } => {
430 *x2 += offset_x;
431 *y2 += offset_y;
432 *x += offset_x;
433 *y += offset_y;
434 }
435 PathSegment::Quadratic {
436 ref mut x1,
437 ref mut y1,
438 ref mut x,
439 ref mut y,
440 ..
441 } => {
442 *x1 += offset_x;
443 *y1 += offset_y;
444 *x += offset_x;
445 *y += offset_y;
446 }
447 PathSegment::SmoothQuadratic {
448 ref mut x,
449 ref mut y,
450 ..
451 } => {
452 *x += offset_x;
453 *y += offset_y;
454 }
455 PathSegment::EllipticalArc {
456 ref mut x,
457 ref mut y,
458 ..
459 } => {
460 *x += offset_x;
461 *y += offset_y;
462 }
463 PathSegment::ClosePath { .. } => {}
464 }
465}
466
467impl From<Vec<PathSegment>> for Path {
468 #[inline]
469 fn from(v: Vec<PathSegment>) -> Self {
470 Path(v)
471 }
472}
473
474impl ::std::ops::Deref for Path {
475 type Target = Vec<PathSegment>;
476
477 #[inline]
478 fn deref(&self) -> &Self::Target {
479 &self.0
480 }
481}
482
483impl ::std::ops::DerefMut for Path {
484 #[inline]
485 fn deref_mut(&mut self) -> &mut Self::Target {
486 &mut self.0
487 }
488}
489
490pub trait FuzzyEq<Rhs: ?Sized = Self> {
492 fn fuzzy_eq(&self, other: &Rhs) -> bool;
494
495 #[inline]
497 fn fuzzy_ne(&self, other: &Rhs) -> bool {
498 !self.fuzzy_eq(other)
499 }
500}
501
502impl<T: FuzzyEq> FuzzyEq for Vec<T> {
503 fn fuzzy_eq(&self, other: &Self) -> bool {
504 if self.len() != other.len() {
505 return false;
506 }
507
508 for (a, b) in self.iter().zip(other.iter()) {
509 if a.fuzzy_ne(b) {
510 return false;
511 }
512 }
513
514 true
515 }
516}
517
518pub trait WriteBuffer {
520 fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>);
522
523 fn write_buf(&self, buf: &mut Vec<u8>) {
525 self.write_buf_opt(&WriteOptions::default(), buf);
526 }
527
528 fn with_write_opt<'a>(&'a self, opt: &'a WriteOptions) -> DisplaySvg<'a, Self>
530 where
531 Self: Sized,
532 {
533 DisplaySvg { value: self, opt }
534 }
535}
536
537impl<T: WriteBuffer> WriteBuffer for Vec<T> {
538 fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
539 for (n, l) in self.iter().enumerate() {
540 l.write_buf_opt(opt, buf);
541 if n < self.len() - 1 {
542 opt.write_separator(buf);
543 }
544 }
545 }
546}
547
548pub struct DisplaySvg<'a, T: 'a + WriteBuffer> {
569 value: &'a T,
570 opt: &'a WriteOptions,
571}
572
573impl<'a, T: WriteBuffer> fmt::Debug for DisplaySvg<'a, T> {
574 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
575 write!(f, "{}", self)
577 }
578}
579
580impl<'a, T: WriteBuffer> fmt::Display for DisplaySvg<'a, T> {
581 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
582 use std::str;
583
584 let mut out = Vec::with_capacity(32);
585 self.value.write_buf_opt(self.opt, &mut out);
586 write!(f, "{}", str::from_utf8(&out).unwrap())
587 }
588}
589
590impl WriteBuffer for f64 {
591 fn write_buf_opt(&self, opt: &WriteOptions, buf: &mut Vec<u8>) {
592 write_num(self, opt.remove_leading_zero, buf);
593 }
594}
595
596impl FuzzyEq for f32 {
597 #[inline]
598 fn fuzzy_eq(&self, other: &f32) -> bool {
599 self.approx_eq_ulps(other, 4)
600 }
601}
602
603impl FuzzyEq for f64 {
604 #[inline]
605 fn fuzzy_eq(&self, other: &f64) -> bool {
606 self.approx_eq_ulps(other, 4)
607 }
608}
609
610impl FuzzyZero for f32 {
611 #[inline]
612 fn is_fuzzy_zero(&self) -> bool {
613 self.fuzzy_eq(&0.0)
614 }
615}
616
617impl FuzzyZero for f64 {
618 #[inline]
619 fn is_fuzzy_zero(&self) -> bool {
620 self.fuzzy_eq(&0.0)
621 }
622}
623
624fn write_num(num: &f64, rm_leading_zero: bool, buf: &mut Vec<u8>) {
625 if num.fract().is_fuzzy_zero() {
627 write!(buf, "{}", *num as i32).unwrap();
628 return;
629 }
630
631 let v = (num * 100_000_000_000.0).round() / 100_000_000_000.0;
635
636 let start_pos = buf.len();
637
638 write!(buf, "{}", v).unwrap();
639
640 if rm_leading_zero {
641 let mut has_dot = false;
642 let mut pos = 0;
643 for c in buf.iter().skip(start_pos) {
644 if *c == b'.' {
645 has_dot = true;
646 break;
647 }
648 pos += 1;
649 }
650
651 if has_dot && buf[start_pos + pos - 1] == b'0' {
652 if pos == 2 && num.is_sign_negative() {
653 buf.remove(start_pos + 1);
655 } else if pos == 1 && num.is_sign_positive() {
656 buf.remove(start_pos);
658 }
659 }
660 }
661}
662
663pub trait FuzzyZero: FuzzyEq {
665 fn is_fuzzy_zero(&self) -> bool;
667}
668
669#[cfg(test)]
670mod to_absolute {
671 use super::*;
672 use std::str::FromStr;
673
674 macro_rules! test {
675 ($name:ident, $in_text:expr, $out_text:expr) => {
676 #[test]
677 fn $name() {
678 let mut path = Path::from_str($in_text).unwrap();
679 path.conv_to_absolute();
680 assert_eq!(path.to_string(), $out_text);
681 }
682 };
683 }
684
685 test!(line_to, "m 10 20 l 20 20", "M 10 20 L 30 40");
686
687 test!(close_path, "m 10 20 l 20 20 z", "M 10 20 L 30 40 Z");
688
689 test!(implicit_line_to, "m 10 20 20 20", "M 10 20 L 30 40");
691
692 test!(
693 hline_vline,
694 "m 10 20 v 10 h 10 l 10 10",
695 "M 10 20 V 30 H 20 L 30 40"
696 );
697
698 test!(
699 curve,
700 "m 10 20 c 10 10 10 10 10 10",
701 "M 10 20 C 20 30 20 30 20 30"
702 );
703
704 test!(
705 move_to_1,
706 "m 10 20 l 10 10 m 10 10 l 10 10",
707 "M 10 20 L 20 30 M 30 40 L 40 50"
708 );
709
710 test!(
711 move_to_2,
712 "m 10 20 l 10 10 z m 10 10 l 10 10",
713 "M 10 20 L 20 30 Z M 20 30 L 30 40"
714 );
715
716 test!(
717 move_to_3,
718 "m 10 20 l 10 10 Z m 10 10 l 10 10",
719 "M 10 20 L 20 30 Z M 20 30 L 30 40"
720 );
721
722 test!(
724 move_to_4,
725 "m 10 20 l 10 10 Z l 10 10",
726 "M 10 20 L 20 30 Z L 20 30"
727 );
728
729 test!(
730 smooth_curve,
731 "m 10 20 s 10 10 10 10",
732 "M 10 20 S 20 30 20 30"
733 );
734
735 test!(quad, "m 10 20 q 10 10 10 10", "M 10 20 Q 20 30 20 30");
736
737 test!(
738 arc_mixed,
739 "M 30 150 a 40 40 0 0 1 65 50 Z m 30 30 A 20 20 0 0 0 125 230 Z \
740 m 40 24 a 20 20 0 0 1 65 50 z",
741 "M 30 150 A 40 40 0 0 1 95 200 Z M 60 180 A 20 20 0 0 0 125 230 Z \
742 M 100 204 A 20 20 0 0 1 165 254 Z"
743 );
744}
745
746#[cfg(test)]
747mod to_relative {
748 use super::*;
749 use std::str::FromStr;
750
751 macro_rules! test {
752 ($name:ident, $in_text:expr, $out_text:expr) => {
753 #[test]
754 fn $name() {
755 let mut path = Path::from_str($in_text).unwrap();
756 path.conv_to_relative();
757 assert_eq!(path.to_string(), $out_text);
758 }
759 };
760 }
761
762 test!(line_to, "M 10 20 L 30 40", "m 10 20 l 20 20");
763
764 test!(close_path, "M 10 20 L 30 40 Z", "m 10 20 l 20 20 z");
765
766 test!(implicit_line_to, "M 10 20 30 40", "m 10 20 l 20 20");
767
768 test!(
769 hline_vline,
770 "M 10 20 V 30 H 20 L 30 40",
771 "m 10 20 v 10 h 10 l 10 10"
772 );
773
774 test!(
775 curve,
776 "M 10 20 C 20 30 20 30 20 30",
777 "m 10 20 c 10 10 10 10 10 10"
778 );
779
780 test!(
781 move_to_1,
782 "M 10 20 L 20 30 M 30 40 L 40 50",
783 "m 10 20 l 10 10 m 10 10 l 10 10"
784 );
785
786 test!(
787 move_to_2,
788 "M 10 20 L 20 30 Z M 20 30 L 30 40",
789 "m 10 20 l 10 10 z m 10 10 l 10 10"
790 );
791
792 test!(
793 move_to_3,
794 "M 10 20 L 20 30 z M 20 30 L 30 40",
795 "m 10 20 l 10 10 z m 10 10 l 10 10"
796 );
797
798 test!(
800 move_to_4,
801 "M 10 20 L 20 30 Z L 20 30",
802 "m 10 20 l 10 10 z l 10 10"
803 );
804
805 test!(
806 smooth_curve,
807 "M 10 20 S 20 30 20 30",
808 "m 10 20 s 10 10 10 10"
809 );
810
811 test!(quad, "M 10 20 Q 20 30 20 30", "m 10 20 q 10 10 10 10");
812
813 test!(
814 arc_mixed,
815 "M 30 150 a 40 40 0 0 1 65 50 Z m 30 30 A 20 20 0 0 0 125 230 Z \
816 m 40 24 a 20 20 0 0 1 65 50 z",
817 "m 30 150 a 40 40 0 0 1 65 50 z m 30 30 a 20 20 0 0 0 65 50 z \
818 m 40 24 a 20 20 0 0 1 65 50 z"
819 );
820}