1 | // Copyright 2018 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | mod clippath; |
5 | mod converter; |
6 | mod filter; |
7 | mod image; |
8 | mod marker; |
9 | mod mask; |
10 | mod options; |
11 | mod paint_server; |
12 | mod shapes; |
13 | mod style; |
14 | mod svgtree; |
15 | mod switch; |
16 | mod units; |
17 | mod use_node; |
18 | |
19 | #[cfg (feature = "text" )] |
20 | mod text; |
21 | |
22 | pub use image::{ImageHrefDataResolverFn, ImageHrefResolver, ImageHrefStringResolverFn}; |
23 | pub use options::Options; |
24 | pub(crate) use svgtree::{AId, EId}; |
25 | |
26 | /// List of all errors. |
27 | #[derive (Debug)] |
28 | pub enum Error { |
29 | /// Only UTF-8 content are supported. |
30 | NotAnUtf8Str, |
31 | |
32 | /// Compressed SVG must use the GZip algorithm. |
33 | MalformedGZip, |
34 | |
35 | /// We do not allow SVG with more than 1_000_000 elements for security reasons. |
36 | ElementsLimitReached, |
37 | |
38 | /// SVG doesn't have a valid size. |
39 | /// |
40 | /// Occurs when width and/or height are <= 0. |
41 | /// |
42 | /// Also occurs if width, height and viewBox are not set. |
43 | InvalidSize, |
44 | |
45 | /// Failed to parse an SVG data. |
46 | ParsingFailed(roxmltree::Error), |
47 | } |
48 | |
49 | impl From<roxmltree::Error> for Error { |
50 | fn from(e: roxmltree::Error) -> Self { |
51 | Error::ParsingFailed(e) |
52 | } |
53 | } |
54 | |
55 | impl std::fmt::Display for Error { |
56 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
57 | match *self { |
58 | Error::NotAnUtf8Str => { |
59 | write!(f, "provided data has not an UTF-8 encoding" ) |
60 | } |
61 | Error::MalformedGZip => { |
62 | write!(f, "provided data has a malformed GZip content" ) |
63 | } |
64 | Error::ElementsLimitReached => { |
65 | write!(f, "the maximum number of SVG elements has been reached" ) |
66 | } |
67 | Error::InvalidSize => { |
68 | write!(f, "SVG has an invalid size" ) |
69 | } |
70 | Error::ParsingFailed(ref e: &Error) => { |
71 | write!(f, "SVG data parsing failed cause {}" , e) |
72 | } |
73 | } |
74 | } |
75 | } |
76 | |
77 | impl std::error::Error for Error {} |
78 | |
79 | pub(crate) trait OptionLog { |
80 | fn log_none<F: FnOnce()>(self, f: F) -> Self; |
81 | } |
82 | |
83 | impl<T> OptionLog for Option<T> { |
84 | #[inline ] |
85 | fn log_none<F: FnOnce()>(self, f: F) -> Self { |
86 | self.or_else(|| { |
87 | f(); |
88 | None |
89 | }) |
90 | } |
91 | } |
92 | |
93 | impl crate::Tree { |
94 | /// Parses `Tree` from an SVG data. |
95 | /// |
96 | /// Can contain an SVG string or a gzip compressed data. |
97 | pub fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error> { |
98 | if data.starts_with(&[0x1f, 0x8b]) { |
99 | let data = decompress_svgz(data)?; |
100 | let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?; |
101 | Self::from_str(text, opt) |
102 | } else { |
103 | let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?; |
104 | Self::from_str(text, opt) |
105 | } |
106 | } |
107 | |
108 | /// Parses `Tree` from an SVG string. |
109 | pub fn from_str(text: &str, opt: &Options) -> Result<Self, Error> { |
110 | let xml_opt = roxmltree::ParsingOptions { |
111 | allow_dtd: true, |
112 | ..Default::default() |
113 | }; |
114 | |
115 | let doc = |
116 | roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?; |
117 | |
118 | Self::from_xmltree(&doc, opt) |
119 | } |
120 | |
121 | /// Parses `Tree` from `roxmltree::Document`. |
122 | pub fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error> { |
123 | let doc = svgtree::Document::parse_tree(doc, opt.style_sheet.as_deref())?; |
124 | self::converter::convert_doc(&doc, opt) |
125 | } |
126 | } |
127 | |
128 | /// Decompresses an SVGZ file. |
129 | pub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> { |
130 | use std::io::Read; |
131 | |
132 | let mut decoder: GzDecoder<&[u8]> = flate2::read::GzDecoder::new(data); |
133 | let mut decoded: Vec = Vec::with_capacity(data.len() * 2); |
134 | decoder |
135 | .read_to_end(&mut decoded) |
136 | .map_err(|_| Error::MalformedGZip)?; |
137 | Ok(decoded) |
138 | } |
139 | |
140 | #[inline ] |
141 | pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 { |
142 | debug_assert!(min.is_finite()); |
143 | debug_assert!(val.is_finite()); |
144 | debug_assert!(max.is_finite()); |
145 | |
146 | if val > max { |
147 | max |
148 | } else if val < min { |
149 | min |
150 | } else { |
151 | val |
152 | } |
153 | } |
154 | |