1 | // Copyright 2018 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use std::collections::{HashMap, HashSet}; |
5 | use std::hash::{Hash, Hasher}; |
6 | use std::str::FromStr; |
7 | use std::sync::Arc; |
8 | |
9 | #[cfg (feature = "text" )] |
10 | use fontdb::Database; |
11 | use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin}; |
12 | use tiny_skia_path::PathBuilder; |
13 | |
14 | use super::svgtree::{self, AId, EId, FromValue, SvgNode}; |
15 | use super::units::{self, convert_length}; |
16 | use super::{marker, Error, Options}; |
17 | use crate::parser::paint_server::process_paint; |
18 | use crate::*; |
19 | |
20 | #[derive (Clone)] |
21 | pub struct State<'a> { |
22 | pub(crate) parent_clip_path: Option<SvgNode<'a, 'a>>, |
23 | pub(crate) parent_markers: Vec<SvgNode<'a, 'a>>, |
24 | /// Stores the resolved fill and stroke of a use node |
25 | /// or a path element (for markers) |
26 | pub(crate) context_element: Option<(Option<Fill>, Option<Stroke>)>, |
27 | pub(crate) fe_image_link: bool, |
28 | /// A viewBox of the parent SVG element. |
29 | pub(crate) view_box: NonZeroRect, |
30 | /// A size of the parent `use` element. |
31 | /// Used only during nested `svg` size resolving. |
32 | /// Width and height can be set independently. |
33 | pub(crate) use_size: (Option<f32>, Option<f32>), |
34 | pub(crate) opt: &'a Options<'a>, |
35 | } |
36 | |
37 | #[derive (Clone)] |
38 | pub struct Cache { |
39 | /// This fontdb is initialized from [`Options::fontdb`] and then populated |
40 | /// over the course of conversion. |
41 | #[cfg (feature = "text" )] |
42 | pub fontdb: Arc<Database>, |
43 | |
44 | pub clip_paths: HashMap<String, Arc<ClipPath>>, |
45 | pub masks: HashMap<String, Arc<Mask>>, |
46 | pub filters: HashMap<String, Arc<filter::Filter>>, |
47 | pub paint: HashMap<String, Paint>, |
48 | |
49 | // used for ID generation |
50 | all_ids: HashSet<u64>, |
51 | linear_gradient_index: usize, |
52 | radial_gradient_index: usize, |
53 | pattern_index: usize, |
54 | clip_path_index: usize, |
55 | mask_index: usize, |
56 | filter_index: usize, |
57 | image_index: usize, |
58 | } |
59 | |
60 | impl Cache { |
61 | pub(crate) fn new(#[cfg (feature = "text" )] fontdb: Arc<Database>) -> Self { |
62 | Self { |
63 | #[cfg (feature = "text" )] |
64 | fontdb, |
65 | |
66 | clip_paths: HashMap::new(), |
67 | masks: HashMap::new(), |
68 | filters: HashMap::new(), |
69 | paint: HashMap::new(), |
70 | |
71 | all_ids: HashSet::new(), |
72 | linear_gradient_index: 0, |
73 | radial_gradient_index: 0, |
74 | pattern_index: 0, |
75 | clip_path_index: 0, |
76 | mask_index: 0, |
77 | filter_index: 0, |
78 | image_index: 0, |
79 | } |
80 | } |
81 | |
82 | // TODO: macros? |
83 | pub(crate) fn gen_linear_gradient_id(&mut self) -> NonEmptyString { |
84 | loop { |
85 | self.linear_gradient_index += 1; |
86 | let new_id = format!("linearGradient {}" , self.linear_gradient_index); |
87 | let new_hash = string_hash(&new_id); |
88 | if !self.all_ids.contains(&new_hash) { |
89 | return NonEmptyString::new(new_id).unwrap(); |
90 | } |
91 | } |
92 | } |
93 | |
94 | pub(crate) fn gen_radial_gradient_id(&mut self) -> NonEmptyString { |
95 | loop { |
96 | self.radial_gradient_index += 1; |
97 | let new_id = format!("radialGradient {}" , self.radial_gradient_index); |
98 | let new_hash = string_hash(&new_id); |
99 | if !self.all_ids.contains(&new_hash) { |
100 | return NonEmptyString::new(new_id).unwrap(); |
101 | } |
102 | } |
103 | } |
104 | |
105 | pub(crate) fn gen_pattern_id(&mut self) -> NonEmptyString { |
106 | loop { |
107 | self.pattern_index += 1; |
108 | let new_id = format!("pattern {}" , self.pattern_index); |
109 | let new_hash = string_hash(&new_id); |
110 | if !self.all_ids.contains(&new_hash) { |
111 | return NonEmptyString::new(new_id).unwrap(); |
112 | } |
113 | } |
114 | } |
115 | |
116 | pub(crate) fn gen_clip_path_id(&mut self) -> NonEmptyString { |
117 | loop { |
118 | self.clip_path_index += 1; |
119 | let new_id = format!("clipPath {}" , self.clip_path_index); |
120 | let new_hash = string_hash(&new_id); |
121 | if !self.all_ids.contains(&new_hash) { |
122 | return NonEmptyString::new(new_id).unwrap(); |
123 | } |
124 | } |
125 | } |
126 | |
127 | pub(crate) fn gen_mask_id(&mut self) -> NonEmptyString { |
128 | loop { |
129 | self.mask_index += 1; |
130 | let new_id = format!("mask {}" , self.mask_index); |
131 | let new_hash = string_hash(&new_id); |
132 | if !self.all_ids.contains(&new_hash) { |
133 | return NonEmptyString::new(new_id).unwrap(); |
134 | } |
135 | } |
136 | } |
137 | |
138 | pub(crate) fn gen_filter_id(&mut self) -> NonEmptyString { |
139 | loop { |
140 | self.filter_index += 1; |
141 | let new_id = format!("filter {}" , self.filter_index); |
142 | let new_hash = string_hash(&new_id); |
143 | if !self.all_ids.contains(&new_hash) { |
144 | return NonEmptyString::new(new_id).unwrap(); |
145 | } |
146 | } |
147 | } |
148 | |
149 | pub(crate) fn gen_image_id(&mut self) -> NonEmptyString { |
150 | loop { |
151 | self.image_index += 1; |
152 | let new_id = format!("image {}" , self.image_index); |
153 | let new_hash = string_hash(&new_id); |
154 | if !self.all_ids.contains(&new_hash) { |
155 | return NonEmptyString::new(new_id).unwrap(); |
156 | } |
157 | } |
158 | } |
159 | } |
160 | |
161 | // TODO: is there a simpler way? |
162 | fn string_hash(s: &str) -> u64 { |
163 | let mut h: DefaultHasher = std::collections::hash_map::DefaultHasher::new(); |
164 | s.hash(&mut h); |
165 | h.finish() |
166 | } |
167 | |
168 | impl<'a, 'input: 'a> SvgNode<'a, 'input> { |
169 | pub(crate) fn convert_length( |
170 | &self, |
171 | aid: AId, |
172 | object_units: Units, |
173 | state: &State, |
174 | def: Length, |
175 | ) -> f32 { |
176 | units::convert_length( |
177 | self.attribute(aid).unwrap_or(def), |
178 | *self, |
179 | aid, |
180 | object_units, |
181 | state, |
182 | ) |
183 | } |
184 | |
185 | pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 { |
186 | self.convert_length(aid, Units::UserSpaceOnUse, state, def) |
187 | } |
188 | |
189 | pub fn parse_viewbox(&self) -> Option<NonZeroRect> { |
190 | let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?; |
191 | NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32) |
192 | } |
193 | |
194 | pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 { |
195 | debug_assert!( |
196 | !matches!(aid, AId::BaselineShift | AId::FontSize), |
197 | " {} cannot be resolved via this function" , |
198 | aid |
199 | ); |
200 | |
201 | if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) { |
202 | if let Some(length) = n.attribute(aid) { |
203 | return units::convert_user_length(length, n, aid, state); |
204 | } |
205 | } |
206 | |
207 | def |
208 | } |
209 | |
210 | pub fn resolve_valid_length( |
211 | &self, |
212 | aid: AId, |
213 | state: &State, |
214 | def: f32, |
215 | ) -> Option<NonZeroPositiveF32> { |
216 | let n = self.resolve_length(aid, state, def); |
217 | NonZeroPositiveF32::new(n) |
218 | } |
219 | |
220 | pub(crate) fn try_convert_length( |
221 | &self, |
222 | aid: AId, |
223 | object_units: Units, |
224 | state: &State, |
225 | ) -> Option<f32> { |
226 | Some(units::convert_length( |
227 | self.attribute(aid)?, |
228 | *self, |
229 | aid, |
230 | object_units, |
231 | state, |
232 | )) |
233 | } |
234 | |
235 | pub fn has_valid_transform(&self, aid: AId) -> bool { |
236 | // Do not use Node::attribute::<Transform>, because it will always |
237 | // return a valid transform. |
238 | |
239 | let attr = match self.attribute(aid) { |
240 | Some(attr) => attr, |
241 | None => return true, |
242 | }; |
243 | |
244 | let ts = match svgtypes::Transform::from_str(attr) { |
245 | Ok(v) => v, |
246 | Err(_) => return true, |
247 | }; |
248 | |
249 | let ts = Transform::from_row( |
250 | ts.a as f32, |
251 | ts.b as f32, |
252 | ts.c as f32, |
253 | ts.d as f32, |
254 | ts.e as f32, |
255 | ts.f as f32, |
256 | ); |
257 | ts.is_valid() |
258 | } |
259 | |
260 | pub fn is_visible_element(&self, opt: &crate::Options) -> bool { |
261 | self.attribute(AId::Display) != Some("none" ) |
262 | && self.has_valid_transform(AId::Transform) |
263 | && super::switch::is_condition_passed(*self, opt) |
264 | } |
265 | } |
266 | |
267 | pub trait SvgColorExt { |
268 | fn split_alpha(self) -> (Color, Opacity); |
269 | } |
270 | |
271 | impl SvgColorExt for svgtypes::Color { |
272 | fn split_alpha(self) -> (Color, Opacity) { |
273 | ( |
274 | Color::new_rgb(self.red, self.green, self.blue), |
275 | Opacity::new_u8(self.alpha), |
276 | ) |
277 | } |
278 | } |
279 | |
280 | /// Converts an input `Document` into a `Tree`. |
281 | /// |
282 | /// # Errors |
283 | /// |
284 | /// - If `Document` doesn't have an SVG node - returns an empty tree. |
285 | /// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`. |
286 | pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> { |
287 | let svg = svg_doc.root_element(); |
288 | let (size, restore_viewbox) = resolve_svg_size(&svg, opt); |
289 | let size = size?; |
290 | let view_box = ViewBox { |
291 | rect: svg |
292 | .parse_viewbox() |
293 | .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)), |
294 | aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(), |
295 | }; |
296 | |
297 | let background_color = svg |
298 | .attribute::<&str>(AId::BackgroundColor) |
299 | .and_then(|s| svgtypes::Paint::from_str(s).ok()) |
300 | .and_then(|paint| match paint { |
301 | svgtypes::Paint::Color(c) => Some(c), |
302 | _ => None, |
303 | }); |
304 | |
305 | let mut tree = Tree { |
306 | size, |
307 | root: Group::empty(), |
308 | linear_gradients: Vec::new(), |
309 | radial_gradients: Vec::new(), |
310 | patterns: Vec::new(), |
311 | clip_paths: Vec::new(), |
312 | masks: Vec::new(), |
313 | filters: Vec::new(), |
314 | #[cfg (feature = "text" )] |
315 | fontdb: opt.fontdb.clone(), |
316 | }; |
317 | |
318 | if !svg.is_visible_element(opt) { |
319 | return Ok(tree); |
320 | } |
321 | |
322 | let state = State { |
323 | parent_clip_path: None, |
324 | context_element: None, |
325 | parent_markers: Vec::new(), |
326 | fe_image_link: false, |
327 | view_box: view_box.rect, |
328 | use_size: (None, None), |
329 | opt, |
330 | }; |
331 | |
332 | let mut cache = Cache::new( |
333 | #[cfg (feature = "text" )] |
334 | opt.fontdb.clone(), |
335 | ); |
336 | |
337 | for node in svg_doc.descendants() { |
338 | if let Some(tag) = node.tag_name() { |
339 | if matches!( |
340 | tag, |
341 | EId::ClipPath |
342 | | EId::Filter |
343 | | EId::LinearGradient |
344 | | EId::Mask |
345 | | EId::Pattern |
346 | | EId::RadialGradient |
347 | | EId::Image |
348 | ) { |
349 | if !node.element_id().is_empty() { |
350 | cache.all_ids.insert(string_hash(node.element_id())); |
351 | } |
352 | } |
353 | } |
354 | } |
355 | |
356 | let root_ts = view_box.to_transform(tree.size()); |
357 | if root_ts.is_identity() && background_color.is_none() { |
358 | convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root); |
359 | } else { |
360 | let mut g = Group::empty(); |
361 | |
362 | if let Some(background_color) = background_color { |
363 | if let Some(path) = background_path(background_color, view_box.rect.to_rect()) { |
364 | g.children.push(Node::Path(Box::new(path))); |
365 | } |
366 | } |
367 | |
368 | g.transform = root_ts; |
369 | g.abs_transform = root_ts; |
370 | convert_children(svg_doc.root(), &state, &mut cache, &mut g); |
371 | g.calculate_bounding_boxes(); |
372 | tree.root.children.push(Node::Group(Box::new(g))); |
373 | } |
374 | |
375 | // Clear cache to make sure that all `Arc<T>` objects have a single strong reference. |
376 | cache.clip_paths.clear(); |
377 | cache.masks.clear(); |
378 | cache.filters.clear(); |
379 | cache.paint.clear(); |
380 | |
381 | super::paint_server::update_paint_servers( |
382 | &mut tree.root, |
383 | Transform::default(), |
384 | None, |
385 | None, |
386 | &mut cache, |
387 | ); |
388 | tree.collect_paint_servers(); |
389 | tree.root.collect_clip_paths(&mut tree.clip_paths); |
390 | tree.root.collect_masks(&mut tree.masks); |
391 | tree.root.collect_filters(&mut tree.filters); |
392 | tree.root.calculate_bounding_boxes(); |
393 | |
394 | // The fontdb might have been mutated and we want to apply these changes to |
395 | // the tree's fontdb. |
396 | #[cfg (feature = "text" )] |
397 | { |
398 | tree.fontdb = cache.fontdb; |
399 | } |
400 | |
401 | if restore_viewbox { |
402 | calculate_svg_bbox(&mut tree); |
403 | } |
404 | |
405 | Ok(tree) |
406 | } |
407 | |
408 | fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> { |
409 | let path: Path = PathBuilder::from_rect(area); |
410 | |
411 | let fill: Fill = Fill { |
412 | paint: Paint::Color(Color::new_rgb( |
413 | background_color.red, |
414 | background_color.green, |
415 | background_color.blue, |
416 | )), |
417 | opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?, |
418 | ..Default::default() |
419 | }; |
420 | |
421 | let mut path: Path = Path::new_simple(data:Arc::new(data:path))?; |
422 | path.fill = Some(fill); |
423 | |
424 | Some(path) |
425 | } |
426 | |
427 | fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) { |
428 | let mut state = State { |
429 | parent_clip_path: None, |
430 | context_element: None, |
431 | parent_markers: Vec::new(), |
432 | fe_image_link: false, |
433 | view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(), |
434 | use_size: (None, None), |
435 | opt, |
436 | }; |
437 | |
438 | let def = Length::new(100.0, Unit::Percent); |
439 | let mut width: Length = svg.attribute(AId::Width).unwrap_or(def); |
440 | let mut height: Length = svg.attribute(AId::Height).unwrap_or(def); |
441 | |
442 | let view_box = svg.parse_viewbox(); |
443 | |
444 | let restore_viewbox = |
445 | if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() { |
446 | // Apply the percentages to the fallback size. |
447 | if width.unit == Unit::Percent { |
448 | width = Length::new( |
449 | (width.number / 100.0) * state.opt.default_size.width() as f64, |
450 | Unit::None, |
451 | ); |
452 | } |
453 | |
454 | if height.unit == Unit::Percent { |
455 | height = Length::new( |
456 | (height.number / 100.0) * state.opt.default_size.height() as f64, |
457 | Unit::None, |
458 | ); |
459 | } |
460 | |
461 | true |
462 | } else { |
463 | false |
464 | }; |
465 | |
466 | let size = if let Some(vbox) = view_box { |
467 | state.view_box = vbox; |
468 | |
469 | let w = if width.unit == Unit::Percent { |
470 | vbox.width() * (width.number as f32 / 100.0) |
471 | } else { |
472 | svg.convert_user_length(AId::Width, &state, def) |
473 | }; |
474 | |
475 | let h = if height.unit == Unit::Percent { |
476 | vbox.height() * (height.number as f32 / 100.0) |
477 | } else { |
478 | svg.convert_user_length(AId::Height, &state, def) |
479 | }; |
480 | |
481 | Size::from_wh(w, h) |
482 | } else { |
483 | Size::from_wh( |
484 | svg.convert_user_length(AId::Width, &state, def), |
485 | svg.convert_user_length(AId::Height, &state, def), |
486 | ) |
487 | }; |
488 | |
489 | (size.ok_or(Error::InvalidSize), restore_viewbox) |
490 | } |
491 | |
492 | /// Calculates SVG's size and viewBox in case there were not set. |
493 | /// |
494 | /// Simply iterates over all nodes and calculates a bounding box. |
495 | fn calculate_svg_bbox(tree: &mut Tree) { |
496 | let bbox: Rect = tree.root.abs_bounding_box(); |
497 | if let Some(size: Size) = Size::from_wh(width:bbox.right(), height:bbox.bottom()) { |
498 | tree.size = size; |
499 | } |
500 | } |
501 | |
502 | #[inline (never)] |
503 | pub(crate) fn convert_children( |
504 | parent_node: SvgNode, |
505 | state: &State, |
506 | cache: &mut Cache, |
507 | parent: &mut Group, |
508 | ) { |
509 | for node: SvgNode<'_, '_> in parent_node.children() { |
510 | convert_element(node, state, cache, parent); |
511 | } |
512 | } |
513 | |
514 | #[inline (never)] |
515 | pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) { |
516 | let tag_name = match node.tag_name() { |
517 | Some(v) => v, |
518 | None => return, |
519 | }; |
520 | |
521 | if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) { |
522 | return; |
523 | } |
524 | |
525 | if !node.is_visible_element(state.opt) { |
526 | return; |
527 | } |
528 | |
529 | if tag_name == EId::Use { |
530 | super::use_node::convert(node, state, cache, parent); |
531 | return; |
532 | } |
533 | |
534 | if tag_name == EId::Switch { |
535 | super::switch::convert(node, state, cache, parent); |
536 | return; |
537 | } |
538 | |
539 | if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { |
540 | convert_element_impl(tag_name, node, state, cache, g); |
541 | }) { |
542 | parent.children.push(Node::Group(Box::new(g))); |
543 | } |
544 | } |
545 | |
546 | #[inline (never)] |
547 | fn convert_element_impl( |
548 | tag_name: EId, |
549 | node: SvgNode, |
550 | state: &State, |
551 | cache: &mut Cache, |
552 | parent: &mut Group, |
553 | ) { |
554 | match tag_name { |
555 | EId::Rect |
556 | | EId::Circle |
557 | | EId::Ellipse |
558 | | EId::Line |
559 | | EId::Polyline |
560 | | EId::Polygon |
561 | | EId::Path => { |
562 | if let Some(path) = super::shapes::convert(node, state) { |
563 | convert_path(node, path, state, cache, parent); |
564 | } |
565 | } |
566 | EId::Image => { |
567 | super::image::convert(node, state, cache, parent); |
568 | } |
569 | EId::Text => { |
570 | #[cfg (feature = "text" )] |
571 | { |
572 | super::text::convert(node, state, cache, parent); |
573 | } |
574 | } |
575 | EId::Svg => { |
576 | if node.parent_element().is_some() { |
577 | super::use_node::convert_svg(node, state, cache, parent); |
578 | } else { |
579 | // Skip root `svg`. |
580 | convert_children(node, state, cache, parent); |
581 | } |
582 | } |
583 | EId::G => { |
584 | convert_children(node, state, cache, parent); |
585 | } |
586 | _ => {} |
587 | } |
588 | } |
589 | |
590 | // `clipPath` can have only shape and `text` children. |
591 | // |
592 | // `line` doesn't impact rendering because stroke is always disabled |
593 | // for `clipPath` children. |
594 | #[inline (never)] |
595 | pub(crate) fn convert_clip_path_elements( |
596 | clip_node: SvgNode, |
597 | state: &State, |
598 | cache: &mut Cache, |
599 | parent: &mut Group, |
600 | ) { |
601 | for node in clip_node.children() { |
602 | let tag_name = match node.tag_name() { |
603 | Some(v) => v, |
604 | None => continue, |
605 | }; |
606 | |
607 | if !tag_name.is_graphic() { |
608 | continue; |
609 | } |
610 | |
611 | if !node.is_visible_element(state.opt) { |
612 | continue; |
613 | } |
614 | |
615 | if tag_name == EId::Use { |
616 | super::use_node::convert(node, state, cache, parent); |
617 | continue; |
618 | } |
619 | |
620 | if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { |
621 | convert_clip_path_elements_impl(tag_name, node, state, cache, g); |
622 | }) { |
623 | parent.children.push(Node::Group(Box::new(g))); |
624 | } |
625 | } |
626 | } |
627 | |
628 | #[inline (never)] |
629 | fn convert_clip_path_elements_impl( |
630 | tag_name: EId, |
631 | node: SvgNode, |
632 | state: &State, |
633 | cache: &mut Cache, |
634 | parent: &mut Group, |
635 | ) { |
636 | match tag_name { |
637 | EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => { |
638 | if let Some(path: Arc) = super::shapes::convert(node, state) { |
639 | convert_path(node, path, state, cache, parent); |
640 | } |
641 | } |
642 | EId::Text => { |
643 | #[cfg (feature = "text" )] |
644 | { |
645 | super::text::convert(node, state, cache, parent); |
646 | } |
647 | } |
648 | _ => { |
649 | log::warn!("' {}' is no a valid 'clip-path' child." , tag_name); |
650 | } |
651 | } |
652 | } |
653 | |
654 | #[derive (Clone, Copy, PartialEq, Debug)] |
655 | enum Isolation { |
656 | Auto, |
657 | Isolate, |
658 | } |
659 | |
660 | impl Default for Isolation { |
661 | fn default() -> Self { |
662 | Self::Auto |
663 | } |
664 | } |
665 | |
666 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation { |
667 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
668 | match value { |
669 | "auto" => Some(Isolation::Auto), |
670 | "isolate" => Some(Isolation::Isolate), |
671 | _ => None, |
672 | } |
673 | } |
674 | } |
675 | |
676 | // TODO: explain |
677 | pub(crate) fn convert_group( |
678 | node: SvgNode, |
679 | state: &State, |
680 | force: bool, |
681 | cache: &mut Cache, |
682 | parent: &mut Group, |
683 | collect_children: &dyn Fn(&mut Cache, &mut Group), |
684 | ) -> Option<Group> { |
685 | // A `clipPath` child cannot have an opacity. |
686 | let opacity = if state.parent_clip_path.is_none() { |
687 | node.attribute::<Opacity>(AId::Opacity) |
688 | .unwrap_or(Opacity::ONE) |
689 | } else { |
690 | Opacity::ONE |
691 | }; |
692 | |
693 | let transform = node.resolve_transform(AId::Transform, state); |
694 | let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default(); |
695 | let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default(); |
696 | let isolate = isolation == Isolation::Isolate; |
697 | |
698 | // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. |
699 | let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use)); |
700 | let id = if is_g_or_use && state.parent_markers.is_empty() { |
701 | node.element_id().to_string() |
702 | } else { |
703 | String::new() |
704 | }; |
705 | |
706 | let abs_transform = parent.abs_transform.pre_concat(transform); |
707 | let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(); |
708 | let mut g = Group { |
709 | id, |
710 | transform, |
711 | abs_transform, |
712 | opacity, |
713 | blend_mode, |
714 | isolate, |
715 | clip_path: None, |
716 | mask: None, |
717 | filters: Vec::new(), |
718 | is_context_element: false, |
719 | bounding_box: dummy, |
720 | abs_bounding_box: dummy, |
721 | stroke_bounding_box: dummy, |
722 | abs_stroke_bounding_box: dummy, |
723 | layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), |
724 | abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), |
725 | children: Vec::new(), |
726 | }; |
727 | collect_children(cache, &mut g); |
728 | |
729 | // We need to know group's bounding box before converting |
730 | // clipPaths, masks and filters. |
731 | let object_bbox = g.calculate_object_bbox(); |
732 | |
733 | // `mask` and `filter` cannot be set on `clipPath` children. |
734 | // But `clip-path` can. |
735 | |
736 | let mut clip_path = None; |
737 | if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) { |
738 | clip_path = super::clippath::convert(link, state, object_bbox, cache); |
739 | if clip_path.is_none() { |
740 | return None; |
741 | } |
742 | } |
743 | |
744 | let mut mask = None; |
745 | if state.parent_clip_path.is_none() { |
746 | if let Some(link) = node.attribute::<SvgNode>(AId::Mask) { |
747 | mask = super::mask::convert(link, state, object_bbox, cache); |
748 | if mask.is_none() { |
749 | return None; |
750 | } |
751 | } |
752 | } |
753 | |
754 | let filters = { |
755 | let mut filters = Vec::new(); |
756 | if state.parent_clip_path.is_none() { |
757 | if node.attribute(AId::Filter) == Some("none" ) { |
758 | // Do nothing. |
759 | } else if node.has_attribute(AId::Filter) { |
760 | if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) { |
761 | filters = f; |
762 | } else { |
763 | // A filter that not a link or a filter with a link to a non existing element. |
764 | // |
765 | // Unlike `clip-path` and `mask`, when a `filter` link is invalid |
766 | // then the whole element should be ignored. |
767 | // |
768 | // This is kinda an undefined behaviour. |
769 | // In most cases, Chrome, Firefox and rsvg will ignore such elements, |
770 | // but in some cases Chrome allows it. Not sure why. |
771 | // Inkscape (0.92) simply ignores such attributes, rendering element as is. |
772 | // Batik (1.12) crashes. |
773 | // |
774 | // Test file: e-filter-051.svg |
775 | return None; |
776 | } |
777 | } |
778 | } |
779 | |
780 | filters |
781 | }; |
782 | |
783 | let required = opacity.get().approx_ne_ulps(&1.0, 4) |
784 | || clip_path.is_some() |
785 | || mask.is_some() |
786 | || !filters.is_empty() |
787 | || !transform.is_identity() |
788 | || blend_mode != BlendMode::Normal |
789 | || isolate |
790 | || is_g_or_use |
791 | || force; |
792 | |
793 | if !required { |
794 | parent.children.append(&mut g.children); |
795 | return None; |
796 | } |
797 | |
798 | g.clip_path = clip_path; |
799 | g.mask = mask; |
800 | g.filters = filters; |
801 | |
802 | // Must be called after we set Group::filters |
803 | g.calculate_bounding_boxes(); |
804 | |
805 | Some(g) |
806 | } |
807 | |
808 | fn convert_path( |
809 | node: SvgNode, |
810 | tiny_skia_path: Arc<tiny_skia_path::Path>, |
811 | state: &State, |
812 | cache: &mut Cache, |
813 | parent: &mut Group, |
814 | ) { |
815 | debug_assert!(tiny_skia_path.len() >= 2); |
816 | if tiny_skia_path.len() < 2 { |
817 | return; |
818 | } |
819 | |
820 | let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0; |
821 | let mut fill = super::style::resolve_fill(node, has_bbox, state, cache); |
822 | let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache); |
823 | let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); |
824 | let mut visible = visibility == Visibility::Visible; |
825 | let rendering_mode: ShapeRendering = node |
826 | .find_attribute(AId::ShapeRendering) |
827 | .unwrap_or(state.opt.shape_rendering); |
828 | |
829 | // TODO: handle `markers` before `stroke` |
830 | let raw_paint_order: svgtypes::PaintOrder = |
831 | node.find_attribute(AId::PaintOrder).unwrap_or_default(); |
832 | let paint_order = svg_paint_order_to_usvg(raw_paint_order); |
833 | let path_transform = parent.abs_transform; |
834 | |
835 | // If a path doesn't have a fill or a stroke then it's invisible. |
836 | // By setting `visibility` to `hidden` we are disabling rendering of this path. |
837 | if fill.is_none() && stroke.is_none() { |
838 | visible = false; |
839 | } |
840 | |
841 | if let Some(fill) = fill.as_mut() { |
842 | if let Some(ContextElement::PathNode(context_transform, context_bbox)) = |
843 | fill.context_element |
844 | { |
845 | process_paint( |
846 | &mut fill.paint, |
847 | true, |
848 | context_transform, |
849 | context_bbox.map(|r| r.to_rect()), |
850 | path_transform, |
851 | tiny_skia_path.bounds(), |
852 | cache, |
853 | ); |
854 | fill.context_element = None; |
855 | } |
856 | } |
857 | |
858 | if let Some(stroke) = stroke.as_mut() { |
859 | if let Some(ContextElement::PathNode(context_transform, context_bbox)) = |
860 | stroke.context_element |
861 | { |
862 | process_paint( |
863 | &mut stroke.paint, |
864 | true, |
865 | context_transform, |
866 | context_bbox.map(|r| r.to_rect()), |
867 | path_transform, |
868 | tiny_skia_path.bounds(), |
869 | cache, |
870 | ); |
871 | stroke.context_element = None; |
872 | } |
873 | } |
874 | |
875 | let mut marker = None; |
876 | if marker::is_valid(node) && visibility == Visibility::Visible { |
877 | let mut marker_group = Group { |
878 | abs_transform: parent.abs_transform, |
879 | ..Group::empty() |
880 | }; |
881 | |
882 | let mut marker_state = state.clone(); |
883 | |
884 | let bbox = tiny_skia_path |
885 | .compute_tight_bounds() |
886 | .and_then(|r| r.to_non_zero_rect()); |
887 | |
888 | let fill = fill.clone().map(|mut f| { |
889 | f.context_element = Some(ContextElement::PathNode(path_transform, bbox)); |
890 | f |
891 | }); |
892 | |
893 | let stroke = stroke.clone().map(|mut s| { |
894 | s.context_element = Some(ContextElement::PathNode(path_transform, bbox)); |
895 | s |
896 | }); |
897 | |
898 | marker_state.context_element = Some((fill, stroke)); |
899 | |
900 | marker::convert( |
901 | node, |
902 | &tiny_skia_path, |
903 | &marker_state, |
904 | cache, |
905 | &mut marker_group, |
906 | ); |
907 | marker_group.calculate_bounding_boxes(); |
908 | marker = Some(marker_group); |
909 | } |
910 | |
911 | // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. |
912 | let id = if state.parent_markers.is_empty() { |
913 | node.element_id().to_string() |
914 | } else { |
915 | String::new() |
916 | }; |
917 | |
918 | let path = Path::new( |
919 | id, |
920 | visible, |
921 | fill, |
922 | stroke, |
923 | paint_order, |
924 | rendering_mode, |
925 | tiny_skia_path, |
926 | path_transform, |
927 | ); |
928 | |
929 | let path = match path { |
930 | Some(v) => v, |
931 | None => return, |
932 | }; |
933 | |
934 | match (raw_paint_order.order, marker) { |
935 | ([PaintOrderKind::Markers, _, _], Some(markers_node)) => { |
936 | parent.children.push(Node::Group(Box::new(markers_node))); |
937 | parent.children.push(Node::Path(Box::new(path.clone()))); |
938 | } |
939 | ([first, PaintOrderKind::Markers, last], Some(markers_node)) => { |
940 | append_single_paint_path(first, &path, parent); |
941 | parent.children.push(Node::Group(Box::new(markers_node))); |
942 | append_single_paint_path(last, &path, parent); |
943 | } |
944 | ([_, _, PaintOrderKind::Markers], Some(markers_node)) => { |
945 | parent.children.push(Node::Path(Box::new(path.clone()))); |
946 | parent.children.push(Node::Group(Box::new(markers_node))); |
947 | } |
948 | _ => parent.children.push(Node::Path(Box::new(path.clone()))), |
949 | } |
950 | } |
951 | |
952 | fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) { |
953 | match paint_order_kind { |
954 | PaintOrderKind::Fill => { |
955 | if path.fill.is_some() { |
956 | let mut fill_path: Path = path.clone(); |
957 | fill_path.stroke = None; |
958 | fill_path.id = String::new(); |
959 | parent.children.push(Node::Path(Box::new(fill_path))); |
960 | } |
961 | } |
962 | PaintOrderKind::Stroke => { |
963 | if path.stroke.is_some() { |
964 | let mut stroke_path: Path = path.clone(); |
965 | stroke_path.fill = None; |
966 | stroke_path.id = String::new(); |
967 | parent.children.push(Node::Path(Box::new(stroke_path))); |
968 | } |
969 | } |
970 | _ => {} |
971 | } |
972 | } |
973 | |
974 | pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder { |
975 | match (order.order[0], order.order[1]) { |
976 | (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill, |
977 | (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => { |
978 | PaintOrder::StrokeAndFill |
979 | } |
980 | _ => PaintOrder::FillAndStroke, |
981 | } |
982 | } |
983 | |
984 | impl SvgNode<'_, '_> { |
985 | pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform { |
986 | let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default(); |
987 | let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin); |
988 | |
989 | if let Some(transform_origin) = transform_origin { |
990 | let dx = convert_length( |
991 | transform_origin.x_offset, |
992 | *self, |
993 | AId::Width, |
994 | Units::UserSpaceOnUse, |
995 | state, |
996 | ); |
997 | let dy = convert_length( |
998 | transform_origin.y_offset, |
999 | *self, |
1000 | AId::Height, |
1001 | Units::UserSpaceOnUse, |
1002 | state, |
1003 | ); |
1004 | transform = Transform::default() |
1005 | .pre_translate(dx, dy) |
1006 | .pre_concat(transform) |
1007 | .pre_translate(-dx, -dy); |
1008 | } |
1009 | |
1010 | transform |
1011 | } |
1012 | } |
1013 | |