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    let root_ts = view_box.to_transform(tree.size());
453    if root_ts.is_identity() && background_color.is_none() {
454        convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
455    } else {
456        let mut g = Group::empty();
457
458        if let Some(background_color) = background_color {
459            if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {
460                g.children.push(Node::Path(Box::new(path)));
461            }
462        }
463
464        g.transform = root_ts;
465        g.abs_transform = root_ts;
466        convert_children(svg_doc.root(), &state, &mut cache, &mut g);
467        g.calculate_bounding_boxes();
468        tree.root.children.push(Node::Group(Box::new(g)));
469    }
470
471    // Clear cache to make sure that all `Arc<T>` objects have a single strong reference.
472    cache.clip_paths.clear();
473    cache.masks.clear();
474    cache.filters.clear();
475    cache.paint.clear();
476
477    super::paint_server::update_paint_servers(
478        &mut tree.root,
479        Transform::default(),
480        None,
481        None,
482        &mut cache,
483    );
484    tree.collect_paint_servers();
485    tree.root.collect_clip_paths(&mut tree.clip_paths);
486    tree.root.collect_masks(&mut tree.masks);
487    tree.root.collect_filters(&mut tree.filters);
488    tree.root.calculate_bounding_boxes();
489
490    // The fontdb might have been mutated and we want to apply these changes to
491    // the tree's fontdb.
492    #[cfg(feature = "text")]
493    {
494        tree.fontdb = cache.fontdb;
495    }
496
497    if restore_viewbox {
498        calculate_svg_bbox(&mut tree);
499    }
500
501    Ok(tree)
502}
503
504fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
505    let path = PathBuilder::from_rect(area);
506
507    let fill = Fill {
508        paint: Paint::Color(Color::new_rgb(
509            background_color.red,
510            background_color.green,
511            background_color.blue,
512        )),
513        opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,
514        ..Default::default()
515    };
516
517    let mut path = Path::new_simple(Arc::new(path))?;
518    path.fill = Some(fill);
519
520    Some(path)
521}
522
523fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
524    let mut state = State {
525        parent_clip_path: None,
526        context_element: None,
527        parent_markers: Vec::new(),
528        fe_image_link: false,
529        view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
530        use_size: (None, None),
531        opt,
532    };
533
534    let def = Length::new(100.0, Unit::Percent);
535    let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
536    let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
537
538    let view_box = svg.parse_viewbox();
539
540    let restore_viewbox =
541        if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
542            // Apply the percentages to the fallback size.
543            if width.unit == Unit::Percent {
544                width = Length::new(
545                    (width.number / 100.0) * state.opt.default_size.width() as f64,
546                    Unit::None,
547                );
548            }
549
550            if height.unit == Unit::Percent {
551                height = Length::new(
552                    (height.number / 100.0) * state.opt.default_size.height() as f64,
553                    Unit::None,
554                );
555            }
556
557            true
558        } else {
559            false
560        };
561
562    let size = if let Some(vbox) = view_box {
563        state.view_box = vbox;
564
565        let w = if width.unit == Unit::Percent {
566            vbox.width() * (width.number as f32 / 100.0)
567        } else {
568            svg.convert_user_length(AId::Width, &state, def)
569        };
570
571        let h = if height.unit == Unit::Percent {
572            vbox.height() * (height.number as f32 / 100.0)
573        } else {
574            svg.convert_user_length(AId::Height, &state, def)
575        };
576
577        // If exactly one of width and height is specified, compute the missing
578        // dimension from the specified one and the viewBox aspect ratio.
579        match (
580            svg.attribute::<Length>(AId::Width),
581            svg.attribute::<Length>(AId::Height),
582        ) {
583            (Some(_), None) => Size::from_wh(w, vbox.height() * w / vbox.width()),
584            (None, Some(_)) => Size::from_wh(vbox.width() * h / vbox.height(), h),
585            (_, _) => Size::from_wh(w, h),
586        }
587    } else {
588        Size::from_wh(
589            svg.convert_user_length(AId::Width, &state, def),
590            svg.convert_user_length(AId::Height, &state, def),
591        )
592    };
593
594    (size.ok_or(Error::InvalidSize), restore_viewbox)
595}
596
597/// Calculates SVG's size and viewBox in case there were not set.
598///
599/// Simply iterates over all nodes and calculates a bounding box.
600fn calculate_svg_bbox(tree: &mut Tree) {
601    let bbox = tree.root.abs_bounding_box();
602    if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
603        tree.size = size;
604    }
605}
606
607#[inline(never)]
608pub(crate) fn convert_children(
609    parent_node: SvgNode,
610    state: &State,
611    cache: &mut Cache,
612    parent: &mut Group,
613) {
614    for node in parent_node.children() {
615        convert_element(node, state, cache, parent);
616    }
617}
618
619#[inline(never)]
620pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
621    let tag_name = match node.tag_name() {
622        Some(v) => v,
623        None => return,
624    };
625
626    if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
627        return;
628    }
629
630    if !node.is_visible_element(state.opt) {
631        return;
632    }
633
634    if tag_name == EId::Use {
635        super::use_node::convert(node, state, cache, parent);
636        return;
637    }
638
639    if tag_name == EId::Switch {
640        super::switch::convert(node, state, cache, parent);
641        return;
642    }
643
644    // A nested `svg` is handled just like `use`: it must not be wrapped in an
645    // additional `convert_group`, otherwise its `transform` (and other group
646    // properties) would be applied twice, since `convert_svg`
647    // already creates its own group. The outermost `svg` (no parent element)
648    // is intentionally not handled here - it's processed directly in
649    // `convert_doc`.
650    if tag_name == EId::Svg && node.parent_element().is_some() {
651        super::use_node::convert_svg(node, state, cache, parent);
652
653        return;
654    }
655
656    if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
657        convert_element_impl(tag_name, node, state, cache, g);
658    }) {
659        parent.children.push(Node::Group(Box::new(g)));
660    }
661}
662
663#[inline(never)]
664fn convert_element_impl(
665    tag_name: EId,
666    node: SvgNode,
667    state: &State,
668    cache: &mut Cache,
669    parent: &mut Group,
670) {
671    match tag_name {
672        EId::Rect
673        | EId::Circle
674        | EId::Ellipse
675        | EId::Line
676        | EId::Polyline
677        | EId::Polygon
678        | EId::Path => {
679            if let Some(path) = super::shapes::convert(node, state) {
680                convert_path(node, path, state, cache, parent);
681            }
682        }
683        EId::Image => {
684            super::image::convert(node, state, cache, parent);
685        }
686        EId::Text => {
687            #[cfg(feature = "text")]
688            {
689                super::text::convert(node, state, cache, parent);
690            }
691        }
692        EId::Svg => {
693            // Only the outermost `svg` reaches this point; nested `svg` elements are
694            // handled earlier in `convert_element`. The root `svg` itself is
695            // skipped and only its children are converted.
696            convert_children(node, state, cache, parent);
697        }
698        EId::G => {
699            convert_children(node, state, cache, parent);
700        }
701        _ => {}
702    }
703}
704
705// `clipPath` can have only shape and `text` children.
706//
707// `line` doesn't impact rendering because stroke is always disabled
708// for `clipPath` children.
709#[inline(never)]
710pub(crate) fn convert_clip_path_elements(
711    clip_node: SvgNode,
712    state: &State,
713    cache: &mut Cache,
714    parent: &mut Group,
715) {
716    for node in clip_node.children() {
717        let tag_name = match node.tag_name() {
718            Some(v) => v,
719            None => continue,
720        };
721
722        if !tag_name.is_graphic() {
723            continue;
724        }
725
726        if !node.is_visible_element(state.opt) {
727            continue;
728        }
729
730        if tag_name == EId::Use {
731            super::use_node::convert(node, state, cache, parent);
732            continue;
733        }
734
735        if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
736            convert_clip_path_elements_impl(tag_name, node, state, cache, g);
737        }) {
738            parent.children.push(Node::Group(Box::new(g)));
739        }
740    }
741}
742
743#[inline(never)]
744fn convert_clip_path_elements_impl(
745    tag_name: EId,
746    node: SvgNode,
747    state: &State,
748    cache: &mut Cache,
749    parent: &mut Group,
750) {
751    match tag_name {
752        EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
753            if let Some(path) = super::shapes::convert(node, state) {
754                convert_path(node, path, state, cache, parent);
755            }
756        }
757        EId::Text => {
758            #[cfg(feature = "text")]
759            {
760                super::text::convert(node, state, cache, parent);
761            }
762        }
763        _ => {
764            log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
765        }
766    }
767}
768
769#[derive(Clone, Copy, PartialEq, Debug)]
770enum Isolation {
771    Auto,
772    Isolate,
773}
774
775impl Default for Isolation {
776    fn default() -> Self {
777        Self::Auto
778    }
779}
780
781impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
782    fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
783        match value {
784            "auto" => Some(Isolation::Auto),
785            "isolate" => Some(Isolation::Isolate),
786            _ => None,
787        }
788    }
789}
790
791// TODO: explain
792pub(crate) fn convert_group(
793    node: SvgNode,
794    state: &State,
795    force: bool,
796    cache: &mut Cache,
797    parent: &mut Group,
798    collect_children: &dyn Fn(&mut Cache, &mut Group),
799) -> Option<Group> {
800    // A `clipPath` child cannot have an opacity.
801    let opacity = if state.parent_clip_path.is_none() {
802        node.attribute::<Opacity>(AId::Opacity)
803            .unwrap_or(Opacity::ONE)
804    } else {
805        Opacity::ONE
806    };
807
808    let transform = node.resolve_transform(AId::Transform, state);
809    let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
810    let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
811    let isolate = isolation == Isolation::Isolate;
812
813    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
814    let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
815    let id = if is_g_or_use && state.parent_markers.is_empty() {
816        node.element_id().to_string()
817    } else {
818        String::new()
819    };
820
821    let abs_transform = parent.abs_transform.pre_concat(transform);
822    let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
823    let mut g = Group {
824        id,
825        transform,
826        abs_transform,
827        opacity,
828        blend_mode,
829        isolate,
830        clip_path: None,
831        mask: None,
832        filters: Vec::new(),
833        is_context_element: false,
834        bounding_box: dummy,
835        abs_bounding_box: dummy,
836        stroke_bounding_box: dummy,
837        abs_stroke_bounding_box: dummy,
838        layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
839        abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
840        children: Vec::new(),
841    };
842    collect_children(cache, &mut g);
843
844    // We need to know group's bounding box before converting
845    // clipPaths, masks and filters.
846    let object_bbox = g.calculate_object_bbox();
847
848    // `mask` and `filter` cannot be set on `clipPath` children.
849    // But `clip-path` can.
850
851    let mut clip_path = None;
852    if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
853        clip_path = super::clippath::convert(link, state, object_bbox, cache);
854        if clip_path.is_none() {
855            return None;
856        }
857    }
858
859    let mut mask = None;
860    if state.parent_clip_path.is_none() {
861        if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
862            mask = super::mask::convert(link, state, object_bbox, cache);
863            if mask.is_none() {
864                return None;
865            }
866        }
867    }
868
869    let filters = {
870        let mut filters = Vec::new();
871        if state.parent_clip_path.is_none() {
872            if node.attribute(AId::Filter) == Some("none") {
873                // Do nothing.
874            } else if node.has_attribute(AId::Filter) {
875                if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
876                    filters = f;
877                } else {
878                    // A filter that not a link or a filter with a link to a non existing element.
879                    //
880                    // Unlike `clip-path` and `mask`, when a `filter` link is invalid
881                    // then the whole element should be ignored.
882                    //
883                    // This is kinda an undefined behaviour.
884                    // In most cases, Chrome, Firefox and rsvg will ignore such elements,
885                    // but in some cases Chrome allows it. Not sure why.
886                    // Inkscape (0.92) simply ignores such attributes, rendering element as is.
887                    // Batik (1.12) crashes.
888                    //
889                    // Test file: e-filter-051.svg
890                    return None;
891                }
892            }
893        }
894
895        filters
896    };
897
898    let required = opacity.get().approx_ne_ulps(&1.0, 4)
899        || clip_path.is_some()
900        || mask.is_some()
901        || !filters.is_empty()
902        || !transform.is_identity()
903        || blend_mode != BlendMode::Normal
904        || isolate
905        || is_g_or_use
906        || force;
907
908    if !required {
909        parent.children.append(&mut g.children);
910        return None;
911    }
912
913    g.clip_path = clip_path;
914    g.mask = mask;
915    g.filters = filters;
916
917    // Must be called after we set Group::filters
918    g.calculate_bounding_boxes();
919
920    Some(g)
921}
922
923fn convert_path(
924    node: SvgNode,
925    tiny_skia_path: Arc<tiny_skia_path::Path>,
926    state: &State,
927    cache: &mut Cache,
928    parent: &mut Group,
929) {
930    debug_assert!(tiny_skia_path.len() >= 2);
931    if tiny_skia_path.len() < 2 {
932        return;
933    }
934
935    let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
936    let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
937    let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
938    let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
939    let mut visible = visibility == Visibility::Visible;
940    let rendering_mode: ShapeRendering = node
941        .find_attribute(AId::ShapeRendering)
942        .unwrap_or(state.opt.shape_rendering);
943
944    // TODO: handle `markers` before `stroke`
945    let raw_paint_order: svgtypes::PaintOrder =
946        node.find_attribute(AId::PaintOrder).unwrap_or_default();
947    let paint_order = svg_paint_order_to_usvg(raw_paint_order);
948    let path_transform = parent.abs_transform;
949
950    // If a path doesn't have a fill or a stroke then it's invisible.
951    // By setting `visibility` to `hidden` we are disabling rendering of this path.
952    if fill.is_none() && stroke.is_none() {
953        visible = false;
954    }
955
956    if let Some(fill) = fill.as_mut() {
957        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
958            fill.context_element
959        {
960            process_paint(
961                &mut fill.paint,
962                true,
963                context_transform,
964                context_bbox.map(|r| r.to_rect()),
965                path_transform,
966                tiny_skia_path.bounds(),
967                cache,
968            );
969            fill.context_element = None;
970        }
971    }
972
973    if let Some(stroke) = stroke.as_mut() {
974        if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
975            stroke.context_element
976        {
977            process_paint(
978                &mut stroke.paint,
979                true,
980                context_transform,
981                context_bbox.map(|r| r.to_rect()),
982                path_transform,
983                tiny_skia_path.bounds(),
984                cache,
985            );
986            stroke.context_element = None;
987        }
988    }
989
990    let mut marker = None;
991    if marker::is_valid(node) && visibility == Visibility::Visible {
992        let mut marker_group = Group {
993            abs_transform: parent.abs_transform,
994            ..Group::empty()
995        };
996
997        let mut marker_state = state.clone();
998
999        let bbox = tiny_skia_path
1000            .compute_tight_bounds()
1001            .and_then(|r| r.to_non_zero_rect());
1002
1003        let fill = fill.clone().map(|mut f| {
1004            f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1005            f
1006        });
1007
1008        let stroke = stroke.clone().map(|mut s| {
1009            s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1010            s
1011        });
1012
1013        marker_state.context_element = Some((fill, stroke));
1014
1015        marker::convert(
1016            node,
1017            &tiny_skia_path,
1018            &marker_state,
1019            cache,
1020            &mut marker_group,
1021        );
1022        marker_group.calculate_bounding_boxes();
1023        marker = Some(marker_group);
1024    }
1025
1026    // Nodes generated by markers must not have an ID. Otherwise we would have duplicates.
1027    let id = if state.parent_markers.is_empty() {
1028        node.element_id().to_string()
1029    } else {
1030        String::new()
1031    };
1032
1033    let path = Path::new(
1034        id,
1035        visible,
1036        fill,
1037        stroke,
1038        paint_order,
1039        rendering_mode,
1040        tiny_skia_path,
1041        path_transform,
1042    );
1043
1044    let path = match path {
1045        Some(v) => v,
1046        None => return,
1047    };
1048
1049    match (raw_paint_order.order, marker) {
1050        ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {
1051            parent.children.push(Node::Group(Box::new(markers_node)));
1052            parent.children.push(Node::Path(Box::new(path.clone())));
1053        }
1054        ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {
1055            append_single_paint_path(first, &path, parent);
1056            parent.children.push(Node::Group(Box::new(markers_node)));
1057            append_single_paint_path(last, &path, parent);
1058        }
1059        ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {
1060            parent.children.push(Node::Path(Box::new(path.clone())));
1061            parent.children.push(Node::Group(Box::new(markers_node)));
1062        }
1063        _ => parent.children.push(Node::Path(Box::new(path.clone()))),
1064    }
1065}
1066
1067fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
1068    match paint_order_kind {
1069        PaintOrderKind::Fill => {
1070            if path.fill.is_some() {
1071                let mut fill_path = path.clone();
1072                fill_path.stroke = None;
1073                fill_path.id = String::new();
1074                parent.children.push(Node::Path(Box::new(fill_path)));
1075            }
1076        }
1077        PaintOrderKind::Stroke => {
1078            if path.stroke.is_some() {
1079                let mut stroke_path = path.clone();
1080                stroke_path.fill = None;
1081                stroke_path.id = String::new();
1082                parent.children.push(Node::Path(Box::new(stroke_path)));
1083            }
1084        }
1085        _ => {}
1086    }
1087}
1088
1089pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
1090    match (order.order[0], order.order[1]) {
1091        (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
1092        (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
1093            PaintOrder::StrokeAndFill
1094        }
1095        _ => PaintOrder::FillAndStroke,
1096    }
1097}
1098
1099impl SvgNode<'_, '_> {
1100    pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
1101        let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
1102        let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);
1103
1104        if let Some(transform_origin) = transform_origin {
1105            let dx = convert_length(
1106                transform_origin.x_offset,
1107                *self,
1108                AId::Width,
1109                Units::UserSpaceOnUse,
1110                state,
1111            );
1112            let dy = convert_length(
1113                transform_origin.y_offset,
1114                *self,
1115                AId::Height,
1116                Units::UserSpaceOnUse,
1117                state,
1118            );
1119            transform = Transform::default()
1120                .pre_translate(dx, dy)
1121                .pre_concat(transform)
1122                .pre_translate(-dx, -dy);
1123        }
1124
1125        transform
1126    }
1127}