rsvg/filters/
offset.rs

1use markup5ever::{expanded_name, local_name, ns};
2
3use crate::document::AcquiredNodes;
4use crate::element::{set_attribute, ElementTrait};
5use crate::node::Node;
6use crate::parsers::ParseValue;
7use crate::properties::ColorInterpolationFilters;
8use crate::rect::IRect;
9use crate::rsvg_log;
10use crate::session::Session;
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
20/// The `feOffset` filter primitive.
21#[derive(Default)]
22pub struct FeOffset {
23    base: Primitive,
24    params: Offset,
25}
26
27/// Resolved `feOffset` primitive for rendering.
28#[derive(Clone, Default)]
29pub struct Offset {
30    pub in1: Input,
31    pub dx: f64,
32    pub dy: f64,
33}
34
35impl ElementTrait for FeOffset {
36    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
37        self.params.in1 = self.base.parse_one_input(attrs, session);
38
39        for (attr, value) in attrs.iter() {
40            match attr.expanded() {
41                expanded_name!("", "dx") => {
42                    set_attribute(&mut self.params.dx, attr.parse(value), session)
43                }
44                expanded_name!("", "dy") => {
45                    set_attribute(&mut self.params.dy, attr.parse(value), session)
46                }
47                _ => (),
48            }
49        }
50    }
51}
52
53impl Offset {
54    pub fn render(
55        &self,
56        bounds_builder: BoundsBuilder,
57        ctx: &FilterContext,
58    ) -> Result<FilterOutput, FilterError> {
59        // https://www.w3.org/TR/filter-effects/#ColorInterpolationFiltersProperty
60        //
61        // "Note: The color-interpolation-filters property just has an
62        // effect on filter operations. Therefore, it has no effect on
63        // filter primitives like feOffset"
64        //
65        // This is why we pass Auto here.
66        let input_1 = ctx.get_input(&self.in1, ColorInterpolationFilters::Auto)?;
67        let bounds = bounds_builder.add_input(&input_1).compute(ctx).clipped;
68
69        rsvg_log!(ctx.session(), "(feOffset bounds={:?}", bounds);
70
71        let (dx, dy) = ctx.paffine().transform_distance(self.dx, self.dy);
72
73        let surface = input_1.surface().offset(bounds, dx, dy)?;
74
75        let ibounds: IRect = bounds.into();
76
77        Ok(FilterOutput {
78            surface,
79            bounds: ibounds,
80        })
81    }
82
83    pub fn get_input_requirements(&self) -> InputRequirements {
84        self.in1.get_requirements()
85    }
86}
87
88impl FilterEffect for FeOffset {
89    fn resolve(
90        &self,
91        _acquired_nodes: &mut AcquiredNodes<'_>,
92        _node: &Node,
93    ) -> Result<Vec<ResolvedPrimitive>, FilterResolveError> {
94        Ok(vec![ResolvedPrimitive {
95            primitive: self.base.clone(),
96            params: PrimitiveParams::Offset(self.params.clone()),
97        }])
98    }
99}