use crate::{
filters::{
FilterRegion,
HasFilterRegion,
primitives::PrimitiveBuilder,
},
paint::ResourceIri,
primitives::{
ColorInterpolation,
FilterInput,
},
utils::ElementWriter,
};
use smart_default::SmartDefault;
use std::fmt::{
Display,
Formatter,
};
#[derive(Debug, Hash, Eq, PartialEq, Clone, SmartDefault)]
pub struct Merge {
inputs: Vec<FilterInput>,
region: FilterRegion,
#[default(ColorInterpolation::LinearRgb)]
color_interpolation: ColorInterpolation,
}
impl Merge {
pub(crate) fn new() -> Self {
Merge::default()
}
}
impl HasFilterRegion for Merge {
fn region_mut(&mut self) -> &mut FilterRegion {
&mut self.region
}
}
impl ResourceIri for Merge {}
impl Display for Merge {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.inputs.is_empty() {
return Ok(());
}
ElementWriter::new(f, "feMerge")?
.write(|out| self.region.fmt(out))?
.attr_if(
"color-interpolation-filters",
(&self.color_interpolation,),
self.color_interpolation != ColorInterpolation::LinearRgb,
)?
.attr("result", (self.iri(),))?
.content(|out| {
self.inputs.iter().try_for_each(|node| {
ElementWriter::new(out, "feMergeNode")?
.attr("in", (node,))?
.close()
})
})?
.close()
}
}
impl<'a> PrimitiveBuilder<'a, Merge> {
pub fn input<T>(mut self, input: T) -> Self
where
T: Into<FilterInput>,
{
self.inner.inputs.push(input.into());
self
}
pub fn inputs<I, T>(mut self, inputs: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<FilterInput>,
{
self.inner.inputs.extend(inputs.into_iter().map(Into::into));
self
}
pub fn color_interpolation(mut self, value: ColorInterpolation) -> Self {
self.inner.color_interpolation = value;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
filters::{
FilterContext,
FilterRegionConfig,
},
test_utils::assert_xml,
};
#[test]
fn renders_with_filter_region() {
let ctx = FilterContext::default();
let input = FilterInput::source_alpha();
ctx.merge()
.input(input)
.x(0.5)
.y(0.6)
.width(110)
.height(120)
.finish();
let node = &ctx.into_primitives()[0];
assert_xml(
node.to_string(),
format!(
r#"
<feMerge x="0.5" y="0.6" width="110" height="120" result="{}">
<feMergeNode in="{input}" />
</feMerge>
"#,
node.iri()
),
);
}
#[test]
fn does_not_render_when_empty() {
let ctx = FilterContext::default();
ctx.merge().finish();
let node = &ctx.into_primitives()[0];
assert!(node.to_string().is_empty());
}
#[test]
fn renders_with_single_input() {
let ctx = FilterContext::default();
let input = FilterInput::source_graphic();
ctx.merge().input(input).finish();
let node = &ctx.into_primitives()[0];
assert_xml(
node.to_string(),
format!(
r#"
<feMerge result="{}">
<feMergeNode in="{input}" />
</feMerge>
"#,
node.iri()
),
);
}
#[test]
fn renders_with_multiple_inputs() {
let ctx = FilterContext::default();
ctx.merge()
.input(FilterInput::source_graphic())
.input(FilterInput::source_alpha())
.inputs([FilterInput::source_alpha(), FilterInput::source_graphic()])
.finish();
let node = &ctx.into_primitives()[0];
assert_xml(
node.to_string(),
format!(
r#"
<feMerge result="{}">
<feMergeNode in="{}" />
<feMergeNode in="{}" />
<feMergeNode in="{}" />
<feMergeNode in="{}" />
</feMerge>
"#,
node.iri(),
FilterInput::source_graphic(),
FilterInput::source_alpha(),
FilterInput::source_alpha(),
FilterInput::source_graphic(),
),
);
}
#[test]
fn renders_with_attrs() {
let ctx = FilterContext::default();
let input = FilterInput::source_alpha();
let color_interpolation = ColorInterpolation::SRgb;
ctx.merge()
.input(input)
.color_interpolation(color_interpolation)
.finish();
let node = &ctx.into_primitives()[0];
assert_xml(
node.to_string(),
format!(
r#"
<feMerge color-interpolation-filters="{color_interpolation}" result="{}">
<feMergeNode in="{input}" />
</feMerge>
"#,
node.iri()
),
);
}
}