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