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