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 svgtree;
16mod switch;
17mod units;
18mod use_node;
19
20#[cfg(feature = "text")]
21mod text;
22
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: &Error) => {
72 write!(f, "SVG data parsing failed cause {}", e)
73 }
74 }
75 }
76}
77
78impl std::error::Error for Error {}
79
80trait 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(
99 data: &[u8],
100 opt: &Options,
101 #[cfg(feature = "text")] fontdb: &fontdb::Database,
102 ) -> Result<Self, Error> {
103 if data.starts_with(&[0x1f, 0x8b]) {
104 let data = decompress_svgz(data)?;
105 let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;
106 Self::from_str(
107 text,
108 opt,
109 #[cfg(feature = "text")]
110 fontdb,
111 )
112 } else {
113 let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
114 Self::from_str(
115 text,
116 opt,
117 #[cfg(feature = "text")]
118 fontdb,
119 )
120 }
121 }
122
123 /// Parses `Tree` from an SVG string.
124 pub fn from_str(
125 text: &str,
126 opt: &Options,
127 #[cfg(feature = "text")] fontdb: &fontdb::Database,
128 ) -> Result<Self, Error> {
129 let xml_opt = roxmltree::ParsingOptions {
130 allow_dtd: true,
131 ..Default::default()
132 };
133
134 let doc =
135 roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;
136
137 Self::from_xmltree(
138 &doc,
139 opt,
140 #[cfg(feature = "text")]
141 fontdb,
142 )
143 }
144
145 /// Parses `Tree` from `roxmltree::Document`.
146 pub fn from_xmltree(
147 doc: &roxmltree::Document,
148 opt: &Options,
149 #[cfg(feature = "text")] fontdb: &fontdb::Database,
150 ) -> Result<Self, Error> {
151 let doc = svgtree::Document::parse_tree(doc)?;
152 self::converter::convert_doc(
153 &doc,
154 opt,
155 #[cfg(feature = "text")]
156 fontdb,
157 )
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: GzDecoder<&[u8]> = flate2::read::GzDecoder::new(data);
166 let mut decoded: Vec = 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}
187