1use std::sync::Arc;
6
7use svgtypes::Length;
8use usvg_tree::{Group, Image, ImageKind, Node, NonZeroRect, Size, Transform, Tree, ViewBox};
9
10use crate::svgtree::{AId, SvgNode};
11use crate::{converter, OptionLog, Options, TreeParsing};
12
13pub type ImageHrefDataResolverFn =
15 Box<dyn Fn(&str, Arc<Vec<u8>>, &Options) -> Option<ImageKind> + Send + Sync>;
16pub type ImageHrefStringResolverFn = Box<dyn Fn(&str, &Options) -> Option<ImageKind> + Send + Sync>;
18
19pub struct ImageHrefResolver {
25 pub resolve_data: ImageHrefDataResolverFn,
30
31 pub resolve_string: ImageHrefStringResolverFn,
33}
34
35impl Default for ImageHrefResolver {
36 fn default() -> Self {
37 ImageHrefResolver {
38 resolve_data: ImageHrefResolver::default_data_resolver(),
39 resolve_string: ImageHrefResolver::default_string_resolver(),
40 }
41 }
42}
43
44impl ImageHrefResolver {
45 pub fn default_data_resolver() -> ImageHrefDataResolverFn {
55 Box::new(
56 move |mime: &str, data: Arc<Vec<u8>>, opts: &Options| match mime {
57 "image/jpg" | "image/jpeg" => Some(ImageKind::JPEG(data)),
58 "image/png" => Some(ImageKind::PNG(data)),
59 "image/gif" => Some(ImageKind::GIF(data)),
60 "image/svg+xml" => load_sub_svg(&data, opts),
61 "text/plain" => match get_image_data_format(&data) {
62 Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(data)),
63 Some(ImageFormat::PNG) => Some(ImageKind::PNG(data)),
64 Some(ImageFormat::GIF) => Some(ImageKind::GIF(data)),
65 _ => load_sub_svg(&data, opts),
66 },
67 _ => None,
68 },
69 )
70 }
71
72 pub fn default_string_resolver() -> ImageHrefStringResolverFn {
80 Box::new(move |href: &str, opts: &Options| {
81 let path = opts.get_abs_path(std::path::Path::new(href));
82
83 if path.exists() {
84 let data = match std::fs::read(&path) {
85 Ok(data) => data,
86 Err(_) => {
87 log::warn!("Failed to load '{}'. Skipped.", href);
88 return None;
89 }
90 };
91
92 match get_image_file_format(&path, &data) {
93 Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(Arc::new(data))),
94 Some(ImageFormat::PNG) => Some(ImageKind::PNG(Arc::new(data))),
95 Some(ImageFormat::GIF) => Some(ImageKind::GIF(Arc::new(data))),
96 Some(ImageFormat::SVG) => load_sub_svg(&data, opts),
97 _ => {
98 log::warn!("'{}' is not a PNG, JPEG, GIF or SVG(Z) image.", href);
99 None
100 }
101 }
102 } else {
103 log::warn!("'{}' is not a path to an image.", href);
104 None
105 }
106 })
107 }
108}
109
110impl std::fmt::Debug for ImageHrefResolver {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.write_str("ImageHrefResolver { .. }")
113 }
114}
115
116#[derive(Clone, Copy, PartialEq, Debug)]
117enum ImageFormat {
118 PNG,
119 JPEG,
120 GIF,
121 SVG,
122}
123
124pub(crate) fn convert(node: SvgNode, state: &converter::State, parent: &mut Group) -> Option<()> {
125 let href = node
126 .try_attribute(AId::Href)
127 .log_none(|| log::warn!("Image lacks the 'xlink:href' attribute. Skipped."))?;
128
129 let kind = get_href_data(href, state.opt)?;
130
131 let visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
132 let rendering_mode = node
133 .find_attribute(AId::ImageRendering)
134 .unwrap_or(state.opt.image_rendering);
135
136 let actual_size = match kind {
137 ImageKind::JPEG(ref data) | ImageKind::PNG(ref data) | ImageKind::GIF(ref data) => {
138 imagesize::blob_size(data)
139 .ok()
140 .and_then(|size| Size::from_wh(size.width as f32, size.height as f32))
141 .log_none(|| log::warn!("Image has an invalid size. Skipped."))?
142 }
143 ImageKind::SVG(ref svg) => svg.size,
144 };
145
146 let rect = NonZeroRect::from_xywh(
147 node.convert_user_length(AId::X, state, Length::zero()),
148 node.convert_user_length(AId::Y, state, Length::zero()),
149 node.convert_user_length(
150 AId::Width,
151 state,
152 Length::new_number(actual_size.width() as f64),
153 ),
154 node.convert_user_length(
155 AId::Height,
156 state,
157 Length::new_number(actual_size.height() as f64),
158 ),
159 );
160 let rect = rect.log_none(|| log::warn!("Image has an invalid size. Skipped."))?;
161
162 let view_box = ViewBox {
163 rect,
164 aspect: node.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
165 };
166
167 let id = if state.parent_markers.is_empty() {
169 node.element_id().to_string()
170 } else {
171 String::new()
172 };
173
174 parent.children.push(Node::Image(Box::new(Image {
175 id,
176 visibility,
177 view_box,
178 rendering_mode,
179 kind,
180 abs_transform: Transform::default(),
181 bounding_box: None,
182 })));
183
184 Some(())
185}
186
187pub(crate) fn get_href_data(href: &str, opt: &Options) -> Option<ImageKind> {
188 if let Ok(url) = data_url::DataUrl::process(href) {
189 let (data, _) = url.decode_to_vec().ok()?;
190
191 let mime = format!(
192 "{}/{}",
193 url.mime_type().type_.as_str(),
194 url.mime_type().subtype.as_str()
195 );
196
197 (opt.image_href_resolver.resolve_data)(&mime, Arc::new(data), opt)
198 } else {
199 (opt.image_href_resolver.resolve_string)(href, opt)
200 }
201}
202
203fn get_image_file_format(path: &std::path::Path, data: &[u8]) -> Option<ImageFormat> {
206 let ext = path.extension().and_then(|e| e.to_str())?.to_lowercase();
207 if ext == "svg" || ext == "svgz" {
208 return Some(ImageFormat::SVG);
209 }
210
211 get_image_data_format(data)
212}
213
214fn get_image_data_format(data: &[u8]) -> Option<ImageFormat> {
216 match imagesize::image_type(data).ok()? {
217 imagesize::ImageType::Gif => Some(ImageFormat::GIF),
218 imagesize::ImageType::Jpeg => Some(ImageFormat::JPEG),
219 imagesize::ImageType::Png => Some(ImageFormat::PNG),
220 _ => None,
221 }
222}
223
224pub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option<ImageKind> {
229 let mut sub_opt = Options::default();
230 sub_opt.resources_dir = None;
231 sub_opt.dpi = opt.dpi;
232 sub_opt.font_size = opt.font_size;
233 sub_opt.languages = opt.languages.clone();
234 sub_opt.shape_rendering = opt.shape_rendering;
235 sub_opt.text_rendering = opt.text_rendering;
236 sub_opt.image_rendering = opt.image_rendering;
237 sub_opt.default_size = opt.default_size;
238
239 sub_opt.image_href_resolver = ImageHrefResolver {
242 resolve_data: Box::new(|_, _, _| None),
243 resolve_string: Box::new(|_, _| None),
244 };
245
246 let mut tree = match Tree::from_data(data, &sub_opt) {
247 Ok(tree) => tree,
248 Err(_) => {
249 log::warn!("Failed to load subsvg image.");
250 return None;
251 }
252 };
253 tree.calculate_abs_transforms();
254 tree.calculate_bounding_boxes();
255
256 Some(ImageKind::SVG(tree))
257}