Skip to main content

ai_usvg/parser/
mod.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4mod clippath;
5mod converter;
6mod filter;
7mod image;
8mod marker;
9mod mask;
10mod options;
11mod paint_server;
12mod shapes;
13mod style;
14mod svgtree;
15mod switch;
16mod units;
17mod use_node;
18
19use alloc::boxed::Box;
20use alloc::vec::Vec;
21
22#[cfg(feature = "text")]
23mod text;
24#[cfg(feature = "text")]
25pub(crate) use converter::Cache;
26pub use image::{ImageHrefDataResolverFn, ImageHrefResolver, ImageHrefStringResolverFn};
27pub use options::Options;
28pub(crate) use svgtree::{AId, EId};
29
30/// List of all errors.
31#[derive(Debug)]
32pub enum Error {
33    /// Only UTF-8 content are supported.
34    NotAnUtf8Str,
35
36    /// Compressed SVG must use the GZip algorithm.
37    MalformedGZip,
38
39    /// We do not allow SVG with more than 1_000_000 elements for security reasons.
40    ElementsLimitReached,
41
42    /// SVG doesn't have a valid size.
43    ///
44    /// Occurs when width and/or height are <= 0.
45    ///
46    /// Also occurs if width, height and viewBox are not set.
47    InvalidSize,
48
49    /// Failed to parse an SVG data.
50    ParsingFailed(roxmltree::Error),
51}
52
53impl From<roxmltree::Error> for Error {
54    fn from(e: roxmltree::Error) -> Self {
55        Error::ParsingFailed(e)
56    }
57}
58
59impl core::fmt::Display for Error {
60    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
61        match *self {
62            Error::NotAnUtf8Str => {
63                write!(f, "provided data has not an UTF-8 encoding")
64            }
65            Error::MalformedGZip => {
66                write!(f, "provided data has a malformed GZip content")
67            }
68            Error::ElementsLimitReached => {
69                write!(f, "the maximum number of SVG elements has been reached")
70            }
71            Error::InvalidSize => {
72                write!(f, "SVG has an invalid size")
73            }
74            Error::ParsingFailed(ref e) => {
75                write!(f, "SVG data parsing failed cause {}", e)
76            }
77        }
78    }
79}
80
81impl core::error::Error for Error {}
82
83pub(crate) trait OptionLog {
84    fn log_none<F: FnOnce()>(self, f: F) -> Self;
85}
86
87impl<T> OptionLog for Option<T> {
88    #[inline]
89    fn log_none<F: FnOnce()>(self, f: F) -> Self {
90        self.or_else(|| {
91            f();
92            None
93        })
94    }
95}
96
97impl crate::Tree {
98    /// Parses `Tree` from an SVG data.
99    ///
100    /// Can contain an SVG string or a gzip compressed data.
101    pub fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error> {
102        if data.starts_with(&[0x1f, 0x8b]) {
103            let data = decompress_svgz(data)?;
104            let text = core::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;
105            Self::from_str(text, opt)
106        } else {
107            let text = core::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
108            Self::from_str(text, opt)
109        }
110    }
111
112    /// Similar to the `from_data` method, except that it ignores all `image` elements linking to
113    /// external files, as required by the SVG specification when SVG files are loaded
114    /// for `<image href="..." />` tags.
115    pub fn from_data_nested(data: &[u8], opt: &Options) -> Result<Self, Error> {
116        let nested_opt = Options {
117            resources_dir: None,
118            dpi: opt.dpi,
119            font_size: opt.font_size,
120            languages: opt.languages.clone(),
121            shape_rendering: opt.shape_rendering,
122            text_rendering: opt.text_rendering,
123            image_rendering: opt.image_rendering,
124            default_size: opt.default_size,
125            image_href_resolver: ImageHrefResolver {
126                resolve_data: Box::new(|a, b, c| (opt.image_href_resolver.resolve_data)(a, b, c)),
127                // External images should be ignored.
128                resolve_string: Box::new(|_, _| None),
129            },
130            // In the referenced SVG, we start with the unmodified user-provided
131            // fontdb, not the one from the cache.
132            #[cfg(feature = "text")]
133            fontdb: opt.fontdb.clone(),
134            // Can't clone the resolver, so we create a new one that forwards to it.
135            #[cfg(feature = "text")]
136            font_resolver: crate::FontResolver {
137                select_font: Box::new(|font, db| (opt.font_resolver.select_font)(font, db)),
138                select_fallback: Box::new(|c, used_fonts, db| {
139                    (opt.font_resolver.select_fallback)(c, used_fonts, db)
140                }),
141            },
142            ..Options::default()
143        };
144
145        Self::from_data(data, &nested_opt)
146    }
147
148    /// Parses `Tree` from an SVG string.
149    pub fn from_str(text: &str, opt: &Options) -> Result<Self, Error> {
150        let xml_opt = roxmltree::ParsingOptions {
151            allow_dtd: true,
152            ..Default::default()
153        };
154
155        let doc =
156            roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;
157
158        Self::from_xmltree(&doc, opt)
159    }
160
161    /// Parses `Tree` from `roxmltree::Document`.
162    pub fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error> {
163        let doc = svgtree::Document::parse_tree(doc, opt.style_sheet.as_deref())?;
164        self::converter::convert_doc(&doc, opt)
165    }
166}
167
168/// Decompresses an SVGZ file.
169pub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> {
170    use no_std_io::io::Read;
171    let mut decoder = flate2::read::GzDecoder::new(data);
172    let mut decoded = Vec::with_capacity(data.len() * 2);
173    decoder
174        .read_to_end(&mut decoded)
175        .map_err(|_| Error::MalformedGZip)?;
176    Ok(decoded)
177}
178
179#[inline]
180pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
181    debug_assert!(min.is_finite());
182    debug_assert!(val.is_finite());
183    debug_assert!(max.is_finite());
184
185    if val > max {
186        max
187    } else if val < min {
188        min
189    } else {
190        val
191    }
192}