1#![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#[derive(Debug)]
47pub enum Error {
48 NotAnUtf8Str,
50
51 MalformedGZip,
53
54 ElementsLimitReached,
56
57 InvalidSize,
63
64 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
112pub trait TreeParsing: Sized {
114 fn from_data(data: &[u8], opt: &Options) -> Result<Self, Error>;
118
119 fn from_str(text: &str, opt: &Options) -> Result<Self, Error>;
121
122 fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error>;
124}
125
126impl TreeParsing for usvg_tree::Tree {
127 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 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 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
161pub 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}