1use std::sync::Arc;
5
6use svgtypes::{AspectRatio, Length};
7
8use super::svgtree::{AId, SvgNode};
9use super::{converter, OptionLog, Options};
10use crate::{
11 ClipPath, Group, Image, ImageKind, ImageRendering, Node, NonZeroRect, Path, Size, Transform,
12 Tree, Visibility,
13};
14
15pub type ImageHrefDataResolverFn<'a> =
17 Box<dyn Fn(&str, Arc<Vec<u8>>, &Options) -> Option<ImageKind> + Send + Sync + 'a>;
18
19pub type ImageHrefStringResolverFn<'a> =
21 Box<dyn Fn(&str, &Options) -> Option<ImageKind> + Send + Sync + 'a>;
22
23pub struct ImageHrefResolver<'a> {
29 pub resolve_data: ImageHrefDataResolverFn<'a>,
34
35 pub resolve_string: ImageHrefStringResolverFn<'a>,
37}
38
39impl Default for ImageHrefResolver<'_> {
40 fn default() -> Self {
41 ImageHrefResolver {
42 resolve_data: ImageHrefResolver::default_data_resolver(),
43 resolve_string: ImageHrefResolver::default_string_resolver(),
44 }
45 }
46}
47
48impl ImageHrefResolver<'_> {
49 pub fn default_data_resolver() -> ImageHrefDataResolverFn<'static> {
59 Box::new(
60 move |mime: &str, data: Arc<Vec<u8>>, opts: &Options| match mime {
61 "image/jpg" | "image/jpeg" => Some(ImageKind::JPEG(data)),
62 "image/png" => Some(ImageKind::PNG(data)),
63 "image/gif" => Some(ImageKind::GIF(data)),
64 "image/webp" => Some(ImageKind::WEBP(data)),
65 "image/svg+xml" => load_sub_svg(&data, opts),
66 "text/plain" => match get_image_data_format(&data) {
67 Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(data)),
68 Some(ImageFormat::PNG) => Some(ImageKind::PNG(data)),
69 Some(ImageFormat::GIF) => Some(ImageKind::GIF(data)),
70 Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(data)),
71 _ => load_sub_svg(&data, opts),
72 },
73 _ => None,
74 },
75 )
76 }
77
78 pub fn default_string_resolver() -> ImageHrefStringResolverFn<'static> {
86 Box::new(move |href: &str, opts: &Options| {
87 let path = opts.get_abs_path(std::path::Path::new(href));
88
89 if path.exists() {
90 let data = match std::fs::read(&path) {
91 Ok(data) => data,
92 Err(_) => {
93 log::warn!("Failed to load '{}'. Skipped.", href);
94 return None;
95 }
96 };
97
98 match get_image_file_format(&path, &data) {
99 Some(ImageFormat::JPEG) => Some(ImageKind::JPEG(Arc::new(data))),
100 Some(ImageFormat::PNG) => Some(ImageKind::PNG(Arc::new(data))),
101 Some(ImageFormat::GIF) => Some(ImageKind::GIF(Arc::new(data))),
102 Some(ImageFormat::WEBP) => Some(ImageKind::WEBP(Arc::new(data))),
103 Some(ImageFormat::SVG) => load_sub_svg(&data, opts),
104 _ => {
105 log::warn!("'{}' is not a PNG, JPEG, GIF, WebP or SVG(Z) image.", href);
106 None
107 }
108 }
109 } else {
110 log::warn!("'{}' is not a path to an image.", href);
111 None
112 }
113 })
114 }
115}
116
117impl std::fmt::Debug for ImageHrefResolver<'_> {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.write_str("ImageHrefResolver { .. }")
120 }
121}
122
123#[derive(Clone, Copy, PartialEq, Debug)]
124enum ImageFormat {
125 PNG,
126 JPEG,
127 GIF,
128 WEBP,
129 SVG,
130}
131
132pub(crate) fn convert(
133 node: SvgNode,
134 state: &converter::State,
135 cache: &mut converter::Cache,
136 parent: &mut Group,
137) -> Option<()> {
138 let href = node
139 .try_attribute(AId::Href)
140 .log_none(|| log::warn!("Image lacks the 'xlink:href' attribute. Skipped."))?;
141
142 let kind = get_href_data(href, state)?;
143
144 let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
145 let visible = visibility == Visibility::Visible;
146
147 let rendering_mode = node
148 .find_attribute(AId::ImageRendering)
149 .unwrap_or(state.opt.image_rendering);
150
151 let id = if state.parent_markers.is_empty() {
153 node.element_id().to_string()
154 } else {
155 String::new()
156 };
157
158 let actual_size = kind.actual_size()?;
159
160 let x = node.convert_user_length(AId::X, state, Length::zero());
161 let y = node.convert_user_length(AId::Y, state, Length::zero());
162 let mut width = node.convert_user_length(
163 AId::Width,
164 state,
165 Length::new_number(actual_size.width() as f64),
166 );
167 let mut height = node.convert_user_length(
168 AId::Height,
169 state,
170 Length::new_number(actual_size.height() as f64),
171 );
172
173 match (
174 node.attribute::<Length>(AId::Width),
175 node.attribute::<Length>(AId::Height),
176 ) {
177 (Some(_), None) => {
178 height = actual_size.height() * (width / actual_size.width());
180 }
181 (None, Some(_)) => {
182 width = actual_size.width() * (height / actual_size.height());
184 }
185 _ => {}
186 };
187
188 let aspect: AspectRatio = node.attribute(AId::PreserveAspectRatio).unwrap_or_default();
189
190 let rect = NonZeroRect::from_xywh(x, y, width, height);
191 let rect = rect.log_none(|| log::warn!("Image has an invalid size. Skipped."))?;
192
193 convert_inner(
194 kind,
195 id,
196 visible,
197 rendering_mode,
198 aspect,
199 actual_size,
200 rect,
201 cache,
202 parent,
203 )
204}
205
206pub(crate) fn convert_inner(
207 kind: ImageKind,
208 id: String,
209 visible: bool,
210 rendering_mode: ImageRendering,
211 aspect: AspectRatio,
212 actual_size: Size,
213 rect: NonZeroRect,
214 cache: &mut converter::Cache,
215 parent: &mut Group,
216) -> Option<()> {
217 let aligned_size = fit_view_box(actual_size, rect, aspect);
218 let (aligned_x, aligned_y) = crate::aligned_pos(
219 aspect.align,
220 rect.x(),
221 rect.y(),
222 rect.width() - aligned_size.width(),
223 rect.height() - aligned_size.height(),
224 );
225 let view_box = aligned_size.to_non_zero_rect(aligned_x, aligned_y);
226
227 let image_ts = Transform::from_row(
228 view_box.width() / actual_size.width(),
229 0.0,
230 0.0,
231 view_box.height() / actual_size.height(),
232 view_box.x(),
233 view_box.y(),
234 );
235
236 let abs_transform = parent.abs_transform.pre_concat(image_ts);
237 let abs_bounding_box = rect.transform(abs_transform)?;
238
239 let mut g = Group::empty();
240 g.id = id;
241 g.children.push(Node::Image(Box::new(Image {
242 id: String::new(),
243 visible,
244 size: actual_size,
245 rendering_mode,
246 kind,
247 abs_transform,
248 abs_bounding_box,
249 })));
250 g.transform = image_ts;
251 g.abs_transform = abs_transform;
252 g.calculate_bounding_boxes();
253
254 if aspect.slice {
255 let mut path = Path::new_simple(Arc::new(tiny_skia_path::PathBuilder::from_rect(
257 rect.to_rect(),
258 )))
259 .unwrap();
260 path.fill = Some(crate::Fill::default());
261
262 let mut clip = ClipPath::empty(cache.gen_clip_path_id());
263 clip.root.children.push(Node::Path(Box::new(path)));
264
265 let mut g2 = Group::empty();
274 std::mem::swap(&mut g.id, &mut g2.id);
275 g2.abs_transform = parent.abs_transform;
276 g2.clip_path = Some(Arc::new(clip));
277 g2.children.push(Node::Group(Box::new(g)));
278 g2.calculate_bounding_boxes();
279
280 parent.children.push(Node::Group(Box::new(g2)));
281 } else {
282 parent.children.push(Node::Group(Box::new(g)));
283 }
284
285 Some(())
286}
287
288pub(crate) fn get_href_data(href: &str, state: &converter::State) -> Option<ImageKind> {
289 if let Ok(url) = data_url::DataUrl::process(href) {
290 let (data, _) = url.decode_to_vec().ok()?;
291
292 let mime = format!(
293 "{}/{}",
294 url.mime_type().type_.as_str(),
295 url.mime_type().subtype.as_str()
296 );
297
298 (state.opt.image_href_resolver.resolve_data)(&mime, Arc::new(data), state.opt)
299 } else {
300 (state.opt.image_href_resolver.resolve_string)(href, state.opt)
301 }
302}
303
304fn get_image_file_format(path: &std::path::Path, data: &[u8]) -> Option<ImageFormat> {
307 let ext = path.extension().and_then(|e| e.to_str())?.to_lowercase();
308 if ext == "svg" || ext == "svgz" {
309 return Some(ImageFormat::SVG);
310 }
311
312 get_image_data_format(data)
313}
314
315fn get_image_data_format(data: &[u8]) -> Option<ImageFormat> {
317 match imagesize::image_type(data).ok()? {
318 imagesize::ImageType::Gif => Some(ImageFormat::GIF),
319 imagesize::ImageType::Jpeg => Some(ImageFormat::JPEG),
320 imagesize::ImageType::Png => Some(ImageFormat::PNG),
321 imagesize::ImageType::Webp => Some(ImageFormat::WEBP),
322 _ => None,
323 }
324}
325
326pub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option<ImageKind> {
331 let sub_opt = Options {
332 resources_dir: None,
333 dpi: opt.dpi,
334 font_size: opt.font_size,
335 languages: opt.languages.clone(),
336 shape_rendering: opt.shape_rendering,
337 text_rendering: opt.text_rendering,
338 image_rendering: opt.image_rendering,
339 default_size: opt.default_size,
340 image_href_resolver: ImageHrefResolver {
343 resolve_data: Box::new(|_, _, _| None),
344 resolve_string: Box::new(|_, _| None),
345 },
346 #[cfg(feature = "text")]
349 fontdb: opt.fontdb.clone(),
350 #[cfg(feature = "text")]
352 font_resolver: crate::FontResolver {
353 select_font: Box::new(|font, db| (opt.font_resolver.select_font)(font, db)),
354 select_fallback: Box::new(|c, used_fonts, db| {
355 (opt.font_resolver.select_fallback)(c, used_fonts, db)
356 }),
357 },
358 ..Options::default()
359 };
360
361 let tree = Tree::from_data(data, &sub_opt);
362 let tree = match tree {
363 Ok(tree) => tree,
364 Err(_) => {
365 log::warn!("Failed to load subsvg image.");
366 return None;
367 }
368 };
369
370 Some(ImageKind::SVG(tree))
371}
372
373fn fit_view_box(size: Size, rect: NonZeroRect, aspect: AspectRatio) -> Size {
375 let s = rect.size();
376
377 if aspect.align == svgtypes::Align::None {
378 s
379 } else if aspect.slice {
380 size.expand_to(s)
381 } else {
382 size.scale_to(s)
383 }
384}