Skip to main content

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