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