usvg/parser/
converter.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::collections::{HashMap, HashSet};
5use std::hash::{Hash, Hasher};
6use std::str::FromStr;
7use std::sync::Arc;
8
9#[cfg(feature = "text")]
10use fontdb::Database;
11use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin};
12use tiny_skia_path::PathBuilder;
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    /// 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)]
38pub 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
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    // 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?
162fn 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        // 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
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
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`.
286pub(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
408fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
409    let path = PathBuilder::from_rect(area);
410
411    let 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::new_simple(Arc::new(path))?;
422    path.fill = Some(fill);
423
424    Some(path)
425}
426
427fn 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.
495fn calculate_svg_bbox(tree: &mut Tree) {
496    let bbox = tree.root.abs_bounding_box();
497    if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
498        tree.size = size;
499    }
500}
501
502#[inline(never)]
503pub(crate) fn convert_children(
504    parent_node: SvgNode,
505    state: &State,
506    cache: &mut Cache,
507    parent: &mut Group,
508) {
509    for node in parent_node.children() {
510        convert_element(node, state, cache, parent);
511    }
512}
513
514#[inline(never)]
515pub(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)]
547fn 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)]
595pub(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)]
629fn 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) = 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)]
655enum Isolation {
656    Auto,
657    Isolate,
658}
659
660impl Default for Isolation {
661    fn default() -> Self {
662        Self::Auto
663    }
664}
665
666impl<'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
677pub(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
808fn 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
952fn 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.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.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
974pub 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
984impl 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}