1use markup5ever::{expanded_name, local_name, ns};
2
3use crate::document::AcquiredNodes;
4use crate::element::{set_attribute, ElementData, ElementTrait};
5use crate::node::{CascadedValues, Node, NodeBorrow};
6use crate::parsers::ParseValue;
7use crate::properties::ColorInterpolationFilters;
8use crate::rect::IRect;
9use crate::session::Session;
10use crate::surface_utils::shared_surface::{Operator, SharedImageSurface, SurfaceType};
11use crate::xml::Attributes;
12
13use super::bounds::BoundsBuilder;
14use super::context::{FilterContext, FilterOutput};
15use super::{
16 FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
17 PrimitiveParams, ResolvedPrimitive,
18};
19
20pub struct FeMerge {
22 base: Primitive,
23}
24
25#[derive(Clone, Default)]
27pub struct FeMergeNode {
28 in1: Input,
29}
30
31pub struct Merge {
33 pub merge_nodes: Vec<MergeNode>,
34}
35
36#[derive(Debug, Default, PartialEq)]
38pub struct MergeNode {
39 pub in1: Input,
40 pub color_interpolation_filters: ColorInterpolationFilters,
41}
42
43impl Default for FeMerge {
44 #[inline]
46 fn default() -> FeMerge {
47 FeMerge {
48 base: Default::default(),
49 }
50 }
51}
52
53impl ElementTrait for FeMerge {
54 fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
55 self.base.parse_no_inputs(attrs, session);
56 }
57}
58
59impl ElementTrait for FeMergeNode {
60 fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
61 for (attr, value) in attrs.iter() {
62 if let expanded_name!("", "in") = attr.expanded() {
63 set_attribute(&mut self.in1, attr.parse(value), session);
64 }
65 }
66 }
67}
68
69impl MergeNode {
70 fn render(
71 &self,
72 ctx: &FilterContext,
73 bounds: IRect,
74 output_surface: Option<SharedImageSurface>,
75 ) -> Result<SharedImageSurface, FilterError> {
76 let input = ctx.get_input(&self.in1, self.color_interpolation_filters)?;
77
78 if output_surface.is_none() {
79 return Ok(input.surface().clone());
80 }
81
82 input
83 .surface()
84 .compose(&output_surface.unwrap(), bounds, Operator::Over)
85 .map_err(FilterError::CairoError)
86 }
87}
88
89impl Merge {
90 pub fn render(
91 &self,
92 bounds_builder: BoundsBuilder,
93 ctx: &FilterContext,
94 ) -> Result<FilterOutput, FilterError> {
95 let mut bounds_builder = bounds_builder;
97 for merge_node in &self.merge_nodes {
98 let input = ctx.get_input(&merge_node.in1, merge_node.color_interpolation_filters)?;
99 bounds_builder = bounds_builder.add_input(&input);
100 }
101
102 let bounds: IRect = bounds_builder.compute(ctx).clipped.into();
103
104 let mut output_surface = None;
106 for merge_node in &self.merge_nodes {
107 output_surface = merge_node.render(ctx, bounds, output_surface).ok();
108 }
109
110 let surface = match output_surface {
111 Some(s) => s,
112 None => SharedImageSurface::empty(
113 ctx.source_graphic().width(),
114 ctx.source_graphic().height(),
115 SurfaceType::AlphaOnly,
116 )?,
117 };
118
119 Ok(FilterOutput { surface, bounds })
120 }
121
122 pub fn get_input_requirements(&self) -> InputRequirements {
123 self.merge_nodes
124 .iter()
125 .map(|mn| mn.in1.get_requirements())
126 .fold(InputRequirements::default(), |a, b| a.fold(b))
127 }
128}
129
130impl FilterEffect for FeMerge {
131 fn resolve(
132 &self,
133 _acquired_nodes: &mut AcquiredNodes<'_>,
134 node: &Node,
135 ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
136 Ok(vec![ResolvedPrimitive {
137 primitive: self.base.clone(),
138 params: PrimitiveParams::Merge(Merge {
139 merge_nodes: resolve_merge_nodes(node)?,
140 }),
141 }])
142 }
143}
144
145fn resolve_merge_nodes(node: &Node) -> Result<Vec<MergeNode>, FilterResolveError> {
147 let mut merge_nodes = Vec::new();
148
149 for child in node.children().filter(|c| c.is_element()) {
150 let cascaded = CascadedValues::new_from_node(&child);
151 let values = cascaded.get();
152
153 if let ElementData::FeMergeNode(merge_node) = &*child.borrow_element_data() {
154 merge_nodes.push(MergeNode {
155 in1: merge_node.in1.clone(),
156 color_interpolation_filters: values.color_interpolation_filters(),
157 });
158 }
159 }
160
161 Ok(merge_nodes)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 use crate::borrow_element_as;
169 use crate::document::Document;
170
171 #[test]
172 fn extracts_parameters() {
173 let document = Document::load_from_bytes(
174 br#"<?xml version="1.0" encoding="UTF-8"?>
175<svg xmlns="http://www.w3.org/2000/svg">
176 <filter id="filter">
177 <feMerge id="merge">
178 <feMergeNode in="SourceGraphic"/>
179 <feMergeNode in="SourceAlpha" color-interpolation-filters="sRGB"/>
180 </feMerge>
181 </filter>
182</svg>
183"#,
184 );
185 let mut acquired_nodes = AcquiredNodes::new(&document, None::<gio::Cancellable>);
186
187 let node = document.lookup_internal_node("merge").unwrap();
188 let merge = borrow_element_as!(node, FeMerge);
189 let resolved = merge.resolve(&mut acquired_nodes, &node).unwrap();
190 let ResolvedPrimitive { params, .. } = resolved.first().unwrap();
191 let params = match params {
192 PrimitiveParams::Merge(m) => m,
193 _ => unreachable!(),
194 };
195 assert_eq!(
196 ¶ms.merge_nodes[..],
197 vec![
198 MergeNode {
199 in1: Input::SourceGraphic,
200 color_interpolation_filters: Default::default(),
201 },
202 MergeNode {
203 in1: Input::SourceAlpha,
204 color_interpolation_filters: ColorInterpolationFilters::Srgb,
205 },
206 ]
207 );
208 }
209}