usvg/parser/
mask.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 std::sync::Arc;
6
7use svgtypes::{Length, LengthUnit as Unit};
8
9use super::svgtree::{AId, EId, SvgNode};
10use super::{converter, OptionLog};
11use crate::{Group, Mask, MaskType, Node, NonEmptyString, NonZeroRect, Transform, Units};
12
13pub(crate) fn convert(
14    node: SvgNode,
15    state: &converter::State,
16    object_bbox: Option<NonZeroRect>,
17    cache: &mut converter::Cache,
18) -> Option<Arc<Mask>> {
19    // A `mask` attribute must reference a `mask` element.
20    if node.tag_name() != Some(EId::Mask) {
21        return None;
22    }
23
24    let units = node
25        .attribute(AId::MaskUnits)
26        .unwrap_or(Units::ObjectBoundingBox);
27
28    let content_units = node
29        .attribute(AId::MaskContentUnits)
30        .unwrap_or(Units::UserSpaceOnUse);
31
32    // Check if this element was already converted.
33    //
34    // Only `userSpaceOnUse` masks can be shared,
35    // because `objectBoundingBox` one will be converted into user one
36    // and will become node-specific.
37    let cacheable = units == Units::UserSpaceOnUse && content_units == Units::UserSpaceOnUse;
38    if cacheable {
39        if let Some(mask) = cache.masks.get(node.element_id()) {
40            return Some(mask.clone());
41        }
42    }
43
44    let rect = NonZeroRect::from_xywh(
45        node.convert_length(AId::X, units, state, Length::new(-10.0, Unit::Percent)),
46        node.convert_length(AId::Y, units, state, Length::new(-10.0, Unit::Percent)),
47        node.convert_length(AId::Width, units, state, Length::new(120.0, Unit::Percent)),
48        node.convert_length(AId::Height, units, state, Length::new(120.0, Unit::Percent)),
49    );
50    let mut rect =
51        rect.log_none(|| log::warn!("Mask '{}' has an invalid size. Skipped.", node.element_id()))?;
52
53    let mut mask_all = false;
54    if units == Units::ObjectBoundingBox {
55        if let Some(bbox) = object_bbox {
56            rect = rect.bbox_transform(bbox)
57        } else {
58            // When mask units are `objectBoundingBox` and bbox is zero-sized - the whole
59            // element should be masked.
60            // Technically an UB, but this is what Chrome and Firefox do.
61            mask_all = true;
62        }
63    }
64
65    let mut id = NonEmptyString::new(node.element_id().to_string())?;
66    // Generate ID only when we're parsing `objectBoundingBox` mask for the second time.
67    if !cacheable && cache.masks.contains_key(id.get()) {
68        id = cache.gen_mask_id();
69    }
70    let id_copy = id.get().to_string();
71
72    if mask_all {
73        let mask = Arc::new(Mask {
74            id,
75            rect,
76            kind: MaskType::Luminance,
77            mask: None,
78            root: Group::empty(),
79        });
80        cache.masks.insert(id_copy, mask.clone());
81        return Some(mask);
82    }
83
84    // Resolve linked mask.
85    let mut mask = None;
86    if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
87        mask = convert(link, state, object_bbox, cache);
88
89        // Linked `mask` must be valid.
90        if mask.is_none() {
91            return None;
92        }
93    }
94
95    let kind = if node.attribute(AId::MaskType) == Some("alpha") {
96        MaskType::Alpha
97    } else {
98        MaskType::Luminance
99    };
100
101    let mut mask = Mask {
102        id,
103        rect,
104        kind,
105        mask,
106        root: Group::empty(),
107    };
108
109    // To emulate content `objectBoundingBox` units we have to put
110    // mask children into a group with a transform.
111    let mut subroot = None;
112    if content_units == Units::ObjectBoundingBox {
113        let object_bbox = match object_bbox {
114            Some(v) => v,
115            None => {
116                log::warn!("Masking of zero-sized shapes is not allowed.");
117                return None;
118            }
119        };
120
121        let mut g = Group::empty();
122        g.transform = Transform::from_bbox(object_bbox);
123        // Make sure to set `abs_transform`, because it must propagate to all children.
124        g.abs_transform = g.transform;
125
126        subroot = Some(g);
127    }
128
129    {
130        // Prefer `subroot` to `mask.root`.
131        let real_root = subroot.as_mut().unwrap_or(&mut mask.root);
132        converter::convert_children(node, state, cache, real_root);
133
134        // A mask without children at this point is invalid.
135        // Only masks with zero bbox and `objectBoundingBox` can be empty.
136        if !real_root.has_children() {
137            return None;
138        }
139    }
140
141    if let Some(mut subroot) = subroot {
142        subroot.calculate_bounding_boxes();
143        mask.root.children.push(Node::Group(Box::new(subroot)));
144    }
145
146    mask.root.calculate_bounding_boxes();
147
148    let mask = Arc::new(mask);
149    cache.masks.insert(id_copy, mask.clone());
150    Some(mask)
151}