usvg/parser/
mask.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::sync::Arc;

use svgtypes::{Length, LengthUnit as Unit};

use super::svgtree::{AId, EId, SvgNode};
use super::{converter, OptionLog};
use crate::{Group, Mask, MaskType, Node, NonEmptyString, NonZeroRect, Transform, Units};

pub(crate) fn convert(
    node: SvgNode,
    state: &converter::State,
    object_bbox: Option<NonZeroRect>,
    cache: &mut converter::Cache,
) -> Option<Arc<Mask>> {
    // A `mask` attribute must reference a `mask` element.
    if node.tag_name() != Some(EId::Mask) {
        return None;
    }

    let units = node
        .attribute(AId::MaskUnits)
        .unwrap_or(Units::ObjectBoundingBox);

    let content_units = node
        .attribute(AId::MaskContentUnits)
        .unwrap_or(Units::UserSpaceOnUse);

    // Check if this element was already converted.
    //
    // Only `userSpaceOnUse` masks can be shared,
    // because `objectBoundingBox` one will be converted into user one
    // and will become node-specific.
    let cacheable = units == Units::UserSpaceOnUse && content_units == Units::UserSpaceOnUse;
    if cacheable {
        if let Some(mask) = cache.masks.get(node.element_id()) {
            return Some(mask.clone());
        }
    }

    let rect = NonZeroRect::from_xywh(
        node.convert_length(AId::X, units, state, Length::new(-10.0, Unit::Percent)),
        node.convert_length(AId::Y, units, state, Length::new(-10.0, Unit::Percent)),
        node.convert_length(AId::Width, units, state, Length::new(120.0, Unit::Percent)),
        node.convert_length(AId::Height, units, state, Length::new(120.0, Unit::Percent)),
    );
    let mut rect =
        rect.log_none(|| log::warn!("Mask '{}' has an invalid size. Skipped.", node.element_id()))?;

    let mut mask_all = false;
    if units == Units::ObjectBoundingBox {
        if let Some(bbox) = object_bbox {
            rect = rect.bbox_transform(bbox)
        } else {
            // When mask units are `objectBoundingBox` and bbox is zero-sized - the whole
            // element should be masked.
            // Technically an UB, but this is what Chrome and Firefox do.
            mask_all = true;
        }
    }

    let mut id = NonEmptyString::new(node.element_id().to_string())?;
    // Generate ID only when we're parsing `objectBoundingBox` mask for the second time.
    if !cacheable && cache.masks.contains_key(id.get()) {
        id = cache.gen_mask_id();
    }
    let id_copy = id.get().to_string();

    if mask_all {
        let mask = Arc::new(Mask {
            id,
            rect,
            kind: MaskType::Luminance,
            mask: None,
            root: Group::empty(),
        });
        cache.masks.insert(id_copy, mask.clone());
        return Some(mask);
    }

    // Resolve linked mask.
    let mut mask = None;
    if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
        mask = convert(link, state, object_bbox, cache);

        // Linked `mask` must be valid.
        if mask.is_none() {
            return None;
        }
    }

    let kind = if node.attribute(AId::MaskType) == Some("alpha") {
        MaskType::Alpha
    } else {
        MaskType::Luminance
    };

    let mut mask = Mask {
        id,
        rect,
        kind,
        mask,
        root: Group::empty(),
    };

    // To emulate content `objectBoundingBox` units we have to put
    // mask children into a group with a transform.
    let mut subroot = None;
    if content_units == Units::ObjectBoundingBox {
        let object_bbox = match object_bbox {
            Some(v) => v,
            None => {
                log::warn!("Masking of zero-sized shapes is not allowed.");
                return None;
            }
        };

        let mut g = Group::empty();
        g.transform = Transform::from_bbox(object_bbox);
        // Make sure to set `abs_transform`, because it must propagate to all children.
        g.abs_transform = g.transform;

        subroot = Some(g);
    }

    {
        // Prefer `subroot` to `mask.root`.
        let real_root = subroot.as_mut().unwrap_or(&mut mask.root);
        converter::convert_children(node, state, cache, real_root);

        // A mask without children at this point is invalid.
        // Only masks with zero bbox and `objectBoundingBox` can be empty.
        if !real_root.has_children() {
            return None;
        }
    }

    if let Some(mut subroot) = subroot {
        subroot.calculate_bounding_boxes();
        mask.root.children.push(Node::Group(Box::new(subroot)));
    }

    mask.root.calculate_bounding_boxes();

    let mask = Arc::new(mask);
    cache.masks.insert(id_copy, mask.clone());
    Some(mask)
}