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/.
45use std::sync::Arc;
67use svgtypes::{Length, LengthUnit as Unit};
89use super::svgtree::{AId, EId, SvgNode};
10use super::{converter, OptionLog};
11use crate::{Group, Mask, MaskType, Node, NonEmptyString, NonZeroRect, Transform, Units};
1213pub(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.
20if node.tag_name() != Some(EId::Mask) {
21return None;
22 }
2324let units = node
25 .attribute(AId::MaskUnits)
26 .unwrap_or(Units::ObjectBoundingBox);
2728let content_units = node
29 .attribute(AId::MaskContentUnits)
30 .unwrap_or(Units::UserSpaceOnUse);
3132// 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.
37let cacheable = units == Units::UserSpaceOnUse && content_units == Units::UserSpaceOnUse;
38if cacheable {
39if let Some(mask) = cache.masks.get(node.element_id()) {
40return Some(mask.clone());
41 }
42 }
4344let 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 );
50let mut rect =
51 rect.log_none(|| log::warn!("Mask '{}' has an invalid size. Skipped.", node.element_id()))?;
5253let mut mask_all = false;
54if units == Units::ObjectBoundingBox {
55if 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.
61mask_all = true;
62 }
63 }
6465let mut id = NonEmptyString::new(node.element_id().to_string())?;
66// Generate ID only when we're parsing `objectBoundingBox` mask for the second time.
67if !cacheable && cache.masks.contains_key(id.get()) {
68 id = cache.gen_mask_id();
69 }
70let id_copy = id.get().to_string();
7172if mask_all {
73let 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());
81return Some(mask);
82 }
8384// Resolve linked mask.
85let mut mask = None;
86if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
87 mask = convert(link, state, object_bbox, cache);
8889// Linked `mask` must be valid.
90if mask.is_none() {
91return None;
92 }
93 }
9495let kind = if node.attribute(AId::MaskType) == Some("alpha") {
96 MaskType::Alpha
97 } else {
98 MaskType::Luminance
99 };
100101let mut mask = Mask {
102 id,
103 rect,
104 kind,
105 mask,
106 root: Group::empty(),
107 };
108109// To emulate content `objectBoundingBox` units we have to put
110 // mask children into a group with a transform.
111let mut subroot = None;
112if content_units == Units::ObjectBoundingBox {
113let object_bbox = match object_bbox {
114Some(v) => v,
115None => {
116log::warn!("Masking of zero-sized shapes is not allowed.");
117return None;
118 }
119 };
120121let 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.
124g.abs_transform = g.transform;
125126 subroot = Some(g);
127 }
128129 {
130// Prefer `subroot` to `mask.root`.
131let real_root = subroot.as_mut().unwrap_or(&mut mask.root);
132 converter::convert_children(node, state, cache, real_root);
133134// A mask without children at this point is invalid.
135 // Only masks with zero bbox and `objectBoundingBox` can be empty.
136if !real_root.has_children() {
137return None;
138 }
139 }
140141if let Some(mut subroot) = subroot {
142 subroot.calculate_bounding_boxes();
143 mask.root.children.push(Node::Group(Box::new(subroot)));
144 }
145146 mask.root.calculate_bounding_boxes();
147148let mask = Arc::new(mask);
149 cache.masks.insert(id_copy, mask.clone());
150Some(mask)
151}