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 fontdb::Database;
11#[cfg(feature = "text")]
12use fontdb::ID;
13#[cfg(feature = "text")]
14use rustybuzz::ttf_parser::GlyphId;
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), Option<tiny_skia_path::Path>>,
54    #[cfg(feature = "text")]
55    cache_colr: HashMap<(ID, GlyphId), 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_outline, cache_outline, outline, tiny_skia_path::Path);
208    font_lookup!(fontdb_colr, cache_colr, colr, Tree);
209    font_lookup!(fontdb_svg, cache_svg, svg, Node);
210    font_lookup!(fontdb_raster, cache_raster, raster, BitmapImage);
211
212    #[cfg(feature = "text")]
213    pub(crate) fn has_opsz_axis(&mut self, font: ID) -> bool {
214        if let Some(&cached) = self.cache_has_opsz.get(&font) {
215            return cached;
216        }
217        let has_opsz = self.fontdb.has_opsz_axis(font);
218        self.cache_has_opsz.insert(font, has_opsz);
219        has_opsz
220    }
221}
222
223// TODO: is there a simpler way?
224fn string_hash(s: &str) -> u64 {
225    let mut h = std::collections::hash_map::DefaultHasher::new();
226    s.hash(&mut h);
227    h.finish()
228}
229
230impl<'a, 'input: 'a> SvgNode<'a, 'input> {
231    pub(crate) fn convert_length(
232        &self,
233        aid: AId,
234        object_units: Units,
235        state: &State,
236        def: Length,
237    ) -> f32 {
238        units::convert_length(
239            self.attribute(aid).unwrap_or(def),
240            *self,
241            aid,
242            object_units,
243            state,
244        )
245    }
246
247    pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 {
248        self.convert_length(aid, Units::UserSpaceOnUse, state, def)
249    }
250
251    pub fn parse_viewbox(&self) -> Option<NonZeroRect> {
252        let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?;
253        NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32)
254    }
255
256    pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 {
257        debug_assert!(
258            !matches!(aid, AId::BaselineShift | AId::FontSize),
259            "{} cannot be resolved via this function",
260            aid
261        );
262
263        if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) {
264            if let Some(length) = n.attribute(aid) {
265                return units::convert_user_length(length, n, aid, state);
266            }
267        }
268
269        def
270    }
271
272    pub fn resolve_valid_length(
273        &self,
274        aid: AId,
275        state: &State,
276        def: f32,
277    ) -> Option<NonZeroPositiveF32> {
278        let n = self.resolve_length(aid, state, def);
279        NonZeroPositiveF32::new(n)
280    }
281
282    pub(crate) fn try_convert_length(
283        &self,
284        aid: AId,
285        object_units: Units,
286        state: &State,
287    ) -> Option<f32> {
288        Some(units::convert_length(
289            self.attribute(aid)?,
290            *self,
291            aid,
292            object_units,
293            state,
294        ))
295    }
296
297    pub fn has_valid_transform(&self, aid: AId) -> bool {
298        // Do not use Node::attribute::<Transform>, because it will always
299        // return a valid transform.
300
301        let attr = match self.attribute(aid) {
302            Some(attr) => attr,
303            None => return true,
304        };
305
306        let ts = match svgtypes::Transform::from_str(attr) {
307            Ok(v) => v,
308            Err(_) => return true,
309        };
310
311        let ts = Transform::from_row(
312            ts.a as f32,
313            ts.b as f32,
314            ts.c as f32,
315            ts.d as f32,
316            ts.e as f32,
317            ts.f as f32,
318        );
319        ts.is_valid()
320    }
321
322    pub fn is_visible_element(&self, opt: &crate::Options) -> bool {
323        self.attribute(AId::Display) != Some("none")
324            && self.has_valid_transform(AId::Transform)
325            && super::switch::is_condition_passed(*self, opt)
326    }
327}
328
329pub trait SvgColorExt {
330    fn split_alpha(self) -> (Color, Opacity);
331}
332
333impl SvgColorExt for svgtypes::Color {
334    fn split_alpha(self) -> (Color, Opacity) {
335        (
336            Color::new_rgb(self.red, self.green, self.blue),
337            Opacity::new_u8(self.alpha),
338        )
339    }
340}
341
342/// Converts an input `Document` into a `Tree`.
343///
344/// # Errors
345///
346/// - If `Document` doesn't have an SVG node - returns an empty tree.
347/// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`.
348pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> {
349    let svg = svg_doc.root_element();
350    let (size, restore_viewbox) = resolve_svg_size(&svg, opt);
351    let size = size?;
352    let view_box = ViewBox {
353        rect: svg
354            .parse_viewbox()
355            .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)),
356        aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
357    };
358
359    let background_color = svg
360        .attribute::<&str>(AId::BackgroundColor)
361        .and_then(|s| svgtypes::Paint::from_str(s).ok())
362        .and_then(|paint| match paint {
363            svgtypes::Paint::Color(c) => Some(c),
364            _ => None,
365        });
366
367    let mut tree = Tree {
368        size,
369        root: Group::empty(),
370        linear_gradients: Vec::new(),
371        radial_gradients: Vec::new(),
372        patterns: Vec::new(),
373        clip_paths: Vec::new(),
374        masks: Vec::new(),
375        filters: Vec::new(),
376        #[cfg(feature = "text")]
377        fontdb: opt.fontdb.clone(),
378    };
379
380    if !svg.is_visible_element(opt) {
381        return Ok(tree);
382    }
383
384    let state = State {
385        parent_clip_path: None,
386        context_element: None,
387        parent_markers: Vec::new(),
388        fe_image_link: false,
389        view_box: view_box.rect,
390        use_size: (None, None),
391        opt,
392    };
393
394    let mut cache = Cache::new(
395        #[cfg(feature = "text")]
396        opt.fontdb.clone(),
397    );
398
399    for node in svg_doc.descendants() {
400        if let Some(tag) = node.tag_name() {
401            if matches!(
402                tag,
403                EId::ClipPath
404                    | EId::Filter
405                    | EId::LinearGradient
406                    | EId::Mask
407                    | EId::Pattern
408                    | EId::RadialGradient
409                    | EId::Image
410            ) {
411                if !node.element_id().is_empty() {
412                    cache.all_ids.insert(string_hash(node.element_id()));
413                }
414            }
415        }
416    }
417
418    let root_ts = view_box.to_transform(tree.size());
419    if root_ts.is_identity() && background_color.is_none() {
420        convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
421    } else {
422        let mut g = Group::empty();
423
424        if let Some(background_color) = background_color {
425            if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {
426                g.children.push(Node::Path(Box::new(path)));
427            }
428        }
429
430        g.transform = root_ts;
431        g.abs_transform = root_ts;
432        convert_children(svg_doc.root(), &state, &mut cache, &mut g);
433        g.calculate_bounding_boxes();
434        tree.root.children.push(Node::Group(Box::new(g)));
435    }
436
437    // Clear cache to make sure that all `Arc<T>` objects have a single strong reference.
438    cache.clip_paths.clear();
439    cache.masks.clear();
440    cache.filters.clear();
441    cache.paint.clear();
442
443    super::paint_server::update_paint_servers(
444        &mut tree.root,
445        Transform::default(),
446        None,
447        None,
448        &mut cache,
449    );
450    tree.collect_paint_servers();
451    tree.root.collect_clip_paths(&mut tree.clip_paths);
452    tree.root.collect_masks(&mut tree.masks);
453    tree.root.collect_filters(&mut tree.filters);
454    tree.root.calculate_bounding_boxes();
455
456    // The fontdb might have been mutated and we want to apply these changes to
457    // the tree's fontdb.
458    #[cfg(feature = "text")]
459    {
460        tree.fontdb = cache.fontdb;
461    }
462
463    if restore_viewbox {
464        calculate_svg_bbox(&mut tree);
465    }
466
467    Ok(tree)
468}
469
470fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
471    let path = PathBuilder::from_rect(area);
472
473    let fill = Fill {
474        paint: Paint::Color(Color::new_rgb(
475            background_color.red,
476            background_color.green,
477            background_color.blue,
478        )),
479        opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,
480        ..Default::default()
481    };
482
483    let mut path = Path::new_simple(Arc::new(path))?;
484    path.fill = Some(fill);
485
486    Some(path)
487}
488
489fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
490    let mut state = State {
491        parent_clip_path: None,
492        context_element: None,
493        parent_markers: Vec::new(),
494        fe_image_link: false,
495        view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
496        use_size: (None, None),
497        opt,
498    };
499
500    let def = Length::new(100.0, Unit::Percent);
501    let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
502    let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
503
504    let view_box = svg.parse_viewbox();
505
506    let restore_viewbox =
507        if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
508            // Apply the percentages to the fallback size.
509            if width.unit == Unit::Percent {
510                width = Length::new(
511                    (width.number / 100.0) * state.opt.default_size.width() as f64,
512                    Unit::None,
513                );
514            }
515
516            if height.unit == Unit::Percent {
517                height = Length::new(
518                    (height.number / 100.0) * state.opt.default_size.height() as f64,
519                    Unit::None,
520                );
521            }
522
523            true
524        } else {
525            false
526        };
527
528    let size = if let Some(vbox) = view_box {
529        state.view_box = vbox;
530
531        let w = if width.unit == Unit::Percent {
532            vbox.width() * (width.number as f32 / 100.0)
533        } else {
534            svg.convert_user_length(AId::Width, &state, def)
535        };
536
537        let h = if height.unit == Unit::Percent {
538            vbox.height() * (height.number as f32 / 100.0)
539        } else {
540            svg.convert_user_length(AId::Height, &state, def)
541        };
542
543        Size::from_wh(w, h)
544    } else {
545        Size::from_wh(
546            svg.convert_user_length(AId::Width, &state, def),
547            svg.convert_user_length(AId::Height, &state, def),
548        )
549    };
550
551    (size.ok_or(Error::InvalidSize), restore_viewbox)
552}
553
554/// Calculates SVG's size and viewBox in case there were not set.
555///
556/// Simply iterates over all nodes and calculates a bounding box.
557fn calculate_svg_bbox(tree: &mut Tree) {
558    let bbox = tree.root.abs_bounding_box();
559    if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
560        tree.size = size;
561    }
562}
563
564#[inline(never)]
565pub(crate) fn convert_children(
566    parent_node: SvgNode,
567    state: &State,
568    cache: &mut Cache,
569    parent: &mut Group,
570) {
571    for node in parent_node.children() {
572        convert_element(node, state, cache, parent);
573    }
574}
575
576#[inline(never)]
577pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
578    let tag_name = match node.tag_name() {
579        Some(v) => v,
580        None => return,
581    };
582
583    if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
584        return;
585    }
586
587    if !node.is_visible_element(state.opt) {
588        return;
589    }
590
591    if tag_name == EId::Use {
592        super::use_node::convert(node, state, cache, parent);
593        return;
594    }
595
596    if tag_name == EId::Switch {
597        super::switch::convert(node, state, cache, parent);
598        return;
599    }
600
601    if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
602        convert_element_impl(tag_name, node, state, cache, g);
603    }) {
604        parent.children.push(Node::Group(Box::new(g)));
605    }
606}
607
608#[inline(never)]
609fn convert_element_impl(
610    tag_name: EId,
611    node: SvgNode,
612    state: &State,
613    cache: &mut Cache,
614    parent: &mut Group,
615) {
616    match tag_name {
617        EId::Rect
618        | EId::Circle
619        | EId::Ellipse
620        | EId::Line
621        | EId::Polyline
622        | EId::Polygon
623        | EId::Path => {
624            if let Some(path) = super::shapes::convert(node, state) {
625                convert_path(node, path, state, cache, parent);
626            }
627        }
628        EId::Image => {
629            super::image::convert(node, state, cache, parent);
630        }
631        EId::Text => {
632            #[cfg(feature = "text")]
633            {
634                super::text::convert(node, state, cache, parent);
635            }
636        }
637        EId::Svg => {
638            if node.parent_element().is_some() {
639                super::use_node::convert_svg(node, state, cache, parent);
640            } else {
641                // Skip root `svg`.
642                convert_children(node, state, cache, parent);
643            }
644        }
645        EId::G => {
646            convert_children(node, state, cache, parent);
647        }
648        _ => {}
649    }
650}
651
652// `clipPath` can have only shape and `text` children.
653//
654// `line` doesn't impact rendering because stroke is always disabled
655// for `clipPath` children.
656#[inline(never)]
657pub(crate) fn convert_clip_path_elements(
658    clip_node: SvgNode,
659    state: &State,
660    cache: &mut Cache,
661    parent: &mut Group,
662) {
663    for node in clip_node.children() {
664        let tag_name = match node.tag_name() {
665            Some(v) => v,
666            None => continue,
667        };
668
669        if !tag_name.is_graphic() {
670            continue;
671        }
672
673        if !node.is_visible_element(state.opt) {
674            continue;
675        }
676
677        if tag_name == EId::Use {
678            super::use_node::convert(node, state, cache, parent);
679            continue;
680        }
681
682        if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
683            convert_clip_path_elements_impl(tag_name, node, state, cache, g);
684        }) {
685            parent.children.push(Node::Group(Box::new(g)));
686        }
687    }
688}
689
690#[inline(never)]
691fn convert_clip_path_elements_impl(
692    tag_name: EId,
693    node: SvgNode,
694    state: &State,
695    cache: &mut Cache,
696    parent: &mut Group,
697) {
698    match tag_name {
699        EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
700            if let Some(path) = super::shapes::convert(node, state) {
701                convert_path(node, path, state, cache, parent);
702            }
703        }
704        EId::Text => {
705            #[cfg(feature = "text")]
706            {
707                super::text::convert(node, state, cache, parent);
708            }
709        }
710        _ => {
711            log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
712        }
713    }
714}
715
716#[derive(Clone, Copy, PartialEq, Debug)]
717enum Isolation {
718    Auto,
719    Isolate,
720}
721
722impl Default for Isolation {
723    fn default() -> Self {
724        Self::Auto
725    }
726}
727
728impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
729    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
730        match value {
731            "auto" => Some(Isolation::Auto),
732            "isolate" => Some(Isolation::Isolate),
733            _ => None,
734        }
735    }
736}
737
738// TODO: explain
739pub(crate) fn convert_group(
740    node: SvgNode,
741    state: &State,
742    force: bool,
743    cache: &mut Cache,
744    parent: &mut Group,
745    collect_children: &dyn Fn(&mut Cache, &mut Group),
746) -> Option<Group> {
747    // A `clipPath` child cannot have an opacity.
748    let opacity = if state.parent_clip_path.is_none() {
749        node.attribute::<Opacity>(AId::Opacity)
750            .unwrap_or(Opacity::ONE)
751    } else {
752        Opacity::ONE
753    };
754
755    let transform = node.resolve_transform(AId::Transform, state);
756    let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
757    let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
758    let isolate = isolation == Isolation::Isolate;
759
760    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
761    let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
762    let id = if is_g_or_use && state.parent_markers.is_empty() {
763        node.element_id().to_string()
764    } else {
765        String::new()
766    };
767
768    let abs_transform = parent.abs_transform.pre_concat(transform);
769    let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
770    let mut g = Group {
771        id,
772        transform,
773        abs_transform,
774        opacity,
775        blend_mode,
776        isolate,
777        clip_path: None,
778        mask: None,
779        filters: Vec::new(),
780        is_context_element: false,
781        bounding_box: dummy,
782        abs_bounding_box: dummy,
783        stroke_bounding_box: dummy,
784        abs_stroke_bounding_box: dummy,
785        layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
786        abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
787        children: Vec::new(),
788    };
789    collect_children(cache, &mut g);
790
791    // We need to know group's bounding box before converting
792    // clipPaths, masks and filters.
793    let object_bbox = g.calculate_object_bbox();
794
795    // `mask` and `filter` cannot be set on `clipPath` children.
796    // But `clip-path` can.
797
798    let mut clip_path = None;
799    if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
800        clip_path = super::clippath::convert(link, state, object_bbox, cache);
801        if clip_path.is_none() {
802            return None;
803        }
804    }
805
806    let mut mask = None;
807    if state.parent_clip_path.is_none() {
808        if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
809            mask = super::mask::convert(link, state, object_bbox, cache);
810            if mask.is_none() {
811                return None;
812            }
813        }
814    }
815
816    let filters = {
817        let mut filters = Vec::new();
818        if state.parent_clip_path.is_none() {
819            if node.attribute(AId::Filter) == Some("none") {
820                // Do nothing.
821            } else if node.has_attribute(AId::Filter) {
822                if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
823                    filters = f;
824                } else {
825                    // A filter that not a link or a filter with a link to a non existing element.
826                    //
827                    // Unlike `clip-path` and `mask`, when a `filter` link is invalid
828                    // then the whole element should be ignored.
829                    //
830                    // This is kinda an undefined behaviour.
831                    // In most cases, Chrome, Firefox and rsvg will ignore such elements,
832                    // but in some cases Chrome allows it. Not sure why.
833                    // Inkscape (0.92) simply ignores such attributes, rendering element as is.
834                    // Batik (1.12) crashes.
835                    //
836                    // Test file: e-filter-051.svg
837                    return None;
838                }
839            }
840        }
841
842        filters
843    };
844
845    let required = opacity.get().approx_ne_ulps(&1.0, 4)
846        || clip_path.is_some()
847        || mask.is_some()
848        || !filters.is_empty()
849        || !transform.is_identity()
850        || blend_mode != BlendMode::Normal
851        || isolate
852        || is_g_or_use
853        || force;
854
855    if !required {
856        parent.children.append(&mut g.children);
857        return None;
858    }
859
860    g.clip_path = clip_path;
861    g.mask = mask;
862    g.filters = filters;
863
864    // Must be called after we set Group::filters
865    g.calculate_bounding_boxes();
866
867    Some(g)
868}
869
870fn convert_path(
871    node: SvgNode,
872    tiny_skia_path: Arc<tiny_skia_path::Path>,
873    state: &State,
874    cache: &mut Cache,
875    parent: &mut Group,
876) {
877    debug_assert!(tiny_skia_path.len() >= 2);
878    if tiny_skia_path.len() < 2 {
879        return;
880    }
881
882    let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
883    let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
884    let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
885    let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
886    let mut visible = visibility == Visibility::Visible;
887    let rendering_mode: ShapeRendering = node
888        .find_attribute(AId::ShapeRendering)
889        .unwrap_or(state.opt.shape_rendering);
890
891    // TODO: handle `markers` before `stroke`
892    let raw_paint_order: svgtypes::PaintOrder =
893        node.find_attribute(AId::PaintOrder).unwrap_or_default();
894    let paint_order = svg_paint_order_to_usvg(raw_paint_order);
895    let path_transform = parent.abs_transform;
896
897    // If a path doesn't have a fill or a stroke then it's invisible.
898    // By setting `visibility` to `hidden` we are disabling rendering of this path.
899    if fill.is_none() && stroke.is_none() {
900        visible = false;
901    }
902
903    if let Some(fill) = fill.as_mut() {
904        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
905            fill.context_element
906        {
907            process_paint(
908                &mut fill.paint,
909                true,
910                context_transform,
911                context_bbox.map(|r| r.to_rect()),
912                path_transform,
913                tiny_skia_path.bounds(),
914                cache,
915            );
916            fill.context_element = None;
917        }
918    }
919
920    if let Some(stroke) = stroke.as_mut() {
921        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
922            stroke.context_element
923        {
924            process_paint(
925                &mut stroke.paint,
926                true,
927                context_transform,
928                context_bbox.map(|r| r.to_rect()),
929                path_transform,
930                tiny_skia_path.bounds(),
931                cache,
932            );
933            stroke.context_element = None;
934        }
935    }
936
937    let mut marker = None;
938    if marker::is_valid(node) && visibility == Visibility::Visible {
939        let mut marker_group = Group {
940            abs_transform: parent.abs_transform,
941            ..Group::empty()
942        };
943
944        let mut marker_state = state.clone();
945
946        let bbox = tiny_skia_path
947            .compute_tight_bounds()
948            .and_then(|r| r.to_non_zero_rect());
949
950        let fill = fill.clone().map(|mut f| {
951            f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
952            f
953        });
954
955        let stroke = stroke.clone().map(|mut s| {
956            s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
957            s
958        });
959
960        marker_state.context_element = Some((fill, stroke));
961
962        marker::convert(
963            node,
964            &tiny_skia_path,
965            &marker_state,
966            cache,
967            &mut marker_group,
968        );
969        marker_group.calculate_bounding_boxes();
970        marker = Some(marker_group);
971    }
972
973    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
974    let id = if state.parent_markers.is_empty() {
975        node.element_id().to_string()
976    } else {
977        String::new()
978    };
979
980    let path = Path::new(
981        id,
982        visible,
983        fill,
984        stroke,
985        paint_order,
986        rendering_mode,
987        tiny_skia_path,
988        path_transform,
989    );
990
991    let path = match path {
992        Some(v) => v,
993        None => return,
994    };
995
996    match (raw_paint_order.order, marker) {
997        ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {
998            parent.children.push(Node::Group(Box::new(markers_node)));
999            parent.children.push(Node::Path(Box::new(path.clone())));
1000        }
1001        ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {
1002            append_single_paint_path(first, &path, parent);
1003            parent.children.push(Node::Group(Box::new(markers_node)));
1004            append_single_paint_path(last, &path, parent);
1005        }
1006        ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {
1007            parent.children.push(Node::Path(Box::new(path.clone())));
1008            parent.children.push(Node::Group(Box::new(markers_node)));
1009        }
1010        _ => parent.children.push(Node::Path(Box::new(path.clone()))),
1011    }
1012}
1013
1014fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
1015    match paint_order_kind {
1016        PaintOrderKind::Fill => {
1017            if path.fill.is_some() {
1018                let mut fill_path = path.clone();
1019                fill_path.stroke = None;
1020                fill_path.id = String::new();
1021                parent.children.push(Node::Path(Box::new(fill_path)));
1022            }
1023        }
1024        PaintOrderKind::Stroke => {
1025            if path.stroke.is_some() {
1026                let mut stroke_path = path.clone();
1027                stroke_path.fill = None;
1028                stroke_path.id = String::new();
1029                parent.children.push(Node::Path(Box::new(stroke_path)));
1030            }
1031        }
1032        _ => {}
1033    }
1034}
1035
1036pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
1037    match (order.order[0], order.order[1]) {
1038        (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
1039        (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
1040            PaintOrder::StrokeAndFill
1041        }
1042        _ => PaintOrder::FillAndStroke,
1043    }
1044}
1045
1046impl SvgNode<'_, '_> {
1047    pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
1048        let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
1049        let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);
1050
1051        if let Some(transform_origin) = transform_origin {
1052            let dx = convert_length(
1053                transform_origin.x_offset,
1054                *self,
1055                AId::Width,
1056                Units::UserSpaceOnUse,
1057                state,
1058            );
1059            let dy = convert_length(
1060                transform_origin.y_offset,
1061                *self,
1062                AId::Height,
1063                Units::UserSpaceOnUse,
1064                state,
1065            );
1066            transform = Transform::default()
1067                .pre_translate(dx, dy)
1068                .pre_concat(transform)
1069                .pre_translate(-dx, -dy);
1070        }
1071
1072        transform
1073    }
1074}