usvg_parser/
lib.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
5/*!
6`usvg-parser` is an [SVG] parser used by [usvg].
7
8[SVG]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
9[usvg]: https://github.com/RazrFalcon/resvg/tree/master/crates/usvg
10*/
11
12#![forbid(unsafe_code)]
13#![warn(missing_docs)]
14#![warn(missing_debug_implementations)]
15#![warn(missing_copy_implementations)]
16#![allow(clippy::collapsible_else_if)]
17#![allow(clippy::collapsible_if)]
18#![allow(clippy::field_reassign_with_default)]
19#![allow(clippy::identity_op)]
20#![allow(clippy::question_mark)]
21#![allow(clippy::too_many_arguments)]
22#![allow(clippy::upper_case_acronyms)]
23
24mod clippath;
25mod converter;
26mod filter;
27mod image;
28mod marker;
29mod mask;
30mod options;
31mod paint_server;
32mod shapes;
33mod style;
34mod svgtree;
35mod switch;
36mod text;
37mod units;
38mod use_node;
39
40pub use crate::options::*;
41pub use image::ImageHrefResolver;
42pub use roxmltree;
43pub use svgtree::{AId, EId};
44
45/// List of all errors.
46#[derive(Debug)]
47pub enum Error {
48    /// Only UTF-8 content are supported.
49    NotAnUtf8Str,
50
51    /// Compressed SVG must use the GZip algorithm.
52    MalformedGZip,
53
54    /// We do not allow SVG with more than 1_000_000 elements for security reasons.
55    ElementsLimitReached,
56
57    /// SVG doesn't have a valid size.
58    ///
59    /// Occurs when width and/or height are <= 0.
60    ///
61    /// Also occurs if width, height and viewBox are not set.
62    InvalidSize,
63
64    /// Failed to parse an SVG data.
65    ParsingFailed(roxmltree::Error),
66}
67
68impl From<roxmltree::Error> for Error {
69    fn from(e: roxmltree::Error) -> Self {
70        Error::ParsingFailed(e)
71    }
72}
73
74impl std::fmt::Display for Error {
75    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
76        match *self {
77            Error::NotAnUtf8Str => {
78                write!(f, "provided data has not an UTF-8 encoding")
79            }
80            Error::MalformedGZip => {
81                write!(f, "provided data has a malformed GZip content")
82            }
83            Error::ElementsLimitReached => {
84                write!(f, "the maximum number of SVG elements has been reached")
85            }
86            Error::InvalidSize => {
87                write!(f, "SVG has an invalid size")
88            }
89            Error::ParsingFailed(ref e) => {
90                write!(f, "SVG data parsing failed cause {}", e)
91            }
92        }
93    }
94}
95
96impl std::error::Error for Error {}
97
98trait OptionLog {
99    fn log_none<F: FnOnce()>(self, f: F) -> Self;
100}
101
102impl<T> OptionLog for Option<T> {
103    #[inline]
104    fn log_none<F: FnOnce()>(self, f: F) -> Self {
105        self.or_else(|| {
106            f();
107            None
108        })
109    }
110}
111
112/// A trait to parse `usvg_tree::Tree` from various sources.
113pub trait TreeParsing: Sized {
114    /// Parses `Tree` from an SVG data.
115    ///
116    /// Can contain an SVG string or a gzip compressed data.
117    fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error>;
118
119    /// Parses `Tree` from an SVG string.
120    fn from_str(text: &str, opt: &Options) -> Result<Self, Error>;
121
122    /// Parses `Tree` from `roxmltree::Document`.
123    fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error>;
124}
125
126impl TreeParsing for usvg_tree::Tree {
127    /// Parses `Tree` from an SVG data.
128    ///
129    /// Can contain an SVG string or a gzip compressed data.
130    fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error> {
131        if data.starts_with(&[0x1f, 0x8b]) {
132            let data = decompress_svgz(data)?;
133            let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;
134            Self::from_str(text, opt)
135        } else {
136            let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
137            Self::from_str(text, opt)
138        }
139    }
140
141    /// Parses `Tree` from an SVG string.
142    fn from_str(text: &str, opt: &Options) -> Result<Self, Error> {
143        let xml_opt = roxmltree::ParsingOptions {
144            allow_dtd: true,
145            ..Default::default()
146        };
147
148        let doc =
149            roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;
150
151        Self::from_xmltree(&doc, opt)
152    }
153
154    /// Parses `Tree` from `roxmltree::Document`.
155    fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error> {
156        let doc = svgtree::Document::parse_tree(doc)?;
157        crate::converter::convert_doc(&doc, opt)
158    }
159}
160
161/// Decompresses an SVGZ file.
162pub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> {
163    use std::io::Read;
164
165    let mut decoder = flate2::read::GzDecoder::new(data);
166    let mut decoded = Vec::with_capacity(data.len() * 2);
167    decoder
168        .read_to_end(&mut decoded)
169        .map_err(|_| Error::MalformedGZip)?;
170    Ok(decoded)
171}
172
173#[inline]
174pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
175    debug_assert!(min.is_finite());
176    debug_assert!(val.is_finite());
177    debug_assert!(max.is_finite());
178
179    if val > max {
180        max
181    } else if val < min {
182        min
183    } else {
184        val
185    }
186}