usvg/parser/
switch.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use super::svgtree::{AId, SvgNode};
5use super::{converter, Options};
6use crate::{Group, Node};
7
8// Full list can be found here: https://www.w3.org/TR/SVG11/feature.html
9static FEATURES: &[&str] = &[
10    "http://www.w3.org/TR/SVG11/feature#SVGDOM-static",
11    "http://www.w3.org/TR/SVG11/feature#SVG-static",
12    "http://www.w3.org/TR/SVG11/feature#CoreAttribute", // no xml:base and xml:lang
13    "http://www.w3.org/TR/SVG11/feature#Structure",
14    "http://www.w3.org/TR/SVG11/feature#BasicStructure",
15    "http://www.w3.org/TR/SVG11/feature#ContainerAttribute", // `enable-background`
16    "http://www.w3.org/TR/SVG11/feature#ConditionalProcessing",
17    "http://www.w3.org/TR/SVG11/feature#Image",
18    "http://www.w3.org/TR/SVG11/feature#Style",
19    // "http://www.w3.org/TR/SVG11/feature#ViewportAttribute", // `clip` and `overflow`, not yet
20    "http://www.w3.org/TR/SVG11/feature#Shape",
21    "http://www.w3.org/TR/SVG11/feature#Text",
22    "http://www.w3.org/TR/SVG11/feature#BasicText",
23    "http://www.w3.org/TR/SVG11/feature#PaintAttribute", // no color-interpolation and color-rendering
24    "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute", // no color-interpolation
25    "http://www.w3.org/TR/SVG11/feature#OpacityAttribute",
26    "http://www.w3.org/TR/SVG11/feature#GraphicsAttribute",
27    "http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute",
28    "http://www.w3.org/TR/SVG11/feature#Marker",
29    // "http://www.w3.org/TR/SVG11/feature#ColorProfile", // not yet
30    "http://www.w3.org/TR/SVG11/feature#Gradient",
31    "http://www.w3.org/TR/SVG11/feature#Pattern",
32    "http://www.w3.org/TR/SVG11/feature#Clip",
33    "http://www.w3.org/TR/SVG11/feature#BasicClip",
34    "http://www.w3.org/TR/SVG11/feature#Mask",
35    "http://www.w3.org/TR/SVG11/feature#Filter",
36    "http://www.w3.org/TR/SVG11/feature#BasicFilter",
37    // only xlink:href
38    "http://www.w3.org/TR/SVG11/feature#XlinkAttribute",
39    // "http://www.w3.org/TR/SVG11/feature#Font",
40    // "http://www.w3.org/TR/SVG11/feature#BasicFont",
41];
42
43pub(crate) fn convert(
44    node: SvgNode,
45    state: &converter::State,
46    cache: &mut converter::Cache,
47    parent: &mut Group,
48) -> Option<()> {
49    let child = node
50        .children()
51        .find(|n| is_condition_passed(*n, state.opt))?;
52    if let Some(g) = converter::convert_group(node, state, false, cache, parent, &|cache, g| {
53        converter::convert_element(child, state, cache, g);
54    }) {
55        parent.children.push(Node::Group(Box::new(g)));
56    }
57
58    Some(())
59}
60
61pub(crate) fn is_condition_passed(node: SvgNode, opt: &Options) -> bool {
62    if !node.is_element() {
63        return false;
64    }
65
66    if node.has_attribute(AId::RequiredExtensions) {
67        return false;
68    }
69
70    // 'The value is a list of feature strings, with the individual values separated by white space.
71    // Determines whether all of the named features are supported by the user agent.
72    // Only feature strings defined in the Feature String appendix are allowed.
73    // If all of the given features are supported, then the attribute evaluates to true;
74    // otherwise, the current element and its children are skipped and thus will not be rendered.'
75    if let Some(features) = node.attribute::<&str>(AId::RequiredFeatures) {
76        for feature in features.split(' ') {
77            if !FEATURES.contains(&feature) {
78                return false;
79            }
80        }
81    }
82
83    if !is_valid_sys_lang(node, opt) {
84        return false;
85    }
86
87    true
88}
89
90/// SVG spec 5.8.5
91fn is_valid_sys_lang(node: SvgNode, opt: &Options) -> bool {
92    // 'The attribute value is a comma-separated list of language names
93    // as defined in BCP 47.'
94    //
95    // But we support only simple cases like `en` or `en-US`.
96    // No one really uses this, especially with complex BCP 47 values.
97    if let Some(langs) = node.attribute::<&str>(AId::SystemLanguage) {
98        let mut has_match = false;
99        for lang in langs.split(',') {
100            let lang = lang.trim();
101
102            // 'Evaluates to `true` if one of the languages indicated by user preferences exactly
103            // equals one of the languages given in the value of this parameter.'
104            if opt.languages.iter().any(|v| v == lang) {
105                has_match = true;
106                break;
107            }
108
109            // 'If one of the languages indicated by user preferences exactly equals a prefix
110            // of one of the languages given in the value of this parameter such that
111            // the first tag character following the prefix is `-`.'
112            if let Some(idx) = lang.bytes().position(|c| c == b'-') {
113                let lang_prefix = &lang[..idx];
114                if opt.languages.iter().any(|v| v == lang_prefix) {
115                    has_match = true;
116                    break;
117                }
118            }
119        }
120
121        has_match
122    } else {
123        true
124    }
125}