Skip to main content

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