rsvg/filters/
tile.rs

1use crate::document::AcquiredNodes;
2use crate::element::ElementTrait;
3use crate::node::Node;
4use crate::properties::ColorInterpolationFilters;
5use crate::rect::IRect;
6use crate::rsvg_log;
7use crate::session::Session;
8use crate::xml::Attributes;
9
10use super::bounds::BoundsBuilder;
11use super::context::{FilterContext, FilterInput, FilterOutput};
12use super::{
13    FilterEffect, FilterError, FilterResolveError, Input, InputRequirements, Primitive,
14    PrimitiveParams, ResolvedPrimitive,
15};
16
17/// The `feTile` filter primitive.
18#[derive(Default)]
19pub struct FeTile {
20    base: Primitive,
21    params: Tile,
22}
23
24/// Resolved `feTile` primitive for rendering.
25#[derive(Clone, Default)]
26pub struct Tile {
27    in1: Input,
28}
29
30impl ElementTrait for FeTile {
31    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
32        self.params.in1 = self.base.parse_one_input(attrs, session);
33    }
34}
35
36impl Tile {
37    pub fn render(
38        &self,
39        bounds_builder: BoundsBuilder,
40        ctx: &FilterContext,
41    ) -> Result<FilterOutput, FilterError> {
42        // https://www.w3.org/TR/filter-effects/#ColorInterpolationFiltersProperty
43        //
44        // "Note: The color-interpolation-filters property just has an
45        // effect on filter operations. Therefore, it has no effect on
46        // filter primitives like [...], feTile"
47        //
48        // This is why we pass Auto here.
49        let input_1 = ctx.get_input(&self.in1, ColorInterpolationFilters::Auto)?;
50
51        // feTile doesn't consider its inputs in the filter primitive subregion calculation.
52        let bounds: IRect = bounds_builder.compute(ctx).clipped.into();
53
54        let surface = match input_1 {
55            FilterInput::StandardInput(input_surface) => input_surface,
56            FilterInput::PrimitiveOutput(FilterOutput {
57                surface: input_surface,
58                bounds: input_bounds,
59            }) => {
60                if input_bounds.is_empty() {
61                    rsvg_log!(
62                        ctx.session(),
63                        "(feTile with empty input_bounds; returning just the input surface)"
64                    );
65
66                    input_surface
67                } else {
68                    rsvg_log!(
69                        ctx.session(),
70                        "(feTile bounds={:?}, input_bounds={:?})",
71                        bounds,
72                        input_bounds
73                    );
74
75                    let tile_surface = input_surface.tile(input_bounds)?;
76
77                    ctx.source_graphic().paint_image_tiled(
78                        bounds,
79                        &tile_surface,
80                        input_bounds.x0,
81                        input_bounds.y0,
82                    )?
83                }
84            }
85        };
86
87        Ok(FilterOutput { surface, bounds })
88    }
89
90    pub fn get_input_requirements(&self) -> InputRequirements {
91        self.in1.get_requirements()
92    }
93}
94
95impl FilterEffect for FeTile {
96    fn resolve(
97        &self,
98        _acquired_nodes: &mut AcquiredNodes<'_>,
99        _node: &Node,
100    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
101        Ok(vec![ResolvedPrimitive {
102            primitive: self.base.clone(),
103            params: PrimitiveParams::Tile(self.params.clone()),
104        }])
105    }
106}