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