1use alloc::vec::Vec;
7use core::f64::consts::PI;
8use core::fmt::{self, Display, Formatter};
9#[cfg(feature = "std")]
11use std::error::Error;
12#[cfg(feature = "std")]
13use std::io::{self, Write};
14
15use crate::{Arc, BezPath, ParamCurve, PathEl, PathSeg, Point, Vec2};
16
17#[cfg(not(feature = "std"))]
18use crate::common::FloatFuncs;
19
20#[derive(Clone, Copy, Debug)]
24#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct SvgArc {
27 pub from: Point,
29 pub to: Point,
31 pub radii: Vec2,
34 pub x_rotation: f64,
36 pub large_arc: bool,
38 pub sweep: bool,
40}
41
42impl BezPath {
43 pub fn from_path_segments(segments: impl Iterator<Item = PathSeg>) -> BezPath {
46 let mut path_elements = Vec::new();
47 let mut current_pos = None;
48
49 for segment in segments {
50 let start = segment.start();
51 if Some(start) != current_pos {
52 path_elements.push(PathEl::MoveTo(start));
53 }
54 path_elements.push(match segment {
55 PathSeg::Line(l) => PathEl::LineTo(l.p1),
56 PathSeg::Quad(q) => PathEl::QuadTo(q.p1, q.p2),
57 PathSeg::Cubic(c) => PathEl::CurveTo(c.p1, c.p2, c.p3),
58 });
59
60 current_pos = Some(segment.end());
61 }
62
63 BezPath::from_vec(path_elements)
64 }
65
66 #[cfg(feature = "std")]
71 pub fn to_svg(&self) -> String {
72 let mut buffer = Vec::new();
73 self.write_to(&mut buffer).unwrap();
74 String::from_utf8(buffer).unwrap()
75 }
76
77 #[cfg(feature = "std")]
79 pub fn write_to<W: Write>(&self, mut writer: W) -> io::Result<()> {
80 for (i, el) in self.elements().iter().enumerate() {
81 if i > 0 {
82 write!(writer, " ")?;
83 }
84 match *el {
85 PathEl::MoveTo(p) => write!(writer, "M{},{}", p.x, p.y)?,
86 PathEl::LineTo(p) => write!(writer, "L{},{}", p.x, p.y)?,
87 PathEl::QuadTo(p1, p2) => write!(writer, "Q{},{} {},{}", p1.x, p1.y, p2.x, p2.y)?,
88 PathEl::CurveTo(p1, p2, p3) => write!(
89 writer,
90 "C{},{} {},{} {},{}",
91 p1.x, p1.y, p2.x, p2.y, p3.x, p3.y
92 )?,
93 PathEl::ClosePath => write!(writer, "Z")?,
94 }
95 }
96
97 Ok(())
98 }
99
100 pub fn from_svg(data: &str) -> Result<BezPath, SvgParseError> {
106 let mut lexer = SvgLexer::new(data);
107 let mut path = BezPath::new();
108 let mut last_cmd = 0;
109 let mut last_ctrl = None;
110 let mut first_pt = Point::ORIGIN;
111 let mut implicit_moveto = None;
112 while let Some(c) = lexer.get_cmd(last_cmd) {
113 if c != b'm' && c != b'M' {
114 if path.elements().is_empty() {
115 return Err(SvgParseError::UninitializedPath);
116 }
117
118 if let Some(pt) = implicit_moveto.take() {
119 path.move_to(pt);
120 }
121 }
122 match c {
123 b'm' | b'M' => {
124 implicit_moveto = None;
125 let pt = lexer.get_maybe_relative(c)?;
126 path.move_to(pt);
127 lexer.last_pt = pt;
128 first_pt = pt;
129 last_ctrl = Some(pt);
130 last_cmd = c - (b'M' - b'L');
131 }
132 b'l' | b'L' => {
133 let pt = lexer.get_maybe_relative(c)?;
134 path.line_to(pt);
135 lexer.last_pt = pt;
136 last_ctrl = Some(pt);
137 last_cmd = c;
138 }
139 b'h' | b'H' => {
140 let mut x = lexer.get_number()?;
141 lexer.opt_comma();
142 if c == b'h' {
143 x += lexer.last_pt.x;
144 }
145 let pt = Point::new(x, lexer.last_pt.y);
146 path.line_to(pt);
147 lexer.last_pt = pt;
148 last_ctrl = Some(pt);
149 last_cmd = c;
150 }
151 b'v' | b'V' => {
152 let mut y = lexer.get_number()?;
153 lexer.opt_comma();
154 if c == b'v' {
155 y += lexer.last_pt.y;
156 }
157 let pt = Point::new(lexer.last_pt.x, y);
158 path.line_to(pt);
159 lexer.last_pt = pt;
160 last_ctrl = Some(pt);
161 last_cmd = c;
162 }
163 b'q' | b'Q' => {
164 let p1 = lexer.get_maybe_relative(c)?;
165 let p2 = lexer.get_maybe_relative(c)?;
166 path.quad_to(p1, p2);
167 last_ctrl = Some(p1);
168 lexer.last_pt = p2;
169 last_cmd = c;
170 }
171 b't' | b'T' => {
172 let p1 = match last_ctrl {
173 Some(ctrl) => (2.0 * lexer.last_pt.to_vec2() - ctrl.to_vec2()).to_point(),
174 None => lexer.last_pt,
175 };
176 let p2 = lexer.get_maybe_relative(c)?;
177 path.quad_to(p1, p2);
178 last_ctrl = Some(p1);
179 lexer.last_pt = p2;
180 last_cmd = c;
181 }
182 b'c' | b'C' => {
183 let p1 = lexer.get_maybe_relative(c)?;
184 let p2 = lexer.get_maybe_relative(c)?;
185 let p3 = lexer.get_maybe_relative(c)?;
186 path.curve_to(p1, p2, p3);
187 last_ctrl = Some(p2);
188 lexer.last_pt = p3;
189 last_cmd = c;
190 }
191 b's' | b'S' => {
192 let p1 = match last_ctrl {
193 Some(ctrl) => (2.0 * lexer.last_pt.to_vec2() - ctrl.to_vec2()).to_point(),
194 None => lexer.last_pt,
195 };
196 let p2 = lexer.get_maybe_relative(c)?;
197 let p3 = lexer.get_maybe_relative(c)?;
198 path.curve_to(p1, p2, p3);
199 last_ctrl = Some(p2);
200 lexer.last_pt = p3;
201 last_cmd = c;
202 }
203 b'a' | b'A' => {
204 let radii = lexer.get_number_pair()?;
205 let x_rotation = lexer.get_number()?.to_radians();
206 lexer.opt_comma();
207 let large_arc = lexer.get_flag()?;
208 lexer.opt_comma();
209 let sweep = lexer.get_flag()?;
210 lexer.opt_comma();
211 let p = lexer.get_maybe_relative(c)?;
212 let svg_arc = SvgArc {
213 from: lexer.last_pt,
214 to: p,
215 radii: radii.to_vec2(),
216 x_rotation,
217 large_arc,
218 sweep,
219 };
220
221 match Arc::from_svg_arc(&svg_arc) {
222 Some(arc) => {
223 arc.to_cubic_beziers(0.1, |p1, p2, p3| {
225 path.curve_to(p1, p2, p3);
226 });
227 }
228 None => {
229 path.line_to(p);
230 }
231 }
232
233 last_ctrl = Some(p);
234 lexer.last_pt = p;
235 last_cmd = c;
236 }
237 b'z' | b'Z' => {
238 path.close_path();
239 lexer.last_pt = first_pt;
240 implicit_moveto = Some(first_pt);
241 }
242 _ => return Err(SvgParseError::UnknownCommand(c as char)),
243 }
244 }
245 Ok(path)
246 }
247}
248
249#[derive(Debug)]
251#[non_exhaustive]
252pub enum SvgParseError {
253 Wrong,
255 UnexpectedEof,
257 UnknownCommand(char),
259 UninitializedPath,
261}
262
263impl Display for SvgParseError {
264 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
265 match self {
266 SvgParseError::Wrong => write!(f, "Unable to parse a number"),
267 SvgParseError::UnexpectedEof => write!(f, "Unexpected EOF"),
268 SvgParseError::UnknownCommand(letter) => write!(f, "Unknown command, \"{letter}\""),
269 SvgParseError::UninitializedPath => {
270 write!(f, "Uninitialized path (missing moveto command)")
271 }
272 }
273 }
274}
275
276#[cfg(feature = "std")]
277impl Error for SvgParseError {}
278
279struct SvgLexer<'a> {
280 data: &'a str,
281 ix: usize,
282 pub last_pt: Point,
283}
284
285impl SvgLexer<'_> {
286 fn new(data: &str) -> SvgLexer {
287 SvgLexer {
288 data,
289 ix: 0,
290 last_pt: Point::ORIGIN,
291 }
292 }
293
294 fn skip_ws(&mut self) {
295 while let Some(&c) = self.data.as_bytes().get(self.ix) {
296 if !(c == b' ' || c == 9 || c == 10 || c == 12 || c == 13) {
297 break;
298 }
299 self.ix += 1;
300 }
301 }
302
303 fn get_cmd(&mut self, last_cmd: u8) -> Option<u8> {
304 self.skip_ws();
305 if let Some(c) = self.get_byte() {
306 if c.is_ascii_lowercase() || c.is_ascii_uppercase() {
307 return Some(c);
308 } else if last_cmd != 0 && (c == b'-' || c == b'.' || c.is_ascii_digit()) {
309 self.unget();
311 return Some(last_cmd);
312 } else {
313 self.unget();
314 }
315 }
316 None
317 }
318
319 fn get_byte(&mut self) -> Option<u8> {
320 self.data.as_bytes().get(self.ix).map(|&c| {
321 self.ix += 1;
322 c
323 })
324 }
325
326 fn unget(&mut self) {
327 self.ix -= 1;
328 }
329
330 fn get_number(&mut self) -> Result<f64, SvgParseError> {
331 self.skip_ws();
332 let start = self.ix;
333 let c = self.get_byte().ok_or(SvgParseError::UnexpectedEof)?;
334 if !(c == b'-' || c == b'+') {
335 self.unget();
336 }
337 let mut digit_count = 0;
338 let mut seen_period = false;
339 while let Some(c) = self.get_byte() {
340 if c.is_ascii_digit() {
341 digit_count += 1;
342 } else if c == b'.' && !seen_period {
343 seen_period = true;
344 } else {
345 self.unget();
346 break;
347 }
348 }
349 if let Some(c) = self.get_byte() {
350 if c == b'e' || c == b'E' {
351 let mut c = self.get_byte().ok_or(SvgParseError::Wrong)?;
352 if c == b'-' || c == b'+' {
353 c = self.get_byte().ok_or(SvgParseError::Wrong)?;
354 }
355 if !c.is_ascii_digit() {
356 return Err(SvgParseError::Wrong);
357 }
358 while let Some(c) = self.get_byte() {
359 if !c.is_ascii_digit() {
360 self.unget();
361 break;
362 }
363 }
364 } else {
365 self.unget();
366 }
367 }
368 if digit_count > 0 {
369 self.data[start..self.ix]
370 .parse()
371 .map_err(|_| SvgParseError::Wrong)
372 } else {
373 Err(SvgParseError::Wrong)
374 }
375 }
376
377 fn get_flag(&mut self) -> Result<bool, SvgParseError> {
378 self.skip_ws();
379 match self.get_byte().ok_or(SvgParseError::UnexpectedEof)? {
380 b'0' => Ok(false),
381 b'1' => Ok(true),
382 _ => Err(SvgParseError::Wrong),
383 }
384 }
385
386 fn get_number_pair(&mut self) -> Result<Point, SvgParseError> {
387 let x = self.get_number()?;
388 self.opt_comma();
389 let y = self.get_number()?;
390 self.opt_comma();
391 Ok(Point::new(x, y))
392 }
393
394 fn get_maybe_relative(&mut self, cmd: u8) -> Result<Point, SvgParseError> {
395 let pt = self.get_number_pair()?;
396 if cmd.is_ascii_lowercase() {
397 Ok(self.last_pt + pt.to_vec2())
398 } else {
399 Ok(pt)
400 }
401 }
402
403 fn opt_comma(&mut self) {
404 self.skip_ws();
405 if let Some(c) = self.get_byte() {
406 if c != b',' {
407 self.unget();
408 }
409 }
410 }
411}
412
413impl SvgArc {
414 pub fn is_straight_line(&self) -> bool {
418 self.radii.x.abs() <= 1e-5 || self.radii.y.abs() <= 1e-5 || self.from == self.to
419 }
420}
421
422impl Arc {
423 pub fn from_svg_arc(arc: &SvgArc) -> Option<Arc> {
427 if arc.is_straight_line() {
429 return None;
430 }
431
432 let mut rx = arc.radii.x.abs();
433 let mut ry = arc.radii.y.abs();
434
435 let xr = arc.x_rotation % (2.0 * PI);
436 let (sin_phi, cos_phi) = xr.sin_cos();
437 let hd_x = (arc.from.x - arc.to.x) * 0.5;
438 let hd_y = (arc.from.y - arc.to.y) * 0.5;
439 let hs_x = (arc.from.x + arc.to.x) * 0.5;
440 let hs_y = (arc.from.y + arc.to.y) * 0.5;
441
442 let p = Vec2::new(
444 cos_phi * hd_x + sin_phi * hd_y,
445 -sin_phi * hd_x + cos_phi * hd_y,
446 );
447
448 let rf = p.x * p.x / (rx * rx) + p.y * p.y / (ry * ry);
455 if rf > 1.0 {
456 let scale = rf.sqrt();
457 rx *= scale;
458 ry *= scale;
459 }
460
461 let rxry = rx * ry;
462 let rxpy = rx * p.y;
463 let rypx = ry * p.x;
464 let sum_of_sq = rxpy * rxpy + rypx * rypx;
465
466 debug_assert!(sum_of_sq != 0.0);
467
468 let sign_coe = if arc.large_arc == arc.sweep {
470 -1.0
471 } else {
472 1.0
473 };
474 let coe = sign_coe * ((rxry * rxry - sum_of_sq) / sum_of_sq).abs().sqrt();
475 let transformed_cx = coe * rxpy / ry;
476 let transformed_cy = -coe * rypx / rx;
477
478 let center = Point::new(
480 cos_phi * transformed_cx - sin_phi * transformed_cy + hs_x,
481 sin_phi * transformed_cx + cos_phi * transformed_cy + hs_y,
482 );
483
484 let start_v = Vec2::new((p.x - transformed_cx) / rx, (p.y - transformed_cy) / ry);
485 let end_v = Vec2::new((-p.x - transformed_cx) / rx, (-p.y - transformed_cy) / ry);
486
487 let start_angle = start_v.atan2();
488
489 let mut sweep_angle = (end_v.atan2() - start_angle) % (2.0 * PI);
490
491 if arc.sweep && sweep_angle < 0.0 {
492 sweep_angle += 2.0 * PI;
493 } else if !arc.sweep && sweep_angle > 0.0 {
494 sweep_angle -= 2.0 * PI;
495 }
496
497 Some(Arc {
498 center,
499 radii: Vec2::new(rx, ry),
500 start_angle,
501 sweep_angle,
502 x_rotation: arc.x_rotation,
503 })
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use crate::{BezPath, CubicBez, Line, ParamCurve, PathEl, PathSeg, Point, QuadBez, Shape};
510
511 #[test]
512 fn test_parse_svg() {
513 let path = BezPath::from_svg("m10 10 100 0 0 100 -100 0z").unwrap();
514 assert_eq!(path.segments().count(), 4);
515 }
516
517 #[test]
518 fn test_parse_svg2() {
519 let path =
520 BezPath::from_svg("M3.5 8a.5.5 0 01.5-.5h8a.5.5 0 010 1H4a.5.5 0 01-.5-.5z").unwrap();
521 assert_eq!(path.segments().count(), 6);
522 }
523
524 #[test]
525 fn test_parse_svg_arc() {
526 let path = BezPath::from_svg("M 100 100 A 25 25 0 1 0 -25 25 z").unwrap();
527 assert_eq!(path.segments().count(), 3);
528 }
529
530 #[test]
532 #[allow(clippy::float_cmp)]
533 fn test_parse_svg_arc_pie() {
534 let path = BezPath::from_svg("M 100 100 h 25 a 25 25 0 1 0 -25 25 z").unwrap();
535 assert_eq!(path.area().round(), -1473.0);
537 assert_eq!(path.perimeter(1e-6).round(), 168.0);
538 }
539
540 #[test]
541 fn test_parse_svg_uninitialized() {
542 let path = BezPath::from_svg("L10 10 100 0 0 100");
543 assert!(path.is_err());
544 }
545
546 #[test]
547 #[allow(clippy::float_cmp)]
548 fn test_parse_scientific_notation() {
549 let path = BezPath::from_svg("M 0 0 L 1e-123 -4E+5").unwrap();
550 assert_eq!(
551 path.elements(),
552 &[
553 PathEl::MoveTo(Point { x: 0.0, y: 0.0 }),
554 PathEl::LineTo(Point {
555 x: 1e-123,
556 y: -4E+5
557 })
558 ]
559 );
560 }
561
562 #[test]
563 fn test_write_svg_single() {
564 let segments = [CubicBez::new(
565 Point::new(10., 10.),
566 Point::new(20., 20.),
567 Point::new(30., 30.),
568 Point::new(40., 40.),
569 )
570 .into()];
571 let path = BezPath::from_path_segments(segments.iter().cloned());
572
573 assert_eq!(path.to_svg(), "M10,10 C20,20 30,30 40,40");
574 }
575
576 #[test]
577 fn test_write_svg_two_nomove() {
578 let segments = [
579 CubicBez::new(
580 Point::new(10., 10.),
581 Point::new(20., 20.),
582 Point::new(30., 30.),
583 Point::new(40., 40.),
584 )
585 .into(),
586 CubicBez::new(
587 Point::new(40., 40.),
588 Point::new(30., 30.),
589 Point::new(20., 20.),
590 Point::new(10., 10.),
591 )
592 .into(),
593 ];
594 let path = BezPath::from_path_segments(segments.iter().cloned());
595
596 assert_eq!(
597 path.to_svg(),
598 "M10,10 C20,20 30,30 40,40 C30,30 20,20 10,10"
599 );
600 }
601
602 #[test]
603 fn test_write_svg_two_move() {
604 let segments = [
605 CubicBez::new(
606 Point::new(10., 10.),
607 Point::new(20., 20.),
608 Point::new(30., 30.),
609 Point::new(40., 40.),
610 )
611 .into(),
612 CubicBez::new(
613 Point::new(50., 50.),
614 Point::new(30., 30.),
615 Point::new(20., 20.),
616 Point::new(10., 10.),
617 )
618 .into(),
619 ];
620 let path = BezPath::from_path_segments(segments.iter().cloned());
621
622 assert_eq!(
623 path.to_svg(),
624 "M10,10 C20,20 30,30 40,40 M50,50 C30,30 20,20 10,10"
625 );
626 }
627
628 use rand::prelude::*;
629 #[cfg(all(
631 target_arch = "wasm32",
632 target_vendor = "unknown",
633 target_os = "unknown"
634 ))]
635 use getrandom as _;
636
637 fn gen_random_path_sequence(rng: &mut impl Rng) -> Vec<PathSeg> {
638 const MAX_LENGTH: u32 = 10;
639
640 let mut elements = vec![];
641 let mut position = None;
642
643 let length = rng.random_range(0..MAX_LENGTH);
644 for _ in 0..length {
645 let should_follow: bool = rand::random_bool(0.5);
646 let kind = rng.random_range(0..3);
647
648 let first = position
649 .filter(|_| should_follow)
650 .unwrap_or_else(|| Point::new(rng.random(), rng.random()));
651
652 let element: PathSeg = match kind {
653 0 => Line::new(first, Point::new(rng.random(), rng.random())).into(),
654
655 1 => QuadBez::new(
656 first,
657 Point::new(rng.random(), rng.random()),
658 Point::new(rng.random(), rng.random()),
659 )
660 .into(),
661
662 2 => CubicBez::new(
663 first,
664 Point::new(rng.random(), rng.random()),
665 Point::new(rng.random(), rng.random()),
666 Point::new(rng.random(), rng.random()),
667 )
668 .into(),
669
670 _ => unreachable!(),
671 };
672
673 position = Some(element.end());
674 elements.push(element);
675 }
676
677 elements
678 }
679
680 #[test]
681 fn test_serialize_deserialize() {
682 const N_TESTS: u32 = 100;
683 let mut rng = rand::rng();
684
685 for _ in 0..N_TESTS {
686 let vec = gen_random_path_sequence(&mut rng);
687 let ser = BezPath::from_path_segments(vec.iter().cloned()).to_svg();
688 let deser = BezPath::from_svg(&ser).expect("failed deserialization");
689
690 let deser_vec = deser.segments().collect::<Vec<PathSeg>>();
691
692 assert_eq!(vec, deser_vec);
693 }
694 }
695}