usvg/parser/
converter.rs

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
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    /// 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 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    // Clear cache to make sure that all `Arc<T>` objects have a single strong reference.
360    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    // The fontdb might have been mutated and we want to apply these changes to
379    // the tree's fontdb.
380    #[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            // Apply the percentages to the fallback size.
412            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
457/// Calculates SVG's size and viewBox in case there were not set.
458///
459/// Simply iterates over all nodes and calculates a bounding box.
460fn 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                // Skip root `svg`.
545                convert_children(node, state, cache, parent);
546            }
547        }
548        EId::G => {
549            convert_children(node, state, cache, parent);
550        }
551        _ => {}
552    }
553}
554
555// `clipPath` can have only shape and `text` children.
556//
557// `line` doesn't impact rendering because stroke is always disabled
558// for `clipPath` children.
559#[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
641// TODO: explain
642pub(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    // A `clipPath` child cannot have an opacity.
651    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    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
664    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    // We need to know group's bounding box before converting
695    // clipPaths, masks and filters.
696    let object_bbox = g.calculate_object_bbox();
697
698    // `mask` and `filter` cannot be set on `clipPath` children.
699    // But `clip-path` can.
700
701    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                // Do nothing.
724            } 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                    // A filter that not a link or a filter with a link to a non existing element.
729                    //
730                    // Unlike `clip-path` and `mask`, when a `filter` link is invalid
731                    // then the whole element should be ignored.
732                    //
733                    // This is kinda an undefined behaviour.
734                    // In most cases, Chrome, Firefox and rsvg will ignore such elements,
735                    // but in some cases Chrome allows it. Not sure why.
736                    // Inkscape (0.92) simply ignores such attributes, rendering element as is.
737                    // Batik (1.12) crashes.
738                    //
739                    // Test file: e-filter-051.svg
740                    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    // Must be called after we set Group::filters
768    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    // TODO: handle `markers` before `stroke`
795    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 a path doesn't have a fill or a stroke then it's invisible.
801    // By setting `visibility` to `hidden` we are disabling rendering of this path.
802    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    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
877    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}