rsvg/
gradient.rs

1//! Gradient paint servers; the `linearGradient` and `radialGradient` elements.
2
3use cssparser::Parser;
4use markup5ever::{ExpandedName, LocalName, Namespace, expanded_name, local_name, ns};
5
6use crate::color::{Color, resolve_color};
7use crate::coord_units;
8use crate::coord_units::CoordUnits;
9use crate::document::{AcquiredNodes, NodeId, NodeStack};
10use crate::drawing_ctx::Viewport;
11use crate::element::{ElementData, ElementTrait, set_attribute};
12use crate::error::*;
13use crate::href::{is_href, set_href};
14use crate::length::*;
15use crate::node::{CascadedValues, Node, NodeBorrow};
16use crate::parse_identifiers;
17use crate::parsers::{Parse, ParseValue};
18use crate::rect::{Rect, rect_to_transform};
19use crate::rsvg_log;
20use crate::session::Session;
21use crate::transform::{Transform, TransformAttribute};
22use crate::unit_interval::UnitInterval;
23use crate::xml::Attributes;
24
25/// Contents of a `<stop>` element for gradient color stops
26#[derive(Copy, Clone)]
27pub struct ColorStop {
28    /// `<stop offset="..."/>`
29    pub offset: UnitInterval,
30
31    /// `<stop stop-color="..." stop-opacity="..."/>`
32    pub color: Color,
33}
34
35// gradientUnits attribute; its default is objectBoundingBox
36coord_units!(GradientUnits, CoordUnits::ObjectBoundingBox);
37
38/// spreadMethod attribute for gradients
39#[derive(Debug, Default, Copy, Clone, PartialEq)]
40pub enum SpreadMethod {
41    #[default]
42    Pad,
43    Reflect,
44    Repeat,
45}
46
47impl Parse for SpreadMethod {
48    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<SpreadMethod, ParseError<'i>> {
49        Ok(parse_identifiers!(
50            parser,
51            "pad" => SpreadMethod::Pad,
52            "reflect" => SpreadMethod::Reflect,
53            "repeat" => SpreadMethod::Repeat,
54        )?)
55    }
56}
57
58/// Node for the `<stop>` element
59#[derive(Default)]
60pub struct Stop {
61    /// `<stop offset="..."/>`
62    offset: UnitInterval,
63    /* stop-color and stop-opacity are not attributes; they are properties, so
64     * they go into property_defs.rs */
65}
66
67impl ElementTrait for Stop {
68    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
69        for (attr, value) in attrs.iter() {
70            if attr.expanded() == expanded_name!("", "offset") {
71                set_attribute(&mut self.offset, attr.parse(value), session);
72            }
73        }
74    }
75}
76
77/// Parameters specific to each gradient type, before being resolved.
78/// These will be composed together with UnreseolvedVariant from fallback
79/// nodes (referenced with e.g. `<linearGradient xlink:href="#fallback">`) to form
80/// a final, resolved Variant.
81#[derive(Copy, Clone)]
82enum UnresolvedVariant {
83    Linear {
84        x1: Option<Length<Horizontal>>,
85        y1: Option<Length<Vertical>>,
86        x2: Option<Length<Horizontal>>,
87        y2: Option<Length<Vertical>>,
88    },
89
90    Radial {
91        cx: Option<Length<Horizontal>>,
92        cy: Option<Length<Vertical>>,
93        r: Option<Length<Both>>,
94        fx: Option<Length<Horizontal>>,
95        fy: Option<Length<Vertical>>,
96        fr: Option<Length<Both>>,
97    },
98}
99
100/// Parameters specific to each gradient type, after resolving.
101#[derive(Clone)]
102enum ResolvedGradientVariant {
103    Linear {
104        x1: Length<Horizontal>,
105        y1: Length<Vertical>,
106        x2: Length<Horizontal>,
107        y2: Length<Vertical>,
108    },
109
110    Radial {
111        cx: Length<Horizontal>,
112        cy: Length<Vertical>,
113        r: Length<Both>,
114        fx: Length<Horizontal>,
115        fy: Length<Vertical>,
116        fr: Length<Both>,
117    },
118}
119
120/// Parameters specific to each gradient type, after normalizing to user-space units.
121pub enum GradientVariant {
122    Linear {
123        x1: f64,
124        y1: f64,
125        x2: f64,
126        y2: f64,
127    },
128
129    Radial {
130        cx: f64,
131        cy: f64,
132        r: f64,
133        fx: f64,
134        fy: f64,
135        fr: f64,
136    },
137}
138
139impl UnresolvedVariant {
140    fn into_resolved(self) -> ResolvedGradientVariant {
141        assert!(self.is_resolved());
142
143        match self {
144            UnresolvedVariant::Linear { x1, y1, x2, y2 } => ResolvedGradientVariant::Linear {
145                x1: x1.unwrap(),
146                y1: y1.unwrap(),
147                x2: x2.unwrap(),
148                y2: y2.unwrap(),
149            },
150
151            UnresolvedVariant::Radial {
152                cx,
153                cy,
154                r,
155                fx,
156                fy,
157                fr,
158            } => ResolvedGradientVariant::Radial {
159                cx: cx.unwrap(),
160                cy: cy.unwrap(),
161                r: r.unwrap(),
162                fx: fx.unwrap(),
163                fy: fy.unwrap(),
164                fr: fr.unwrap(),
165            },
166        }
167    }
168
169    fn is_resolved(&self) -> bool {
170        match *self {
171            UnresolvedVariant::Linear { x1, y1, x2, y2 } => {
172                x1.is_some() && y1.is_some() && x2.is_some() && y2.is_some()
173            }
174
175            UnresolvedVariant::Radial {
176                cx,
177                cy,
178                r,
179                fx,
180                fy,
181                fr,
182            } => {
183                cx.is_some()
184                    && cy.is_some()
185                    && r.is_some()
186                    && fx.is_some()
187                    && fy.is_some()
188                    && fr.is_some()
189            }
190        }
191    }
192
193    fn resolve_from_fallback(&self, fallback: &UnresolvedVariant) -> UnresolvedVariant {
194        match (*self, *fallback) {
195            (
196                UnresolvedVariant::Linear { x1, y1, x2, y2 },
197                UnresolvedVariant::Linear {
198                    x1: fx1,
199                    y1: fy1,
200                    x2: fx2,
201                    y2: fy2,
202                },
203            ) => UnresolvedVariant::Linear {
204                x1: x1.or(fx1),
205                y1: y1.or(fy1),
206                x2: x2.or(fx2),
207                y2: y2.or(fy2),
208            },
209
210            (
211                UnresolvedVariant::Radial {
212                    cx,
213                    cy,
214                    r,
215                    fx,
216                    fy,
217                    fr,
218                },
219                UnresolvedVariant::Radial {
220                    cx: f_cx,
221                    cy: f_cy,
222                    r: f_r,
223                    fx: f_fx,
224                    fy: f_fy,
225                    fr: f_fr,
226                },
227            ) => UnresolvedVariant::Radial {
228                cx: cx.or(f_cx),
229                cy: cy.or(f_cy),
230                r: r.or(f_r),
231                fx: fx.or(f_fx),
232                fy: fy.or(f_fy),
233                fr: fr.or(f_fr),
234            },
235
236            _ => *self, // If variants are of different types, then nothing to resolve
237        }
238    }
239
240    // https://www.w3.org/TR/SVG/pservers.html#LinearGradients
241    // https://www.w3.org/TR/SVG/pservers.html#RadialGradients
242    fn resolve_from_defaults(&self) -> UnresolvedVariant {
243        match self {
244            UnresolvedVariant::Linear { x1, y1, x2, y2 } => UnresolvedVariant::Linear {
245                x1: x1.or_else(|| Some(Length::<Horizontal>::parse_str("0%").unwrap())),
246                y1: y1.or_else(|| Some(Length::<Vertical>::parse_str("0%").unwrap())),
247                x2: x2.or_else(|| Some(Length::<Horizontal>::parse_str("100%").unwrap())),
248                y2: y2.or_else(|| Some(Length::<Vertical>::parse_str("0%").unwrap())),
249            },
250
251            UnresolvedVariant::Radial {
252                cx,
253                cy,
254                r,
255                fx,
256                fy,
257                fr,
258            } => {
259                let cx = cx.or_else(|| Some(Length::<Horizontal>::parse_str("50%").unwrap()));
260                let cy = cy.or_else(|| Some(Length::<Vertical>::parse_str("50%").unwrap()));
261                let r = r.or_else(|| Some(Length::<Both>::parse_str("50%").unwrap()));
262
263                // fx and fy fall back to the presentational value of cx and cy
264                let fx = fx.or(cx);
265                let fy = fy.or(cy);
266                let fr = fr.or_else(|| Some(Length::<Both>::parse_str("0%").unwrap()));
267
268                UnresolvedVariant::Radial {
269                    cx,
270                    cy,
271                    r,
272                    fx,
273                    fy,
274                    fr,
275                }
276            }
277        }
278    }
279}
280
281/// Fields shared by all gradient nodes
282#[derive(Default)]
283struct Common {
284    units: Option<GradientUnits>,
285    transform: Option<TransformAttribute>,
286    spread: Option<SpreadMethod>,
287
288    fallback: Option<NodeId>,
289}
290
291/// Node for the `<linearGradient>` element
292#[derive(Default)]
293pub struct LinearGradient {
294    common: Common,
295
296    x1: Option<Length<Horizontal>>,
297    y1: Option<Length<Vertical>>,
298    x2: Option<Length<Horizontal>>,
299    y2: Option<Length<Vertical>>,
300}
301
302/// Node for the `<radialGradient>` element
303#[derive(Default)]
304pub struct RadialGradient {
305    common: Common,
306
307    cx: Option<Length<Horizontal>>,
308    cy: Option<Length<Vertical>>,
309    r: Option<Length<Both>>,
310    fx: Option<Length<Horizontal>>,
311    fy: Option<Length<Vertical>>,
312    fr: Option<Length<Both>>,
313}
314
315/// Main structure used during gradient resolution.  For unresolved
316/// gradients, we store all fields as `Option<T>` - if `None`, it means
317/// that the field is not specified; if `Some(T)`, it means that the
318/// field was specified.
319struct UnresolvedGradient {
320    units: Option<GradientUnits>,
321    transform: Option<TransformAttribute>,
322    spread: Option<SpreadMethod>,
323    stops: Option<Vec<ColorStop>>,
324
325    variant: UnresolvedVariant,
326}
327
328/// Resolved gradient; this is memoizable after the initial resolution.
329#[derive(Clone)]
330pub struct ResolvedGradient {
331    units: GradientUnits,
332    transform: TransformAttribute,
333    spread: SpreadMethod,
334    stops: Vec<ColorStop>,
335
336    variant: ResolvedGradientVariant,
337}
338
339/// Gradient normalized to user-space units.
340pub struct UserSpaceGradient {
341    pub transform: Transform,
342    pub spread: SpreadMethod,
343    pub stops: Vec<ColorStop>,
344
345    pub variant: GradientVariant,
346}
347
348impl UnresolvedGradient {
349    fn into_resolved(self) -> ResolvedGradient {
350        assert!(self.is_resolved());
351
352        let UnresolvedGradient {
353            units,
354            transform,
355            spread,
356            stops,
357            variant,
358        } = self;
359
360        match variant {
361            UnresolvedVariant::Linear { .. } => ResolvedGradient {
362                units: units.unwrap(),
363                transform: transform.unwrap(),
364                spread: spread.unwrap(),
365                stops: stops.unwrap(),
366
367                variant: variant.into_resolved(),
368            },
369
370            UnresolvedVariant::Radial { .. } => ResolvedGradient {
371                units: units.unwrap(),
372                transform: transform.unwrap(),
373                spread: spread.unwrap(),
374                stops: stops.unwrap(),
375
376                variant: variant.into_resolved(),
377            },
378        }
379    }
380
381    /// Helper for add_color_stops_from_node()
382    fn add_color_stop(&mut self, offset: UnitInterval, color: Color) {
383        if self.stops.is_none() {
384            self.stops = Some(Vec::<ColorStop>::new());
385        }
386
387        if let Some(ref mut stops) = self.stops {
388            let last_offset = if !stops.is_empty() {
389                stops[stops.len() - 1].offset
390            } else {
391                UnitInterval(0.0)
392            };
393
394            let offset = if offset > last_offset {
395                offset
396            } else {
397                last_offset
398            };
399
400            stops.push(ColorStop { offset, color });
401        } else {
402            unreachable!();
403        }
404    }
405
406    /// Looks for `<stop>` children inside a linearGradient or radialGradient node,
407    /// and adds their info to the UnresolvedGradient &self.
408    fn add_color_stops_from_node(&mut self, node: &Node, opacity: UnitInterval) {
409        assert!(matches!(
410            *node.borrow_element_data(),
411            ElementData::LinearGradient(_) | ElementData::RadialGradient(_)
412        ));
413
414        for child in node.children().filter(|c| c.is_element()) {
415            if let ElementData::Stop(stop) = &*child.borrow_element_data() {
416                let cascaded = CascadedValues::new_from_node(&child);
417                let values = cascaded.get();
418
419                let UnitInterval(stop_opacity) = values.stop_opacity().0;
420                let UnitInterval(o) = opacity;
421
422                let composed_opacity = UnitInterval(stop_opacity * o);
423
424                let stop_color =
425                    resolve_color(&values.stop_color().0, composed_opacity, &values.color().0);
426
427                self.add_color_stop(stop.offset, stop_color);
428            }
429        }
430    }
431
432    fn is_resolved(&self) -> bool {
433        self.units.is_some()
434            && self.transform.is_some()
435            && self.spread.is_some()
436            && self.stops.is_some()
437            && self.variant.is_resolved()
438    }
439
440    fn resolve_from_fallback(&self, fallback: &UnresolvedGradient) -> UnresolvedGradient {
441        let units = self.units.or(fallback.units);
442        let transform = self.transform.or(fallback.transform);
443        let spread = self.spread.or(fallback.spread);
444        let stops = self.stops.clone().or_else(|| fallback.stops.clone());
445        let variant = self.variant.resolve_from_fallback(&fallback.variant);
446
447        UnresolvedGradient {
448            units,
449            transform,
450            spread,
451            stops,
452            variant,
453        }
454    }
455
456    fn resolve_from_defaults(&self) -> UnresolvedGradient {
457        let units = self.units.or_else(|| Some(GradientUnits::default()));
458        let transform = self
459            .transform
460            .or_else(|| Some(TransformAttribute::default()));
461        let spread = self.spread.or_else(|| Some(SpreadMethod::default()));
462        let stops = self.stops.clone().or_else(|| Some(Vec::<ColorStop>::new()));
463        let variant = self.variant.resolve_from_defaults();
464
465        UnresolvedGradient {
466            units,
467            transform,
468            spread,
469            stops,
470            variant,
471        }
472    }
473}
474
475/// State used during the gradient resolution process
476///
477/// This is the current node's gradient information, plus the fallback
478/// that should be used in case that information is not complete for a
479/// resolved gradient yet.
480struct Unresolved {
481    gradient: UnresolvedGradient,
482    fallback: Option<NodeId>,
483}
484
485impl LinearGradient {
486    fn get_unresolved_variant(&self) -> UnresolvedVariant {
487        UnresolvedVariant::Linear {
488            x1: self.x1,
489            y1: self.y1,
490            x2: self.x2,
491            y2: self.y2,
492        }
493    }
494}
495
496impl RadialGradient {
497    fn get_unresolved_variant(&self) -> UnresolvedVariant {
498        UnresolvedVariant::Radial {
499            cx: self.cx,
500            cy: self.cy,
501            r: self.r,
502            fx: self.fx,
503            fy: self.fy,
504            fr: self.fr,
505        }
506    }
507}
508
509impl Common {
510    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
511        for (attr, value) in attrs.iter() {
512            match attr.expanded() {
513                expanded_name!("", "gradientUnits") => {
514                    set_attribute(&mut self.units, attr.parse(value), session)
515                }
516                expanded_name!("", "gradientTransform") => {
517                    set_attribute(&mut self.transform, attr.parse(value), session);
518                }
519                expanded_name!("", "spreadMethod") => {
520                    set_attribute(&mut self.spread, attr.parse(value), session)
521                }
522                ref a if is_href(a) => {
523                    let mut href = None;
524                    set_attribute(
525                        &mut href,
526                        NodeId::parse(value).map(Some).attribute(attr.clone()),
527                        session,
528                    );
529                    set_href(a, &mut self.fallback, href);
530                }
531                _ => (),
532            }
533        }
534    }
535}
536
537impl ElementTrait for LinearGradient {
538    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
539        self.common.set_attributes(attrs, session);
540
541        for (attr, value) in attrs.iter() {
542            match attr.expanded() {
543                expanded_name!("", "x1") => set_attribute(&mut self.x1, attr.parse(value), session),
544                expanded_name!("", "y1") => set_attribute(&mut self.y1, attr.parse(value), session),
545                expanded_name!("", "x2") => set_attribute(&mut self.x2, attr.parse(value), session),
546                expanded_name!("", "y2") => set_attribute(&mut self.y2, attr.parse(value), session),
547
548                _ => (),
549            }
550        }
551    }
552}
553
554macro_rules! impl_gradient {
555    ($gradient_type:ident, $other_type:ident) => {
556        impl $gradient_type {
557            fn get_unresolved(&self, node: &Node, opacity: UnitInterval) -> Unresolved {
558                let mut gradient = UnresolvedGradient {
559                    units: self.common.units,
560                    transform: self.common.transform,
561                    spread: self.common.spread,
562                    stops: None,
563                    variant: self.get_unresolved_variant(),
564                };
565
566                gradient.add_color_stops_from_node(node, opacity);
567
568                Unresolved {
569                    gradient,
570                    fallback: self.common.fallback.clone(),
571                }
572            }
573
574            pub fn resolve(
575                &self,
576                node: &Node,
577                acquired_nodes: &mut AcquiredNodes<'_>,
578                opacity: UnitInterval,
579                session: &Session,
580            ) -> Result<ResolvedGradient, AcquireError> {
581                let Unresolved {
582                    mut gradient,
583                    mut fallback,
584                } = self.get_unresolved(node, opacity);
585
586                let mut stack = NodeStack::new();
587
588                while !gradient.is_resolved() {
589                    if let Some(node_id) = fallback {
590                        let node_name = format!("{node}");
591                        let acquired = acquired_nodes.acquire(&node_name, &node_id)?;
592                        let acquired_node = acquired.get();
593
594                        if stack.contains(acquired_node) {
595                            return Err(AcquireError::CircularReference(acquired_node.clone()));
596                        }
597
598                        let unresolved = match *acquired_node.borrow_element_data() {
599                            ElementData::$gradient_type(ref g) => {
600                                g.get_unresolved(&acquired_node, opacity)
601                            }
602                            ElementData::$other_type(ref g) => {
603                                g.get_unresolved(&acquired_node, opacity)
604                            }
605                            _ => {
606                                rsvg_log!(session, "{acquired_node} is not a gradient; ignoring");
607                                return Err(AcquireError::InvalidLinkType(node_id.clone()));
608                            }
609                        };
610
611                        gradient = gradient.resolve_from_fallback(&unresolved.gradient);
612                        fallback = unresolved.fallback;
613
614                        stack.push(acquired_node);
615                    } else {
616                        gradient = gradient.resolve_from_defaults();
617                        break;
618                    }
619                }
620
621                Ok(gradient.into_resolved())
622            }
623        }
624    };
625}
626
627impl_gradient!(LinearGradient, RadialGradient);
628impl_gradient!(RadialGradient, LinearGradient);
629
630impl ElementTrait for RadialGradient {
631    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
632        self.common.set_attributes(attrs, session);
633
634        // Create a local expanded name for "fr" because markup5ever doesn't have built-in
635        let expanded_name_fr = ExpandedName {
636            ns: &Namespace::from(""),
637            local: &LocalName::from("fr"),
638        };
639
640        for (attr, value) in attrs.iter() {
641            let attr_expanded = attr.expanded();
642            match attr_expanded {
643                expanded_name!("", "cx") => set_attribute(&mut self.cx, attr.parse(value), session),
644                expanded_name!("", "cy") => set_attribute(&mut self.cy, attr.parse(value), session),
645                expanded_name!("", "r") => set_attribute(&mut self.r, attr.parse(value), session),
646                expanded_name!("", "fx") => set_attribute(&mut self.fx, attr.parse(value), session),
647                expanded_name!("", "fy") => set_attribute(&mut self.fy, attr.parse(value), session),
648                a if a == expanded_name_fr => {
649                    set_attribute(&mut self.fr, attr.parse(value), session)
650                }
651
652                _ => (),
653            }
654        }
655    }
656}
657
658impl ResolvedGradient {
659    pub fn to_user_space(
660        &self,
661        object_bbox: &Option<Rect>,
662        viewport: &Viewport,
663        values: &NormalizeValues,
664    ) -> Option<UserSpaceGradient> {
665        let units = self.units.0;
666        let transform = rect_to_transform(object_bbox, units).ok()?;
667        let viewport = viewport.with_units(units);
668        let params = NormalizeParams::from_values(values, &viewport);
669
670        let gradient_transform = self.transform.to_transform();
671        let transform = transform.pre_transform(&gradient_transform).invert()?;
672
673        let variant = match self.variant {
674            ResolvedGradientVariant::Linear { x1, y1, x2, y2 } => GradientVariant::Linear {
675                x1: x1.to_user(&params),
676                y1: y1.to_user(&params),
677                x2: x2.to_user(&params),
678                y2: y2.to_user(&params),
679            },
680
681            ResolvedGradientVariant::Radial {
682                cx,
683                cy,
684                r,
685                fx,
686                fy,
687                fr,
688            } => GradientVariant::Radial {
689                cx: cx.to_user(&params),
690                cy: cy.to_user(&params),
691                r: r.to_user(&params),
692                fx: fx.to_user(&params),
693                fy: fy.to_user(&params),
694                fr: fr.to_user(&params),
695            },
696        };
697
698        Some(UserSpaceGradient {
699            transform,
700            spread: self.spread,
701            stops: self.stops.clone(),
702            variant,
703        })
704    }
705}
706
707#[cfg(test)]
708mod tests {
709    use super::*;
710
711    use markup5ever::{QualName, ns};
712
713    use crate::borrow_element_as;
714    use crate::node::{Node, NodeData};
715
716    #[test]
717    fn parses_spread_method() {
718        assert_eq!(SpreadMethod::parse_str("pad").unwrap(), SpreadMethod::Pad);
719        assert_eq!(
720            SpreadMethod::parse_str("reflect").unwrap(),
721            SpreadMethod::Reflect
722        );
723        assert_eq!(
724            SpreadMethod::parse_str("repeat").unwrap(),
725            SpreadMethod::Repeat
726        );
727        assert!(SpreadMethod::parse_str("foobar").is_err());
728    }
729
730    #[test]
731    fn gradient_resolved_from_defaults_is_really_resolved() {
732        let session = Session::default();
733
734        let node = Node::new(NodeData::new_element(
735            &session,
736            &QualName::new(None, ns!(svg), local_name!("linearGradient")),
737            Attributes::new(),
738        ));
739
740        let unresolved = borrow_element_as!(node, LinearGradient)
741            .get_unresolved(&node, UnitInterval::clamp(1.0));
742        let gradient = unresolved.gradient.resolve_from_defaults();
743        assert!(gradient.is_resolved());
744
745        let node = Node::new(NodeData::new_element(
746            &session,
747            &QualName::new(None, ns!(svg), local_name!("radialGradient")),
748            Attributes::new(),
749        ));
750
751        let unresolved = borrow_element_as!(node, RadialGradient)
752            .get_unresolved(&node, UnitInterval::clamp(1.0));
753        let gradient = unresolved.gradient.resolve_from_defaults();
754        assert!(gradient.is_resolved());
755    }
756}