1use std::collections::{HashMap, HashSet};
6use std::hash::{Hash, Hasher};
7use std::str::FromStr;
8use std::sync::Arc;
9
10#[cfg(feature = "text")]
11use fontdb::Database;
12use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin};
13
14use super::svgtree::{self, AId, EId, FromValue, SvgNode};
15use super::units::{self, convert_length};
16use super::{marker, Error, Options};
17use crate::parser::paint_server::process_paint;
18use crate::*;
19
20#[derive(Clone)]
21pub struct State<'a> {
22 pub(crate) parent_clip_path: Option<SvgNode<'a, 'a>>,
23 pub(crate) parent_markers: Vec<SvgNode<'a, 'a>>,
24 pub(crate) context_element: Option<(Option<Fill>, Option<Stroke>)>,
27 pub(crate) fe_image_link: bool,
28 pub(crate) view_box: NonZeroRect,
30 pub(crate) use_size: (Option<f32>, Option<f32>),
34 pub(crate) opt: &'a Options<'a>,
35}
36
37#[derive(Clone)]
38pub struct Cache {
39 #[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 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
60impl 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 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
161fn string_hash(s: &str) -> u64 {
163 let mut h = std::collections::hash_map::DefaultHasher::new();
164 s.hash(&mut h);
165 h.finish()
166}
167
168impl<'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 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
267pub trait SvgColorExt {
268 fn split_alpha(self) -> (Color, Opacity);
269}
270
271impl 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
280pub(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 mut tree = Tree {
298 size,
299 root: Group::empty(),
300 linear_gradients: Vec::new(),
301 radial_gradients: Vec::new(),
302 patterns: Vec::new(),
303 clip_paths: Vec::new(),
304 masks: Vec::new(),
305 filters: Vec::new(),
306 #[cfg(feature = "text")]
307 fontdb: opt.fontdb.clone(),
308 };
309
310 if !svg.is_visible_element(opt) {
311 return Ok(tree);
312 }
313
314 let state = State {
315 parent_clip_path: None,
316 context_element: None,
317 parent_markers: Vec::new(),
318 fe_image_link: false,
319 view_box: view_box.rect,
320 use_size: (None, None),
321 opt,
322 };
323
324 let mut cache = Cache::new(
325 #[cfg(feature = "text")]
326 opt.fontdb.clone(),
327 );
328
329 for node in svg_doc.descendants() {
330 if let Some(tag) = node.tag_name() {
331 if matches!(
332 tag,
333 EId::ClipPath
334 | EId::Filter
335 | EId::LinearGradient
336 | EId::Mask
337 | EId::Pattern
338 | EId::RadialGradient
339 | EId::Image
340 ) {
341 if !node.element_id().is_empty() {
342 cache.all_ids.insert(string_hash(node.element_id()));
343 }
344 }
345 }
346 }
347
348 let root_ts = view_box.to_transform(tree.size());
349 if root_ts.is_identity() {
350 convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
351 } else {
352 let mut g = Group::empty();
353 g.transform = root_ts;
354 g.abs_transform = root_ts;
355 convert_children(svg_doc.root(), &state, &mut cache, &mut g);
356 tree.root.children.push(Node::Group(Box::new(g)));
357 }
358
359 cache.clip_paths.clear();
361 cache.masks.clear();
362 cache.filters.clear();
363 cache.paint.clear();
364
365 super::paint_server::update_paint_servers(
366 &mut tree.root,
367 Transform::default(),
368 None,
369 None,
370 &mut cache,
371 );
372 tree.collect_paint_servers();
373 tree.root.collect_clip_paths(&mut tree.clip_paths);
374 tree.root.collect_masks(&mut tree.masks);
375 tree.root.collect_filters(&mut tree.filters);
376 tree.root.calculate_bounding_boxes();
377
378 #[cfg(feature = "text")]
381 {
382 tree.fontdb = cache.fontdb;
383 }
384
385 if restore_viewbox {
386 calculate_svg_bbox(&mut tree);
387 }
388
389 Ok(tree)
390}
391
392fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
393 let mut state = State {
394 parent_clip_path: None,
395 context_element: None,
396 parent_markers: Vec::new(),
397 fe_image_link: false,
398 view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
399 use_size: (None, None),
400 opt,
401 };
402
403 let def = Length::new(100.0, Unit::Percent);
404 let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
405 let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
406
407 let view_box = svg.parse_viewbox();
408
409 let restore_viewbox =
410 if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
411 if width.unit == Unit::Percent {
413 width = Length::new(
414 (width.number / 100.0) * state.opt.default_size.width() as f64,
415 Unit::None,
416 );
417 }
418
419 if height.unit == Unit::Percent {
420 height = Length::new(
421 (height.number / 100.0) * state.opt.default_size.height() as f64,
422 Unit::None,
423 );
424 }
425
426 true
427 } else {
428 false
429 };
430
431 let size = if let Some(vbox) = view_box {
432 state.view_box = vbox;
433
434 let w = if width.unit == Unit::Percent {
435 vbox.width() * (width.number as f32 / 100.0)
436 } else {
437 svg.convert_user_length(AId::Width, &state, def)
438 };
439
440 let h = if height.unit == Unit::Percent {
441 vbox.height() * (height.number as f32 / 100.0)
442 } else {
443 svg.convert_user_length(AId::Height, &state, def)
444 };
445
446 Size::from_wh(w, h)
447 } else {
448 Size::from_wh(
449 svg.convert_user_length(AId::Width, &state, def),
450 svg.convert_user_length(AId::Height, &state, def),
451 )
452 };
453
454 (size.ok_or(Error::InvalidSize), restore_viewbox)
455}
456
457fn calculate_svg_bbox(tree: &mut Tree) {
461 let bbox = tree.root.abs_bounding_box();
462 if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
463 tree.size = size;
464 }
465}
466
467#[inline(never)]
468pub(crate) fn convert_children(
469 parent_node: SvgNode,
470 state: &State,
471 cache: &mut Cache,
472 parent: &mut Group,
473) {
474 for node in parent_node.children() {
475 convert_element(node, state, cache, parent);
476 }
477}
478
479#[inline(never)]
480pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
481 let tag_name = match node.tag_name() {
482 Some(v) => v,
483 None => return,
484 };
485
486 if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
487 return;
488 }
489
490 if !node.is_visible_element(state.opt) {
491 return;
492 }
493
494 if tag_name == EId::Use {
495 super::use_node::convert(node, state, cache, parent);
496 return;
497 }
498
499 if tag_name == EId::Switch {
500 super::switch::convert(node, state, cache, parent);
501 return;
502 }
503
504 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
505 convert_element_impl(tag_name, node, state, cache, g);
506 }) {
507 parent.children.push(Node::Group(Box::new(g)));
508 }
509}
510
511#[inline(never)]
512fn convert_element_impl(
513 tag_name: EId,
514 node: SvgNode,
515 state: &State,
516 cache: &mut Cache,
517 parent: &mut Group,
518) {
519 match tag_name {
520 EId::Rect
521 | EId::Circle
522 | EId::Ellipse
523 | EId::Line
524 | EId::Polyline
525 | EId::Polygon
526 | EId::Path => {
527 if let Some(path) = super::shapes::convert(node, state) {
528 convert_path(node, path, state, cache, parent);
529 }
530 }
531 EId::Image => {
532 super::image::convert(node, state, cache, parent);
533 }
534 EId::Text => {
535 #[cfg(feature = "text")]
536 {
537 super::text::convert(node, state, cache, parent);
538 }
539 }
540 EId::Svg => {
541 if node.parent_element().is_some() {
542 super::use_node::convert_svg(node, state, cache, parent);
543 } else {
544 convert_children(node, state, cache, parent);
546 }
547 }
548 EId::G => {
549 convert_children(node, state, cache, parent);
550 }
551 _ => {}
552 }
553}
554
555#[inline(never)]
560pub(crate) fn convert_clip_path_elements(
561 clip_node: SvgNode,
562 state: &State,
563 cache: &mut Cache,
564 parent: &mut Group,
565) {
566 for node in clip_node.children() {
567 let tag_name = match node.tag_name() {
568 Some(v) => v,
569 None => continue,
570 };
571
572 if !tag_name.is_graphic() {
573 continue;
574 }
575
576 if !node.is_visible_element(state.opt) {
577 continue;
578 }
579
580 if tag_name == EId::Use {
581 super::use_node::convert(node, state, cache, parent);
582 continue;
583 }
584
585 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
586 convert_clip_path_elements_impl(tag_name, node, state, cache, g);
587 }) {
588 parent.children.push(Node::Group(Box::new(g)));
589 }
590 }
591}
592
593#[inline(never)]
594fn convert_clip_path_elements_impl(
595 tag_name: EId,
596 node: SvgNode,
597 state: &State,
598 cache: &mut Cache,
599 parent: &mut Group,
600) {
601 match tag_name {
602 EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
603 if let Some(path) = super::shapes::convert(node, state) {
604 convert_path(node, path, state, cache, parent);
605 }
606 }
607 EId::Text => {
608 #[cfg(feature = "text")]
609 {
610 super::text::convert(node, state, cache, parent);
611 }
612 }
613 _ => {
614 log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
615 }
616 }
617}
618
619#[derive(Clone, Copy, PartialEq, Debug)]
620enum Isolation {
621 Auto,
622 Isolate,
623}
624
625impl Default for Isolation {
626 fn default() -> Self {
627 Self::Auto
628 }
629}
630
631impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
632 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
633 match value {
634 "auto" => Some(Isolation::Auto),
635 "isolate" => Some(Isolation::Isolate),
636 _ => None,
637 }
638 }
639}
640
641pub(crate) fn convert_group(
643 node: SvgNode,
644 state: &State,
645 force: bool,
646 cache: &mut Cache,
647 parent: &mut Group,
648 collect_children: &dyn Fn(&mut Cache, &mut Group),
649) -> Option<Group> {
650 let opacity = if state.parent_clip_path.is_none() {
652 node.attribute::<Opacity>(AId::Opacity)
653 .unwrap_or(Opacity::ONE)
654 } else {
655 Opacity::ONE
656 };
657
658 let transform = node.resolve_transform(AId::Transform, state);
659 let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
660 let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
661 let isolate = isolation == Isolation::Isolate;
662
663 let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
665 let id = if is_g_or_use && state.parent_markers.is_empty() {
666 node.element_id().to_string()
667 } else {
668 String::new()
669 };
670
671 let abs_transform = parent.abs_transform.pre_concat(transform);
672 let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
673 let mut g = Group {
674 id,
675 transform,
676 abs_transform,
677 opacity,
678 blend_mode,
679 isolate,
680 clip_path: None,
681 mask: None,
682 filters: Vec::new(),
683 is_context_element: false,
684 bounding_box: dummy,
685 abs_bounding_box: dummy,
686 stroke_bounding_box: dummy,
687 abs_stroke_bounding_box: dummy,
688 layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
689 abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
690 children: Vec::new(),
691 };
692 collect_children(cache, &mut g);
693
694 let object_bbox = g.calculate_object_bbox();
697
698 let mut clip_path = None;
702 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
703 clip_path = super::clippath::convert(link, state, object_bbox, cache);
704 if clip_path.is_none() {
705 return None;
706 }
707 }
708
709 let mut mask = None;
710 if state.parent_clip_path.is_none() {
711 if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
712 mask = super::mask::convert(link, state, object_bbox, cache);
713 if mask.is_none() {
714 return None;
715 }
716 }
717 }
718
719 let filters = {
720 let mut filters = Vec::new();
721 if state.parent_clip_path.is_none() {
722 if node.attribute(AId::Filter) == Some("none") {
723 } else if node.has_attribute(AId::Filter) {
725 if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
726 filters = f;
727 } else {
728 return None;
741 }
742 }
743 }
744
745 filters
746 };
747
748 let required = opacity.get().approx_ne_ulps(&1.0, 4)
749 || clip_path.is_some()
750 || mask.is_some()
751 || !filters.is_empty()
752 || !transform.is_identity()
753 || blend_mode != BlendMode::Normal
754 || isolate
755 || is_g_or_use
756 || force;
757
758 if !required {
759 parent.children.append(&mut g.children);
760 return None;
761 }
762
763 g.clip_path = clip_path;
764 g.mask = mask;
765 g.filters = filters;
766
767 g.calculate_bounding_boxes();
769
770 Some(g)
771}
772
773fn convert_path(
774 node: SvgNode,
775 tiny_skia_path: Arc<tiny_skia_path::Path>,
776 state: &State,
777 cache: &mut Cache,
778 parent: &mut Group,
779) {
780 debug_assert!(tiny_skia_path.len() >= 2);
781 if tiny_skia_path.len() < 2 {
782 return;
783 }
784
785 let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
786 let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
787 let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
788 let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
789 let mut visible = visibility == Visibility::Visible;
790 let rendering_mode: ShapeRendering = node
791 .find_attribute(AId::ShapeRendering)
792 .unwrap_or(state.opt.shape_rendering);
793
794 let raw_paint_order: svgtypes::PaintOrder =
796 node.find_attribute(AId::PaintOrder).unwrap_or_default();
797 let paint_order = svg_paint_order_to_usvg(raw_paint_order);
798 let path_transform = parent.abs_transform;
799
800 if fill.is_none() && stroke.is_none() {
803 visible = false;
804 }
805
806 if let Some(fill) = fill.as_mut() {
807 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
808 fill.context_element
809 {
810 process_paint(
811 &mut fill.paint,
812 true,
813 context_transform,
814 context_bbox.map(|r| r.to_rect()),
815 path_transform,
816 tiny_skia_path.bounds(),
817 cache,
818 );
819 fill.context_element = None;
820 }
821 }
822
823 if let Some(stroke) = stroke.as_mut() {
824 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
825 stroke.context_element
826 {
827 process_paint(
828 &mut stroke.paint,
829 true,
830 context_transform,
831 context_bbox.map(|r| r.to_rect()),
832 path_transform,
833 tiny_skia_path.bounds(),
834 cache,
835 );
836 stroke.context_element = None;
837 }
838 }
839
840 let mut marker = None;
841 if marker::is_valid(node) && visibility == Visibility::Visible {
842 let mut marker_group = Group {
843 abs_transform: parent.abs_transform,
844 ..Group::empty()
845 };
846
847 let mut marker_state = state.clone();
848
849 let bbox = tiny_skia_path
850 .compute_tight_bounds()
851 .and_then(|r| r.to_non_zero_rect());
852
853 let fill = fill.clone().map(|mut f| {
854 f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
855 f
856 });
857
858 let stroke = stroke.clone().map(|mut s| {
859 s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
860 s
861 });
862
863 marker_state.context_element = Some((fill, stroke));
864
865 marker::convert(
866 node,
867 &tiny_skia_path,
868 &marker_state,
869 cache,
870 &mut marker_group,
871 );
872 marker_group.calculate_bounding_boxes();
873 marker = Some(marker_group);
874 }
875
876 let id = if state.parent_markers.is_empty() {
878 node.element_id().to_string()
879 } else {
880 String::new()
881 };
882
883 let path = Path::new(
884 id,
885 visible,
886 fill,
887 stroke,
888 paint_order,
889 rendering_mode,
890 tiny_skia_path,
891 path_transform,
892 );
893
894 let path = match path {
895 Some(v) => v,
896 None => return,
897 };
898
899 match raw_paint_order.order {
900 [PaintOrderKind::Markers, _, _] => {
901 if let Some(markers_node) = marker {
902 parent.children.push(Node::Group(Box::new(markers_node)));
903 }
904
905 parent.children.push(Node::Path(Box::new(path.clone())));
906 }
907 [first, PaintOrderKind::Markers, last] => {
908 append_single_paint_path(first, &path, parent);
909
910 if let Some(markers_node) = marker {
911 parent.children.push(Node::Group(Box::new(markers_node)));
912 }
913
914 append_single_paint_path(last, &path, parent);
915 }
916 [_, _, PaintOrderKind::Markers] => {
917 parent.children.push(Node::Path(Box::new(path.clone())));
918
919 if let Some(markers_node) = marker {
920 parent.children.push(Node::Group(Box::new(markers_node)));
921 }
922 }
923 _ => parent.children.push(Node::Path(Box::new(path.clone()))),
924 }
925}
926
927fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
928 match paint_order_kind {
929 PaintOrderKind::Fill => {
930 if path.fill.is_some() {
931 let mut fill_path = path.clone();
932 fill_path.stroke = None;
933 fill_path.id = String::new();
934 parent.children.push(Node::Path(Box::new(fill_path)));
935 }
936 }
937 PaintOrderKind::Stroke => {
938 if path.stroke.is_some() {
939 let mut stroke_path = path.clone();
940 stroke_path.fill = None;
941 stroke_path.id = String::new();
942 parent.children.push(Node::Path(Box::new(stroke_path)));
943 }
944 }
945 _ => {}
946 }
947}
948
949pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
950 match (order.order[0], order.order[1]) {
951 (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
952 (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
953 PaintOrder::StrokeAndFill
954 }
955 _ => PaintOrder::FillAndStroke,
956 }
957}
958
959impl SvgNode<'_, '_> {
960 pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
961 let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
962 let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);
963
964 if let Some(transform_origin) = transform_origin {
965 let dx = convert_length(
966 transform_origin.x_offset,
967 *self,
968 AId::Width,
969 Units::UserSpaceOnUse,
970 state,
971 );
972 let dy = convert_length(
973 transform_origin.y_offset,
974 *self,
975 AId::Height,
976 Units::UserSpaceOnUse,
977 state,
978 );
979 transform = Transform::default()
980 .pre_translate(dx, dy)
981 .pre_concat(transform)
982 .pre_translate(-dx, -dy);
983 }
984
985 transform
986 }
987}