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