1
use markup5ever::{expanded_name, local_name, namespace_url, ns};
2

            
3
use crate::document::AcquiredNodes;
4
use crate::drawing_ctx::DrawingCtx;
5
use crate::element::{set_attribute, ElementData, ElementTrait};
6
use crate::node::{CascadedValues, Node, NodeBorrow};
7
use crate::parsers::ParseValue;
8
use crate::properties::ColorInterpolationFilters;
9
use crate::rect::IRect;
10
use crate::session::Session;
11
use crate::surface_utils::shared_surface::{Operator, SharedImageSurface, SurfaceType};
12
use crate::xml::Attributes;
13

            
14
use super::bounds::BoundsBuilder;
15
use super::context::{FilterContext, FilterOutput};
16
use super::{
17
    FilterEffect, FilterError, FilterResolveError, Input, Primitive, PrimitiveParams,
18
    ResolvedPrimitive,
19
};
20

            
21
/// The `feMerge` filter primitive.
22
pub struct FeMerge {
23
    base: Primitive,
24
}
25

            
26
/// The `<feMergeNode>` element.
27
76
#[derive(Clone, Default)]
28
pub struct FeMergeNode {
29
38
    in1: Input,
30
}
31

            
32
/// Resolved `feMerge` primitive for rendering.
33
pub struct Merge {
34
    pub merge_nodes: Vec<MergeNode>,
35
}
36

            
37
/// Resolved `feMergeNode` for rendering.
38
8
#[derive(Debug, Default, PartialEq)]
39
pub struct MergeNode {
40
6
    pub in1: Input,
41
6
    pub color_interpolation_filters: ColorInterpolationFilters,
42
}
43

            
44
impl Default for FeMerge {
45
    /// Constructs a new `Merge` with empty properties.
46
    #[inline]
47
18
    fn default() -> FeMerge {
48
18
        FeMerge {
49
18
            base: Default::default(),
50
        }
51
18
    }
52
}
53

            
54
impl ElementTrait for FeMerge {
55
18
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
56
18
        self.base.parse_no_inputs(attrs, session);
57
18
    }
58
}
59

            
60
impl ElementTrait for FeMergeNode {
61
38
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
62
77
        for (attr, value) in attrs.iter() {
63
39
            if let expanded_name!("", "in") = attr.expanded() {
64
36
                set_attribute(&mut self.in1, attr.parse(value), session);
65
            }
66
39
        }
67
38
    }
68
}
69

            
70
impl MergeNode {
71
48
    fn render(
72
        &self,
73
        ctx: &FilterContext,
74
        acquired_nodes: &mut AcquiredNodes<'_>,
75
        draw_ctx: &mut DrawingCtx,
76
        bounds: IRect,
77
        output_surface: Option<SharedImageSurface>,
78
    ) -> Result<SharedImageSurface, FilterError> {
79
96
        let input = ctx.get_input(
80
            acquired_nodes,
81
            draw_ctx,
82
            &self.in1,
83
48
            self.color_interpolation_filters,
84
        )?;
85

            
86
48
        if output_surface.is_none() {
87
23
            return Ok(input.surface().clone());
88
        }
89

            
90
50
        input
91
            .surface()
92
25
            .compose(&output_surface.unwrap(), bounds, Operator::Over)
93
            .map_err(FilterError::CairoError)
94
48
    }
95
}
96

            
97
impl Merge {
98
23
    pub fn render(
99
        &self,
100
        bounds_builder: BoundsBuilder,
101
        ctx: &FilterContext,
102
        acquired_nodes: &mut AcquiredNodes<'_>,
103
        draw_ctx: &mut DrawingCtx,
104
    ) -> Result<FilterOutput, FilterError> {
105
        // Compute the filter bounds, taking each feMergeNode's input into account.
106
23
        let mut bounds_builder = bounds_builder;
107
71
        for merge_node in &self.merge_nodes {
108
48
            let input = ctx.get_input(
109
                acquired_nodes,
110
                draw_ctx,
111
                &merge_node.in1,
112
48
                merge_node.color_interpolation_filters,
113
            )?;
114
48
            bounds_builder = bounds_builder.add_input(&input);
115
48
        }
116

            
117
23
        let bounds: IRect = bounds_builder.compute(ctx).clipped.into();
118

            
119
        // Now merge them all.
120
23
        let mut output_surface = None;
121
71
        for merge_node in &self.merge_nodes {
122
48
            output_surface = merge_node
123
48
                .render(ctx, acquired_nodes, draw_ctx, bounds, output_surface)
124
                .ok();
125
        }
126

            
127
23
        let surface = match output_surface {
128
23
            Some(s) => s,
129
            None => SharedImageSurface::empty(
130
                ctx.source_graphic().width(),
131
                ctx.source_graphic().height(),
132
                SurfaceType::AlphaOnly,
133
            )?,
134
        };
135

            
136
23
        Ok(FilterOutput { surface, bounds })
137
23
    }
138
}
139

            
140
impl FilterEffect for FeMerge {
141
22
    fn resolve(
142
        &self,
143
        _acquired_nodes: &mut AcquiredNodes<'_>,
144
        node: &Node,
145
    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
146
44
        Ok(vec![ResolvedPrimitive {
147
22
            primitive: self.base.clone(),
148
22
            params: PrimitiveParams::Merge(Merge {
149
22
                merge_nodes: resolve_merge_nodes(node)?,
150
            }),
151
        }])
152
22
    }
153
}
154

            
155
/// Takes a feMerge and walks its children to produce a list of feMergeNode arguments.
156
22
fn resolve_merge_nodes(node: &Node) -> Result<Vec<MergeNode>, FilterResolveError> {
157
22
    let mut merge_nodes = Vec::new();
158

            
159
136
    for child in node.children().filter(|c| c.is_element()) {
160
46
        let cascaded = CascadedValues::new_from_node(&child);
161
46
        let values = cascaded.get();
162

            
163
46
        if let ElementData::FeMergeNode(merge_node) = &*child.borrow_element_data() {
164
46
            merge_nodes.push(MergeNode {
165
46
                in1: merge_node.in1.clone(),
166
46
                color_interpolation_filters: values.color_interpolation_filters(),
167
            });
168
        }
169
68
    }
170

            
171
70
    Ok(merge_nodes)
172
70
}
173

            
174
#[cfg(test)]
175
mod tests {
176
    use super::*;
177

            
178
    use crate::borrow_element_as;
179
    use crate::document::Document;
180

            
181
    #[test]
182
2
    fn extracts_parameters() {
183
1
        let document = Document::load_from_bytes(
184
            br#"<?xml version="1.0" encoding="UTF-8"?>
185
<svg xmlns="http://www.w3.org/2000/svg">
186
  <filter id="filter">
187
    <feMerge id="merge">
188
      <feMergeNode in="SourceGraphic"/>
189
      <feMergeNode in="SourceAlpha" color-interpolation-filters="sRGB"/>
190
    </feMerge>
191
  </filter>
192
</svg>
193
"#,
194
        );
195
1
        let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
196

            
197
1
        let node = document.lookup_internal_node("merge").unwrap();
198
        let merge = borrow_element_as!(node, FeMerge);
199
1
        let resolved = merge.resolve(&mut acquired_nodes, &node).unwrap();
200
1
        let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
201
1
        let params = match params {
202
1
            PrimitiveParams::Merge(m) => m,
203
            _ => unreachable!(),
204
        };
205
2
        assert_eq!(
206
1
            &params.merge_nodes[..],
207
1
            vec![
208
1
                MergeNode {
209
1
                    in1: Input::SourceGraphic,
210
1
                    color_interpolation_filters: Default::default(),
211
                },
212
1
                MergeNode {
213
1
                    in1: Input::SourceAlpha,
214
1
                    color_interpolation_filters: ColorInterpolationFilters::Srgb,
215
                },
216
            ]
217
        );
218
2
    }
219
}