Skip to main content

gpui_ce_motion/vector/
parser.rs

1//! Setup-only SVG path parsing using the backend's existing SVG grammar library.
2use super::{PathCommand, PathError, PathShape};
3use std::fmt;
4
5/// SVG grammar or bounded-geometry preparation failure.
6#[derive(Debug)]
7pub enum SvgPathError {
8    /// Invalid SVG path data, retaining the parser's source position/error.
9    Syntax(svgtypes::Error),
10    /// Invalid geometry or a preparation budget exceeded.
11    Geometry(PathError),
12}
13
14impl fmt::Display for SvgPathError {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match self {
17            Self::Syntax(error) => write!(f, "invalid SVG path: {error}"),
18            Self::Geometry(error) => error.fmt(f),
19        }
20    }
21}
22
23impl std::error::Error for SvgPathError {
24    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
25        match self {
26            Self::Syntax(error) => Some(error),
27            Self::Geometry(error) => Some(error),
28        }
29    }
30}
31
32impl PathShape {
33    /// Parses an SVG `d` string, resolving relative/implicit/smooth commands and
34    /// converting elliptical arcs to cubic Beziers. This does not parse an SVG
35    /// document, paints, transforms, or CSS. Prepare once outside rendering.
36    /// Input is bounded to 1 MiB and 65,536 expanded commands.
37    ///
38    /// # Errors
39    /// Returns grammar errors with their original position, geometry errors for
40    /// non-finite/out-of-range coordinates, or `Complexity` when budgets are exceeded.
41    #[cfg_attr(feature = "svg-path", doc = "```")]
42    #[cfg_attr(feature = "svg-path", doc = "use gpui_ce_motion::PathShape;")]
43    #[cfg_attr(
44        feature = "svg-path",
45        doc = "let path = PathShape::parse_svg(\"M0 0h20a10 10 0 0 1 10 10z\").unwrap();"
46    )]
47    #[cfg_attr(feature = "svg-path", doc = "assert!(path.commands().len() >= 4);")]
48    #[cfg_attr(feature = "svg-path", doc = "```")]
49    pub fn parse_svg(data: &str) -> Result<Self, SvgPathError> {
50        if data.len() > 1_048_576 {
51            return Err(SvgPathError::Geometry(PathError::Complexity));
52        }
53
54        let mut commands = Vec::new();
55
56        for segment in svgtypes::SimplifyingPathParser::from(data) {
57            if commands.len() >= 65_536 {
58                return Err(SvgPathError::Geometry(PathError::Complexity));
59            }
60
61            let command = match segment.map_err(SvgPathError::Syntax)? {
62                svgtypes::SimplePathSegment::MoveTo { x, y } => PathCommand::Move([x, y]),
63                svgtypes::SimplePathSegment::LineTo { x, y } => PathCommand::Line([x, y]),
64                svgtypes::SimplePathSegment::CurveTo {
65                    x1,
66                    y1,
67                    x2,
68                    y2,
69                    x,
70                    y,
71                } => PathCommand::Cubic([x1, y1], [x2, y2], [x, y]),
72                svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => {
73                    PathCommand::Quadratic([x1, y1], [x, y])
74                }
75                svgtypes::SimplePathSegment::ClosePath => PathCommand::Close,
76            };
77
78            commands.push(command);
79        }
80
81        Self::new(commands).map_err(SvgPathError::Geometry)
82    }
83}