rsvg/
gradient.rs

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