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 | mod clippath; |
6 | mod converter; |
7 | mod filter; |
8 | mod image; |
9 | mod marker; |
10 | mod mask; |
11 | mod options; |
12 | mod paint_server; |
13 | mod shapes; |
14 | mod style; |
15 | mod svgtree; |
16 | mod switch; |
17 | mod units; |
18 | mod use_node; |
19 | |
20 | #[cfg (feature = "text" )] |
21 | mod text; |
22 | |
23 | pub use image::{ImageHrefDataResolverFn, ImageHrefResolver, ImageHrefStringResolverFn}; |
24 | pub use options::Options; |
25 | pub(crate) use svgtree::{AId, EId}; |
26 | |
27 | /// List of all errors. |
28 | #[derive (Debug)] |
29 | pub 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 | |
50 | impl From<roxmltree::Error> for Error { |
51 | fn from(e: roxmltree::Error) -> Self { |
52 | Error::ParsingFailed(e) |
53 | } |
54 | } |
55 | |
56 | impl 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 | |
78 | impl std::error::Error for Error {} |
79 | |
80 | trait OptionLog { |
81 | fn log_none<F: FnOnce()>(self, f: F) -> Self; |
82 | } |
83 | |
84 | impl<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 | |
94 | impl 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. |
162 | pub 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 ] |
174 | pub(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 | |