usvgr/parser/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5mod clippath;
6mod converter;
7mod filter;
8mod image;
9mod marker;
10mod mask;
11mod options;
12mod paint_server;
13mod shapes;
14mod style;
15mod switch;
16mod units;
17mod use_node;
18
19/// FFRAMES: make svgree publcic
20pub mod svgtree;
21#[cfg(feature = "text")]
22mod text;
23
24pub use image::PreloadedImageData;
25pub use options::Options;
26pub(crate) use svgtree::{AId, EId};
27
28pub use self::converter::Cache;
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 std::fmt::Display for Error {
60    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::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 std::error::Error for Error {}
82
83trait 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(
102        data: &[u8],
103        opt: &Options,
104        #[cfg(feature = "text")] fontdb: &fontdb::Database,
105    ) -> Result<Self, Error> {
106        if data.starts_with(&[0x1f, 0x8b]) {
107            let data = decompress_svgz(data)?;
108            let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;
109            Self::from_str(
110                text,
111                opt,
112                #[cfg(feature = "text")]
113                fontdb,
114            )
115        } else {
116            let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
117            Self::from_str(
118                text,
119                opt,
120                #[cfg(feature = "text")]
121                fontdb,
122            )
123        }
124    }
125
126    /// Parses `Tree` from an SVG string.
127    pub fn from_str(
128        text: &str,
129        opt: &Options,
130        #[cfg(feature = "text")] fontdb: &fontdb::Database,
131    ) -> Result<Self, Error> {
132        let xml_opt = roxmltree::ParsingOptions {
133            allow_dtd: true,
134            ..Default::default()
135        };
136
137        let doc =
138            roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;
139
140        Self::from_xmltree(
141            &doc,
142            opt,
143            #[cfg(feature = "text")]
144            fontdb,
145        )
146    }
147
148    /// Parses `Tree` from `roxmltree::Document` with a cache.
149    pub fn from_xmltree_with_cache(
150        doc: &roxmltree::Document,
151        opt: &Options,
152        cache: &mut Cache,
153        #[cfg(feature = "text")] fontdb: &fontdb::Database,
154    ) -> Result<Self, Error> {
155        let doc = svgtree::Document::parse_tree(doc)?;
156        self::converter::convert_doc(
157            &doc,
158            opt,
159            cache,
160            #[cfg(feature = "text")]
161            fontdb,
162        )
163    }
164
165    /// Parses `Tree` from `roxmltree::Document`.
166    pub fn from_xmltree(
167        doc: &roxmltree::Document,
168        opt: &Options,
169        #[cfg(feature = "text")] fontdb: &fontdb::Database,
170    ) -> Result<Self, Error> {
171        let doc = svgtree::Document::parse_tree(doc)?;
172        self::converter::convert_doc(
173            &doc,
174            opt,
175            &mut Cache::default(),
176            #[cfg(feature = "text")]
177            fontdb,
178        )
179    }
180
181    /// Parses `Tree` from `svgtree::NestedSvgDocument`.
182    pub fn from_nested_svgtree(
183        doc: &svgtree::NestedSvgDocument,
184        opt: &Options,
185        #[cfg(feature = "text")] fontdb: &fontdb::Database,
186    ) -> Result<Self, Error> {
187        let doc = svgtree::Document::try_from(doc)?;
188        Self::from_svgtree(
189            doc,
190            opt,
191            &mut Cache::default(),
192            #[cfg(feature = "text")]
193            fontdb,
194        )
195    }
196
197    /// Parses `Tree` from `svgtree::NestedSvgDocument` with a cache.
198    pub fn from_nested_svgtree_with_cache(
199        doc: &svgtree::NestedSvgDocument,
200        opt: &Options,
201        cache: &mut Cache,
202        #[cfg(feature = "text")] fontdb: &fontdb::Database,
203    ) -> Result<Self, Error> {
204        let doc = svgtree::Document::try_from(doc)?;
205        Self::from_svgtree(
206            doc,
207            opt,
208            cache,
209            #[cfg(feature = "text")]
210            fontdb,
211        )
212    }
213
214    /// Parses `Tree` from the `svgtree::Document`.
215    ///
216    /// An empty `Tree` will be returned on any error.
217    pub fn from_svgtree(
218        doc: svgtree::Document,
219        opt: &Options,
220        cache: &mut Cache,
221        #[cfg(feature = "text")] fontdb: &fontdb::Database,
222    ) -> Result<Self, Error> {
223        self::converter::convert_doc(
224            &doc,
225            opt,
226            cache,
227            #[cfg(feature = "text")]
228            fontdb,
229        )
230    }
231}
232
233/// Decompresses an SVGZ file.
234pub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> {
235    use std::io::Read;
236
237    let mut decoder = flate2::read::GzDecoder::new(data);
238    let mut decoded = Vec::with_capacity(data.len() * 2);
239    decoder
240        .read_to_end(&mut decoded)
241        .map_err(|_| Error::MalformedGZip)?;
242    Ok(decoded)
243}
244
245#[inline]
246pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
247    debug_assert!(min.is_finite());
248    debug_assert!(val.is_finite());
249    debug_assert!(max.is_finite());
250
251    if val > max {
252        max
253    } else if val < min {
254        min
255    } else {
256        val
257    }
258}