1mod 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
19pub 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#[derive(Debug)]
32pub enum Error {
33 NotAnUtf8Str,
35
36 MalformedGZip,
38
39 ElementsLimitReached,
41
42 InvalidSize,
48
49 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 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 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 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 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 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 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 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
233pub 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}