usvg/parser/
switch.rs

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