[librsvg: 1/6] paint_server: rework paint servers




commit baef3626354f197b2a0d297cb20a9e1c6b65bdb4
Author: Paolo Borelli <pborelli gnome org>
Date:   Fri Aug 28 22:34:25 2020 +0200

    paint_server: rework paint servers
    
    Simplify the code used to represent paint servers: instead of
    the PaintSource and AsPaintSource traits, use a PaintSource
    enum, which explicitely lists the possible sources.
    The PaintServer struct now has a resolve method that returns
    the corresponding PaintSource.
    Pattern and Gradient now do not need to implemnt those traits
    and also do not need to be aware of cairo, because the
    actual drawing code is moved to the drawing context that now
    knows how to render each paint source. (Note: some cairo
    code is still present in gradient until we rework how to
    distinguish linear and radial gradiants).

 rsvg_internals/src/drawing_ctx.rs     | 305 ++++++++++++++++++++++++-------
 rsvg_internals/src/filters/context.rs |   4 +
 rsvg_internals/src/gradient.rs        | 270 ++++++++++------------------
 rsvg_internals/src/paint_server.rs    | 124 +++++++------
 rsvg_internals/src/pattern.rs         | 329 ++++++++++------------------------
 5 files changed, 502 insertions(+), 530 deletions(-)
---
diff --git a/rsvg_internals/src/drawing_ctx.rs b/rsvg_internals/src/drawing_ctx.rs
index 43fbb442..9b2d97f5 100644
--- a/rsvg_internals/src/drawing_ctx.rs
+++ b/rsvg_internals/src/drawing_ctx.rs
@@ -20,10 +20,12 @@ use crate::element::Element;
 use crate::error::{AcquireError, RenderingError};
 use crate::filters;
 use crate::float_eq_cairo::ApproxEqCairo;
+use crate::gradient::{Gradient, GradientUnits, SpreadMethod};
 use crate::marker;
 use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw};
 use crate::paint_server::{PaintServer, PaintSource};
 use crate::path_builder::*;
+use crate::pattern::{PatternContentUnits, PatternUnits, ResolvedPattern};
 use crate::properties::ComputedValues;
 use crate::property_defs::{
     ClipRule, FillRule, MixBlendMode, Opacity, Overflow, ShapeRendering, StrokeDasharray,
@@ -806,12 +808,214 @@ impl DrawingCtx {
         Ok(child_surface)
     }
 
+    fn set_gradient(
+        self: &mut DrawingCtx,
+        gradient: &Gradient,
+        _acquired_nodes: &mut AcquiredNodes,
+        opacity: UnitInterval,
+        values: &ComputedValues,
+        bbox: &BoundingBox,
+    ) -> Result<bool, RenderingError> {
+        let units = gradient.get_units();
+        let transform = if let Ok(t) = bbox.rect_to_transform(units.0) {
+            t
+        } else {
+            return Ok(false);
+        };
+
+        let params = if units == GradientUnits(CoordUnits::ObjectBoundingBox) {
+            self.push_view_box(1.0, 1.0)
+        } else {
+            self.get_view_params()
+        };
+
+        let g = gradient.to_cairo_gradient(values, &params);
+
+        let transform = transform.pre_transform(&gradient.get_transform());
+        if let Some(m) = transform.invert() {
+            g.set_matrix(m.into())
+        }
+
+        g.set_extend(cairo::Extend::from(gradient.get_spread()));
+
+        for stop in gradient.get_stops() {
+            let UnitInterval(stop_offset) = stop.offset;
+            let UnitInterval(o) = opacity;
+            let UnitInterval(stop_opacity) = stop.opacity;
+
+            g.add_color_stop_rgba(
+                stop_offset,
+                f64::from(stop.rgba.red_f32()),
+                f64::from(stop.rgba.green_f32()),
+                f64::from(stop.rgba.blue_f32()),
+                f64::from(stop.rgba.alpha_f32()) * stop_opacity * o,
+            );
+        }
+
+        let cr = self.get_cairo_context();
+        cr.set_source(&g);
+
+        Ok(true)
+    }
+
+    fn set_pattern(
+        &mut self,
+        pattern: &ResolvedPattern,
+        acquired_nodes: &mut AcquiredNodes,
+        opacity: UnitInterval,
+        values: &ComputedValues,
+        bbox: &BoundingBox,
+    ) -> Result<bool, RenderingError> {
+        let node = if let Some(n) = pattern.node_with_children() {
+            n
+        } else {
+            // This means we didn't find any children among the fallbacks,
+            // so there is nothing to render.
+            return Ok(false);
+        };
+
+        let units = pattern.get_units();
+        let content_units = pattern.get_content_units();
+        let pattern_transform = pattern.get_transform();
+
+        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
+            self.push_view_box(1.0, 1.0)
+        } else {
+            self.get_view_params()
+        };
+
+        let pattern_rect = pattern.get_rect(values, &params);
+
+        // Work out the size of the rectangle so it takes into account the object bounding box
+        let (bbwscale, bbhscale) = match units {
+            PatternUnits(CoordUnits::ObjectBoundingBox) => bbox.rect.unwrap().size(),
+            PatternUnits(CoordUnits::UserSpaceOnUse) => (1.0, 1.0),
+        };
+
+        let taffine = self.get_transform().pre_transform(&pattern_transform);
+
+        let mut scwscale = (taffine.xx.powi(2) + taffine.xy.powi(2)).sqrt();
+        let mut schscale = (taffine.yx.powi(2) + taffine.yy.powi(2)).sqrt();
+
+        let scaled_width = pattern_rect.width() * bbwscale;
+        let scaled_height = pattern_rect.height() * bbhscale;
+
+        let pw: i32 = (scaled_width * scwscale) as i32;
+        let ph: i32 = (scaled_height * schscale) as i32;
+
+        if scaled_width.abs() < f64::EPSILON
+            || scaled_height.abs() < f64::EPSILON
+            || pw < 1
+            || ph < 1
+        {
+            return Ok(false);
+        }
+
+        scwscale = f64::from(pw) / scaled_width;
+        schscale = f64::from(ph) / scaled_height;
+
+        // Create the pattern coordinate system
+        let mut affine = match units {
+            PatternUnits(CoordUnits::ObjectBoundingBox) => {
+                let bbrect = bbox.rect.unwrap();
+                Transform::new_translate(
+                    bbrect.x0 + pattern_rect.x0 * bbrect.width(),
+                    bbrect.y0 + pattern_rect.y0 * bbrect.height(),
+                )
+            }
+
+            PatternUnits(CoordUnits::UserSpaceOnUse) => {
+                Transform::new_translate(pattern_rect.x0, pattern_rect.y0)
+            }
+        };
+
+        // Apply the pattern transform
+        affine = affine.post_transform(&pattern_transform);
+
+        let mut caffine: Transform;
+
+        // Create the pattern contents coordinate system
+        let _params = if let Some(vbox) = pattern.get_vbox() {
+            // If there is a vbox, use that
+            let r = pattern
+                .get_preserve_aspect_ratio()
+                .compute(&vbox, &Rect::from_size(scaled_width, scaled_height));
+
+            let sw = r.width() / vbox.0.width();
+            let sh = r.height() / vbox.0.height();
+            let x = r.x0 - vbox.0.x0 * sw;
+            let y = r.y0 - vbox.0.y0 * sh;
+
+            caffine = Transform::new_scale(sw, sh).pre_translate(x, y);
+
+            self.push_view_box(vbox.0.width(), vbox.0.height())
+        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
+            // If coords are in terms of the bounding box, use them
+            let (bbw, bbh) = bbox.rect.unwrap().size();
+
+            caffine = Transform::new_scale(bbw, bbh);
+
+            self.push_view_box(1.0, 1.0)
+        } else {
+            caffine = Transform::identity();
+            self.get_view_params()
+        };
+
+        if !scwscale.approx_eq_cairo(1.0) || !schscale.approx_eq_cairo(1.0) {
+            caffine = caffine.post_scale(scwscale, schscale);
+            affine = affine.pre_scale(1.0 / scwscale, 1.0 / schscale);
+        }
+
+        // Draw to another surface
+
+        let cr_save = self.get_cairo_context();
+
+        let surface = cr_save
+            .get_target()
+            .create_similar(cairo::Content::ColorAlpha, pw, ph)?;
+
+        let cr_pattern = cairo::Context::new(&surface);
+
+        self.set_cairo_context(&cr_pattern);
+
+        // Set up transformations to be determined by the contents units
+        cr_pattern.set_matrix(caffine.into());
+
+        // Draw everything
+        let res = self.with_alpha(opacity, &mut |dc| {
+            let pattern_cascaded = CascadedValues::new_from_node(&node);
+            let pattern_values = pattern_cascaded.get();
+            dc.with_discrete_layer(
+                &node,
+                acquired_nodes,
+                pattern_values,
+                false,
+                &mut |an, dc| node.draw_children(an, &pattern_cascaded, dc, false),
+            )
+        });
+
+        // Return to the original coordinate system and rendering context
+        self.set_cairo_context(&cr_save);
+
+        // Set the final surface as a Cairo pattern into the Cairo context
+        let pattern = cairo::SurfacePattern::create(&surface);
+
+        if let Some(m) = affine.invert() {
+            pattern.set_matrix(m.into())
+        }
+        pattern.set_extend(cairo::Extend::Repeat);
+        pattern.set_filter(cairo::Filter::Best);
+        cr_save.set_source(&pattern);
+
+        res.map(|_| true)
+    }
+
     fn set_color(
         &self,
         color: cssparser::Color,
         opacity: UnitInterval,
         current_color: cssparser::RGBA,
-    ) {
+    ) -> Result<bool, RenderingError> {
         let rgba = match color {
             cssparser::Color::RGBA(rgba) => rgba,
             cssparser::Color::CurrentColor => current_color,
@@ -824,81 +1028,42 @@ impl DrawingCtx {
             f64::from(rgba.blue_f32()),
             f64::from(rgba.alpha_f32()) * o,
         );
+
+        Ok(true)
     }
 
     pub fn set_source_paint_server(
         &mut self,
         acquired_nodes: &mut AcquiredNodes,
-        ps: &PaintServer,
+        paint_server: &PaintServer,
         opacity: UnitInterval,
         bbox: &BoundingBox,
         current_color: cssparser::RGBA,
+        values: &ComputedValues,
     ) -> Result<bool, RenderingError> {
-        match *ps {
-            PaintServer::Iri {
-                ref iri,
-                ref alternate,
-            } => {
-                let mut had_paint_server = false;
-
-                match acquired_nodes.acquire(iri) {
-                    Ok(acquired) => {
-                        let node = acquired.get();
-
-                        assert!(node.is_element());
-
-                        had_paint_server = match *node.borrow_element() {
-                            Element::LinearGradient(ref g) => g.resolve_fallbacks_and_set_pattern(
-                                &node,
-                                acquired_nodes,
-                                self,
-                                opacity,
-                                bbox,
-                            )?,
-                            Element::RadialGradient(ref g) => g.resolve_fallbacks_and_set_pattern(
-                                &node,
-                                acquired_nodes,
-                                self,
-                                opacity,
-                                bbox,
-                            )?,
-                            Element::Pattern(ref p) => p.resolve_fallbacks_and_set_pattern(
-                                &node,
-                                acquired_nodes,
-                                self,
-                                opacity,
-                                bbox,
-                            )?,
-                            _ => false,
-                        }
-                    }
-
-                    Err(AcquireError::MaxReferencesExceeded) => {
-                        return Err(RenderingError::InstancingLimit);
-                    }
-
-                    Err(_) => (),
-                }
-
-                if !had_paint_server && alternate.is_some() {
-                    self.set_color(alternate.unwrap(), opacity, current_color);
-                    had_paint_server = true;
+        let paint_source = paint_server.resolve(acquired_nodes)?;
+
+        match paint_source {
+            PaintSource::Gradient(g, c) => {
+                if self.set_gradient(&g, acquired_nodes, opacity, values, bbox)? {
+                    Ok(true)
+                } else if let Some(c) = c {
+                    self.set_color(c, opacity, current_color)
                 } else {
-                    rsvg_log!(
-                        "pattern \"{}\" was not found and there was no fallback alternate",
-                        iri
-                    );
+                    Ok(false)
                 }
-
-                Ok(had_paint_server)
             }
-
-            PaintServer::SolidColor(color) => {
-                self.set_color(color, opacity, current_color);
-                Ok(true)
+            PaintSource::Pattern(p, c) => {
+                if self.set_pattern(&p, acquired_nodes, opacity, values, bbox)? {
+                    Ok(true)
+                } else if let Some(c) = c {
+                    self.set_color(c, opacity, current_color)
+                } else {
+                    Ok(false)
+                }
             }
-
-            PaintServer::None => Ok(false),
+            PaintSource::SolidColor(c) => self.set_color(c, opacity, current_color),
+            PaintSource::None => Ok(false),
         }
     }
 
@@ -952,6 +1117,7 @@ impl DrawingCtx {
                 values.fill_opacity().0,
                 &bbox,
                 current_color,
+                values,
             )
             .map(|had_paint_server| {
                 if had_paint_server {
@@ -969,6 +1135,7 @@ impl DrawingCtx {
                     values.stroke_opacity().0,
                     &bbox,
                     current_color,
+                    values,
                 )
                 .map(|had_paint_server| {
                     if had_paint_server {
@@ -1121,6 +1288,7 @@ impl DrawingCtx {
                     values.fill_opacity().0,
                     &bbox,
                     current_color,
+                    values,
                 )
                 .map(|had_paint_server| {
                     if had_paint_server {
@@ -1142,6 +1310,7 @@ impl DrawingCtx {
                         values.stroke_opacity().0,
                         &bbox,
                         current_color,
+                        values,
                     )
                     .map(|had_paint_server| {
                         if had_paint_server {
@@ -1576,6 +1745,16 @@ fn escape_link_target(value: &str) -> Cow<'_, str> {
     })
 }
 
+impl From<SpreadMethod> for cairo::Extend {
+    fn from(s: SpreadMethod) -> cairo::Extend {
+        match s {
+            SpreadMethod::Pad => cairo::Extend::Pad,
+            SpreadMethod::Reflect => cairo::Extend::Reflect,
+            SpreadMethod::Repeat => cairo::Extend::Repeat,
+        }
+    }
+}
+
 impl From<StrokeLinejoin> for cairo::LineJoin {
     fn from(j: StrokeLinejoin) -> cairo::LineJoin {
         match j {
diff --git a/rsvg_internals/src/filters/context.rs b/rsvg_internals/src/filters/context.rs
index b9440cf0..c9c6923a 100644
--- a/rsvg_internals/src/filters/context.rs
+++ b/rsvg_internals/src/filters/context.rs
@@ -271,6 +271,7 @@ impl FilterContext {
         acquired_nodes: &mut AcquiredNodes,
         paint_server: &PaintServer,
         opacity: UnitInterval,
+        values: &ComputedValues,
     ) -> Result<SharedImageSurface, cairo::Status> {
         let mut surface = ExclusiveImageSurface::new(
             self.source_surface.width(),
@@ -290,6 +291,7 @@ impl FilterContext {
                     opacity,
                     &self.node_bbox,
                     self.computed_from_node_being_filtered.color().0,
+                    values,
                 )
                 .map(|had_paint_server| {
                     if had_paint_server {
@@ -355,6 +357,7 @@ impl FilterContext {
                     acquired_nodes,
                     &values.fill().0,
                     values.fill_opacity().0,
+                    &values,
                 )
                 .map_err(FilterError::CairoError)
                 .map(FilterInput::StandardInput),
@@ -365,6 +368,7 @@ impl FilterContext {
                     acquired_nodes,
                     &values.stroke().0,
                     values.stroke_opacity().0,
+                    &values,
                 )
                 .map_err(FilterError::CairoError)
                 .map(FilterInput::StandardInput),
diff --git a/rsvg_internals/src/gradient.rs b/rsvg_internals/src/gradient.rs
index 854e7a0f..a066f0b5 100644
--- a/rsvg_internals/src/gradient.rs
+++ b/rsvg_internals/src/gradient.rs
@@ -8,16 +8,14 @@ use matches::matches;
 use std::cell::RefCell;
 
 use crate::allowed_url::Fragment;
-use crate::bbox::*;
 use crate::coord_units::CoordUnits;
 use crate::document::{AcquiredNodes, NodeStack};
-use crate::drawing_ctx::{DrawingCtx, ViewParams};
+use crate::drawing_ctx::ViewParams;
 use crate::element::{Draw, Element, ElementResult, SetAttributes};
 use crate::error::*;
 use crate::href::{is_href, set_href};
 use crate::length::*;
 use crate::node::{CascadedValues, Node, NodeBorrow};
-use crate::paint_server::{AsPaintSource, PaintSource};
 use crate::parsers::{Parse, ParseValue};
 use crate::properties::ComputedValues;
 use crate::property_bag::PropertyBag;
@@ -27,15 +25,15 @@ use crate::unit_interval::UnitInterval;
 
 /// Contents of a <stop> element for gradient color stops
 #[derive(Copy, Clone)]
-struct ColorStop {
+pub struct ColorStop {
     /// <stop offset="..."/>
-    offset: UnitInterval,
+    pub offset: UnitInterval,
 
     /// <stop stop-color="..."/>
-    rgba: cssparser::RGBA,
+    pub rgba: cssparser::RGBA,
 
     /// <stop stop-opacity="..."/>
-    opacity: UnitInterval,
+    pub opacity: UnitInterval,
 }
 
 // gradientUnits attribute; its default is objectBoundingBox
@@ -43,7 +41,7 @@ coord_units!(GradientUnits, CoordUnits::ObjectBoundingBox);
 
 /// spreadMethod attribute for gradients
 #[derive(Debug, Copy, Clone, PartialEq)]
-enum SpreadMethod {
+pub enum SpreadMethod {
     Pad,
     Reflect,
     Repeat,
@@ -66,16 +64,6 @@ impl Default for SpreadMethod {
     }
 }
 
-impl From<SpreadMethod> for cairo::Extend {
-    fn from(s: SpreadMethod) -> cairo::Extend {
-        match s {
-            SpreadMethod::Pad => cairo::Extend::Pad,
-            SpreadMethod::Reflect => cairo::Extend::Reflect,
-            SpreadMethod::Repeat => cairo::Extend::Repeat,
-        }
-    }
-}
-
 /// Node for the <stop> element
 #[derive(Default)]
 pub struct Stop {
@@ -296,44 +284,6 @@ impl UnresolvedVariant {
     }
 }
 
-impl Variant {
-    /// Creates a cairo::Gradient corresponding to the gradient type of the
-    /// &self Variant.  This does not have color stops set on it yet;
-    /// call Gradient.add_color_stops_to_pattern() afterwards.
-    fn to_cairo_gradient(&self, values: &ComputedValues, params: &ViewParams) -> cairo::Gradient {
-        match *self {
-            Variant::Linear { x1, y1, x2, y2 } => {
-                cairo::Gradient::clone(&cairo::LinearGradient::new(
-                    x1.normalize(values, params),
-                    y1.normalize(values, params),
-                    x2.normalize(values, params),
-                    y2.normalize(values, params),
-                ))
-            }
-
-            Variant::Radial {
-                cx,
-                cy,
-                r,
-                fx,
-                fy,
-                fr,
-            } => {
-                let n_cx = cx.normalize(values, params);
-                let n_cy = cy.normalize(values, params);
-                let n_r = r.normalize(values, params);
-                let n_fx = fx.normalize(values, params);
-                let n_fy = fy.normalize(values, params);
-                let n_fr = fr.normalize(values, params);
-
-                cairo::Gradient::clone(&cairo::RadialGradient::new(
-                    n_fx, n_fy, n_fr, n_cx, n_cy, n_r,
-                ))
-            }
-        }
-    }
-}
-
 /// Fields shared by all gradient nodes
 #[derive(Default)]
 struct Common {
@@ -563,31 +513,6 @@ impl RadialGradient {
     }
 }
 
-macro_rules! impl_get_unresolved {
-    ($gradient:ty) => {
-        impl $gradient {
-            fn get_unresolved(&self, node: &Node) -> Unresolved {
-                let mut gradient = UnresolvedGradient {
-                    units: self.common.units,
-                    transform: self.common.transform,
-                    spread: self.common.spread,
-                    stops: None,
-                    variant: self.get_unresolved_variant(),
-                };
-
-                gradient.add_color_stops_from_node(node);
-
-                Unresolved {
-                    gradient,
-                    fallback: self.common.fallback.clone(),
-                }
-            }
-        }
-    };
-}
-impl_get_unresolved!(LinearGradient);
-impl_get_unresolved!(RadialGradient);
-
 impl SetAttributes for Common {
     fn set_attributes(&mut self, pbag: &PropertyBag<'_>) -> ElementResult {
         for (attr, value) in pbag.iter() {
@@ -633,49 +558,31 @@ impl SetAttributes for LinearGradient {
 
 impl Draw for LinearGradient {}
 
-impl SetAttributes for RadialGradient {
-    fn set_attributes(&mut self, pbag: &PropertyBag<'_>) -> ElementResult {
-        self.common.set_attributes(pbag)?;
-        // Create a local expanded name for "fr" because markup5ever doesn't have built-in
-        let expanded_name_fr = ExpandedName {
-            ns: &Namespace::from(""),
-            local: &LocalName::from("fr"),
-        };
-
-        for (attr, value) in pbag.iter() {
-            let attr_expanded = attr.expanded();
+macro_rules! impl_gradient {
+    ($gradient_type:ident, $other_type:ident) => {
+        impl $gradient_type {
+            fn get_unresolved(&self, node: &Node) -> Unresolved {
+                let mut gradient = UnresolvedGradient {
+                    units: self.common.units,
+                    transform: self.common.transform,
+                    spread: self.common.spread,
+                    stops: None,
+                    variant: self.get_unresolved_variant(),
+                };
 
-            if attr_expanded == expanded_name_fr {
-                self.fr = Some(attr.parse(value)?);
-            } else {
-                match attr_expanded {
-                    expanded_name!("", "cx") => self.cx = Some(attr.parse(value)?),
-                    expanded_name!("", "cy") => self.cy = Some(attr.parse(value)?),
-                    expanded_name!("", "r") => self.r = Some(attr.parse(value)?),
-                    expanded_name!("", "fx") => self.fx = Some(attr.parse(value)?),
-                    expanded_name!("", "fy") => self.fy = Some(attr.parse(value)?),
+                gradient.add_color_stops_from_node(node);
 
-                    _ => (),
+                Unresolved {
+                    gradient,
+                    fallback: self.common.fallback.clone(),
                 }
             }
-        }
-
-        Ok(())
-    }
-}
-
-impl Draw for RadialGradient {}
 
-macro_rules! impl_paint_source {
-    ($gradient_type:ident, $other_type:ident) => {
-        impl PaintSource for $gradient_type {
-            type Resolved = Gradient;
-
-            fn resolve(
+            pub fn resolve(
                 &self,
                 node: &Node,
                 acquired_nodes: &mut AcquiredNodes,
-            ) -> Result<Self::Resolved, AcquireError> {
+            ) -> Result<Gradient, AcquireError> {
                 let mut resolved = self.common.resolved.borrow_mut();
                 if let Some(ref gradient) = *resolved {
                     return Ok(gradient.clone());
@@ -723,79 +630,92 @@ macro_rules! impl_paint_source {
     };
 }
 
-impl_paint_source!(LinearGradient, RadialGradient);
+impl_gradient!(LinearGradient, RadialGradient);
+impl_gradient!(RadialGradient, LinearGradient);
+
+impl SetAttributes for RadialGradient {
+    fn set_attributes(&mut self, pbag: &PropertyBag<'_>) -> ElementResult {
+        self.common.set_attributes(pbag)?;
+        // Create a local expanded name for "fr" because markup5ever doesn't have built-in
+        let expanded_name_fr = ExpandedName {
+            ns: &Namespace::from(""),
+            local: &LocalName::from("fr"),
+        };
 
-impl_paint_source!(RadialGradient, LinearGradient);
+        for (attr, value) in pbag.iter() {
+            let attr_expanded = attr.expanded();
 
-impl AsPaintSource for Gradient {
-    fn set_as_paint_source(
-        self,
-        _acquired_nodes: &mut AcquiredNodes,
-        values: &ComputedValues,
-        draw_ctx: &mut DrawingCtx,
-        opacity: UnitInterval,
-        bbox: &BoundingBox,
-    ) -> Result<bool, RenderingError> {
-        if let Ok(transform) = bbox.rect_to_transform(self.units.0) {
-            let params = if self.units == GradientUnits(CoordUnits::ObjectBoundingBox) {
-                draw_ctx.push_view_box(1.0, 1.0)
+            if attr_expanded == expanded_name_fr {
+                self.fr = Some(attr.parse(value)?);
             } else {
-                draw_ctx.get_view_params()
-            };
+                match attr_expanded {
+                    expanded_name!("", "cx") => self.cx = Some(attr.parse(value)?),
+                    expanded_name!("", "cy") => self.cy = Some(attr.parse(value)?),
+                    expanded_name!("", "r") => self.r = Some(attr.parse(value)?),
+                    expanded_name!("", "fx") => self.fx = Some(attr.parse(value)?),
+                    expanded_name!("", "fy") => self.fy = Some(attr.parse(value)?),
 
-            let p = match self.variant {
-                Variant::Linear { .. } => {
-                    let g = self.variant.to_cairo_gradient(values, &params);
-                    cairo::Gradient::clone(&g)
+                    _ => (),
                 }
+            }
+        }
 
-                Variant::Radial { .. } => {
-                    let g = self.variant.to_cairo_gradient(values, &params);
-                    cairo::Gradient::clone(&g)
-                }
-            };
+        Ok(())
+    }
+}
 
-            self.set_on_cairo_pattern(&p, &transform, opacity);
+impl Draw for RadialGradient {}
 
-            let cr = draw_ctx.get_cairo_context();
-            cr.set_source(&p);
+impl Gradient {
+    pub fn get_units(&self) -> GradientUnits {
+        self.units
+    }
 
-            Ok(true)
-        } else {
-            Ok(false)
-        }
+    pub fn get_spread(&self) -> SpreadMethod {
+        self.spread
     }
-}
 
-impl Gradient {
-    fn set_on_cairo_pattern(
-        &self,
-        pattern: &cairo::Gradient,
-        transform: &Transform,
-        opacity: UnitInterval,
-    ) {
-        let transform = transform.pre_transform(&self.transform);
+    pub fn get_transform(&self) -> Transform {
+        self.transform
+    }
 
-        if let Some(m) = transform.invert() {
-            pattern.set_matrix(m.into())
-        }
-        pattern.set_extend(cairo::Extend::from(self.spread));
-        self.add_color_stops_to_pattern(pattern, opacity);
+    pub fn get_stops(&self) -> &Vec<ColorStop> {
+        &self.stops
     }
 
-    fn add_color_stops_to_pattern(&self, pattern: &cairo::Gradient, opacity: UnitInterval) {
-        for stop in &self.stops {
-            let UnitInterval(stop_offset) = stop.offset;
-            let UnitInterval(o) = opacity;
-            let UnitInterval(stop_opacity) = stop.opacity;
-
-            pattern.add_color_stop_rgba(
-                stop_offset,
-                f64::from(stop.rgba.red_f32()),
-                f64::from(stop.rgba.green_f32()),
-                f64::from(stop.rgba.blue_f32()),
-                f64::from(stop.rgba.alpha_f32()) * stop_opacity * o,
-            );
+    /// Creates a cairo::Gradient corresponding to the gradient type of the
+    /// &self Variant.  This does not have color stops set on it yet;
+    /// call Gradient.add_color_stops_to_pattern() afterwards.
+    pub fn to_cairo_gradient(&self, values: &ComputedValues, params: &ViewParams) -> cairo::Gradient {
+        match self.variant {
+            Variant::Linear { x1, y1, x2, y2 } => {
+                cairo::Gradient::clone(&cairo::LinearGradient::new(
+                    x1.normalize(values, params),
+                    y1.normalize(values, params),
+                    x2.normalize(values, params),
+                    y2.normalize(values, params),
+                ))
+            }
+
+            Variant::Radial {
+                cx,
+                cy,
+                r,
+                fx,
+                fy,
+                fr,
+            } => {
+                let n_cx = cx.normalize(values, params);
+                let n_cy = cy.normalize(values, params);
+                let n_r = r.normalize(values, params);
+                let n_fx = fx.normalize(values, params);
+                let n_fy = fy.normalize(values, params);
+                let n_fr = fr.normalize(values, params);
+
+                cairo::Gradient::clone(&cairo::RadialGradient::new(
+                    n_fx, n_fy, n_fr, n_cx, n_cy, n_r,
+                ))
+            }
         }
     }
 }
diff --git a/rsvg_internals/src/paint_server.rs b/rsvg_internals/src/paint_server.rs
index b64e01c5..fce883aa 100644
--- a/rsvg_internals/src/paint_server.rs
+++ b/rsvg_internals/src/paint_server.rs
@@ -3,14 +3,13 @@
 use cssparser::Parser;
 
 use crate::allowed_url::Fragment;
-use crate::bbox::BoundingBox;
 use crate::document::AcquiredNodes;
-use crate::drawing_ctx::DrawingCtx;
+use crate::element::Element;
 use crate::error::*;
-use crate::node::{CascadedValues, Node};
+use crate::gradient::Gradient;
+use crate::node::NodeBorrow;
 use crate::parsers::Parse;
-use crate::properties::ComputedValues;
-use crate::unit_interval::UnitInterval;
+use crate::pattern::ResolvedPattern;
 
 #[derive(Debug, Clone, PartialEq)]
 pub enum PaintServer {
@@ -22,6 +21,13 @@ pub enum PaintServer {
     SolidColor(cssparser::Color),
 }
 
+pub enum PaintSource {
+    None,
+    Gradient(Gradient, Option<cssparser::Color>),
+    Pattern(ResolvedPattern, Option<cssparser::Color>),
+    SolidColor(cssparser::Color),
+}
+
 impl Parse for PaintServer {
     fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<PaintServer, ParseError<'i>> {
         if parser
@@ -57,61 +63,71 @@ impl Parse for PaintServer {
     }
 }
 
-pub trait PaintSource {
-    type Resolved: AsPaintSource;
-
-    fn resolve(
+impl PaintServer {
+    pub fn resolve(
         &self,
-        node: &Node,
         acquired_nodes: &mut AcquiredNodes,
-    ) -> Result<Self::Resolved, AcquireError>;
-
-    fn resolve_fallbacks_and_set_pattern(
-        &self,
-        node: &Node,
-        acquired_nodes: &mut AcquiredNodes,
-        draw_ctx: &mut DrawingCtx,
-        opacity: UnitInterval,
-        bbox: &BoundingBox,
-    ) -> Result<bool, RenderingError> {
-        match self.resolve(&node, acquired_nodes) {
-            Ok(resolved) => {
-                let cascaded = CascadedValues::new_from_node(node);
-                let values = cascaded.get();
-                resolved.set_as_paint_source(acquired_nodes, values, draw_ctx, opacity, bbox)
-            }
-
-            Err(AcquireError::CircularReference(node)) => {
-                rsvg_log!("circular reference in paint server {}", node);
-                Err(RenderingError::CircularReference)
-            }
-
-            Err(AcquireError::MaxReferencesExceeded) => {
-                rsvg_log!("maximum number of references exceeded");
-                Err(RenderingError::InstancingLimit)
-            }
-
-            Err(e) => {
-                rsvg_log!("not using paint server {}: {}", node, e);
-
-                // "could not resolve" means caller needs to fall back to color
-                Ok(false)
-            }
+    ) -> Result<PaintSource, RenderingError> {
+        match self {
+            PaintServer::Iri {
+                ref iri,
+                ref alternate,
+            } => acquired_nodes
+                .acquire(iri)
+                .and_then(|acquired| {
+                    let node = acquired.get();
+                    assert!(node.is_element());
+
+                    match *node.borrow_element() {
+                        Element::LinearGradient(ref g) => g
+                            .resolve(&node, acquired_nodes)
+                            .map(|g| PaintSource::Gradient(g, *alternate)),
+                        Element::Pattern(ref p) => p
+                            .resolve(&node, acquired_nodes)
+                            .map(|p| PaintSource::Pattern(p, *alternate)),
+                        Element::RadialGradient(ref g) => g
+                            .resolve(&node, acquired_nodes)
+                            .map(|g| PaintSource::Gradient(g, *alternate)),
+                        _ => Err(AcquireError::InvalidLinkType(iri.clone())),
+                    }
+                })
+                .or_else(|err| match (err, alternate) {
+                    (AcquireError::CircularReference(node), _) => {
+                        rsvg_log!("circular reference in paint server {}", node);
+                        Err(RenderingError::CircularReference)
+                    }
+
+                    (AcquireError::MaxReferencesExceeded, _) => {
+                        rsvg_log!("maximum number of references exceeded");
+                        Err(RenderingError::InstancingLimit)
+                    }
+
+                    (_, Some(color)) => {
+                        rsvg_log!(
+                            "could not resolve paint server \"{}\", using alternate color",
+                            iri
+                        );
+
+                        Ok(PaintSource::SolidColor(*color))
+                    }
+
+                    (_, _) => {
+                        rsvg_log!(
+                            "could not resolve paint server \"{}\", no alternate color specified",
+                            iri
+                        );
+
+                        Ok(PaintSource::None)
+                    }
+                }),
+
+            PaintServer::SolidColor(color) => Ok(PaintSource::SolidColor(*color)),
+
+            PaintServer::None => Ok(PaintSource::None),
         }
     }
 }
 
-pub trait AsPaintSource {
-    fn set_as_paint_source(
-        self,
-        acquired_nodes: &mut AcquiredNodes,
-        values: &ComputedValues,
-        draw_ctx: &mut DrawingCtx,
-        opacity: UnitInterval,
-        bbox: &BoundingBox,
-    ) -> Result<bool, RenderingError>;
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/rsvg_internals/src/pattern.rs b/rsvg_internals/src/pattern.rs
index 3baad79d..3320e543 100644
--- a/rsvg_internals/src/pattern.rs
+++ b/rsvg_internals/src/pattern.rs
@@ -2,27 +2,22 @@
 
 use markup5ever::{expanded_name, local_name, namespace_url, ns};
 use std::cell::RefCell;
-use std::f64;
 
 use crate::allowed_url::Fragment;
 use crate::aspect_ratio::*;
-use crate::bbox::*;
 use crate::coord_units::CoordUnits;
 use crate::document::{AcquiredNodes, NodeStack};
-use crate::drawing_ctx::{DrawingCtx, ViewParams};
+use crate::drawing_ctx::ViewParams;
 use crate::element::{Draw, Element, ElementResult, SetAttributes};
 use crate::error::*;
-use crate::float_eq_cairo::ApproxEqCairo;
 use crate::href::{is_href, set_href};
 use crate::length::*;
-use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw, WeakNode};
-use crate::paint_server::{AsPaintSource, PaintSource};
+use crate::node::{Node, NodeBorrow, WeakNode};
 use crate::parsers::ParseValue;
 use crate::properties::ComputedValues;
 use crate::property_bag::PropertyBag;
 use crate::rect::Rect;
 use crate::transform::Transform;
-use crate::unit_interval::UnitInterval;
 use crate::viewbox::*;
 
 coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
@@ -158,228 +153,6 @@ impl SetAttributes for Pattern {
 
 impl Draw for Pattern {}
 
-impl PaintSource for Pattern {
-    type Resolved = ResolvedPattern;
-
-    fn resolve(
-        &self,
-        node: &Node,
-        acquired_nodes: &mut AcquiredNodes,
-    ) -> Result<Self::Resolved, AcquireError> {
-        let mut resolved = self.resolved.borrow_mut();
-        if let Some(ref pattern) = *resolved {
-            return Ok(pattern.clone());
-        }
-
-        let Unresolved {
-            mut pattern,
-            mut fallback,
-        } = self.get_unresolved(node);
-
-        let mut stack = NodeStack::new();
-
-        while !pattern.is_resolved() {
-            if let Some(ref fragment) = fallback {
-                match acquired_nodes.acquire(&fragment) {
-                    Ok(acquired) => {
-                        let acquired_node = acquired.get();
-
-                        if stack.contains(acquired_node) {
-                            return Err(AcquireError::CircularReference(acquired_node.clone()));
-                        }
-
-                        match *acquired_node.borrow_element() {
-                            Element::Pattern(ref p) => {
-                                let unresolved = p.get_unresolved(&acquired_node);
-                                pattern = pattern.resolve_from_fallback(&unresolved.pattern);
-                                fallback = unresolved.fallback;
-
-                                stack.push(acquired_node);
-                            }
-                            _ => return Err(AcquireError::InvalidLinkType(fragment.clone())),
-                        }
-                    }
-
-                    Err(AcquireError::MaxReferencesExceeded) => {
-                        return Err(AcquireError::MaxReferencesExceeded)
-                    }
-
-                    Err(e) => {
-                        rsvg_log!("Stopping pattern resolution: {}", e);
-                        pattern = pattern.resolve_from_defaults();
-                        break;
-                    }
-                }
-            } else {
-                pattern = pattern.resolve_from_defaults();
-                break;
-            }
-        }
-
-        let pattern = pattern.into_resolved();
-
-        *resolved = Some(pattern.clone());
-
-        Ok(pattern)
-    }
-}
-
-impl AsPaintSource for ResolvedPattern {
-    fn set_as_paint_source(
-        self,
-        acquired_nodes: &mut AcquiredNodes,
-        values: &ComputedValues,
-        draw_ctx: &mut DrawingCtx,
-        opacity: UnitInterval,
-        bbox: &BoundingBox,
-    ) -> Result<bool, RenderingError> {
-        let node = if let Some(n) = self.children.node_with_children() {
-            n
-        } else {
-            // This means we didn't find any children among the fallbacks,
-            // so there is nothing to render.
-            return Ok(false);
-        };
-
-        let units = self.units;
-        let content_units = self.content_units;
-        let pattern_affine = self.affine;
-        let vbox = self.vbox;
-        let preserve_aspect_ratio = self.preserve_aspect_ratio;
-
-        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
-            draw_ctx.push_view_box(1.0, 1.0)
-        } else {
-            draw_ctx.get_view_params()
-        };
-
-        let pattern_rect = self.get_rect(values, &params);
-
-        // Work out the size of the rectangle so it takes into account the object bounding box
-
-        let (bbwscale, bbhscale) = match units {
-            PatternUnits(CoordUnits::ObjectBoundingBox) => bbox.rect.unwrap().size(),
-            PatternUnits(CoordUnits::UserSpaceOnUse) => (1.0, 1.0),
-        };
-
-        let taffine = draw_ctx.get_transform().pre_transform(&pattern_affine);
-
-        let mut scwscale = (taffine.xx.powi(2) + taffine.xy.powi(2)).sqrt();
-        let mut schscale = (taffine.yx.powi(2) + taffine.yy.powi(2)).sqrt();
-
-        let scaled_width = pattern_rect.width() * bbwscale;
-        let scaled_height = pattern_rect.height() * bbhscale;
-
-        let pw: i32 = (scaled_width * scwscale) as i32;
-        let ph: i32 = (scaled_height * schscale) as i32;
-
-        if scaled_width.abs() < f64::EPSILON
-            || scaled_height.abs() < f64::EPSILON
-            || pw < 1
-            || ph < 1
-        {
-            return Ok(false);
-        }
-
-        scwscale = f64::from(pw) / scaled_width;
-        schscale = f64::from(ph) / scaled_height;
-
-        // Create the pattern coordinate system
-        let mut affine = match units {
-            PatternUnits(CoordUnits::ObjectBoundingBox) => {
-                let bbrect = bbox.rect.unwrap();
-                Transform::new_translate(
-                    bbrect.x0 + pattern_rect.x0 * bbrect.width(),
-                    bbrect.y0 + pattern_rect.y0 * bbrect.height(),
-                )
-            }
-
-            PatternUnits(CoordUnits::UserSpaceOnUse) => {
-                Transform::new_translate(pattern_rect.x0, pattern_rect.y0)
-            }
-        };
-
-        // Apply the pattern transform
-        affine = affine.post_transform(&pattern_affine);
-
-        let mut caffine: Transform;
-
-        // Create the pattern contents coordinate system
-        let _params = if let Some(vbox) = vbox {
-            // If there is a vbox, use that
-            let r =
-                preserve_aspect_ratio.compute(&vbox, &Rect::from_size(scaled_width, scaled_height));
-
-            let sw = r.width() / vbox.0.width();
-            let sh = r.height() / vbox.0.height();
-            let x = r.x0 - vbox.0.x0 * sw;
-            let y = r.y0 - vbox.0.y0 * sh;
-
-            caffine = Transform::new_scale(sw, sh).pre_translate(x, y);
-
-            draw_ctx.push_view_box(vbox.0.width(), vbox.0.height())
-        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
-            // If coords are in terms of the bounding box, use them
-            let (bbw, bbh) = bbox.rect.unwrap().size();
-
-            caffine = Transform::new_scale(bbw, bbh);
-
-            draw_ctx.push_view_box(1.0, 1.0)
-        } else {
-            caffine = Transform::identity();
-            draw_ctx.get_view_params()
-        };
-
-        if !scwscale.approx_eq_cairo(1.0) || !schscale.approx_eq_cairo(1.0) {
-            caffine = caffine.post_scale(scwscale, schscale);
-            affine = affine.pre_scale(1.0 / scwscale, 1.0 / schscale);
-        }
-
-        // Draw to another surface
-
-        let cr_save = draw_ctx.get_cairo_context();
-
-        let surface = cr_save
-            .get_target()
-            .create_similar(cairo::Content::ColorAlpha, pw, ph)?;
-
-        let cr_pattern = cairo::Context::new(&surface);
-
-        draw_ctx.set_cairo_context(&cr_pattern);
-
-        // Set up transformations to be determined by the contents units
-        cr_pattern.set_matrix(caffine.into());
-
-        // Draw everything
-        let res = draw_ctx.with_alpha(opacity, &mut |dc| {
-            let pattern_cascaded = CascadedValues::new_from_node(&node);
-            let pattern_values = pattern_cascaded.get();
-            dc.with_discrete_layer(
-                &node,
-                acquired_nodes,
-                pattern_values,
-                false,
-                &mut |an, dc| node.draw_children(an, &pattern_cascaded, dc, false),
-            )
-        });
-
-        // Return to the original coordinate system and rendering context
-        draw_ctx.set_cairo_context(&cr_save);
-
-        // Set the final surface as a Cairo pattern into the Cairo context
-        let pattern = cairo::SurfacePattern::create(&surface);
-
-        if let Some(m) = affine.invert() {
-            pattern.set_matrix(m.into())
-        }
-        pattern.set_extend(cairo::Extend::Repeat);
-        pattern.set_filter(cairo::Filter::Best);
-        cr_save.set_source(&pattern);
-
-        res.map(|_| true)
-    }
-}
-
 impl UnresolvedPattern {
     fn into_resolved(self) -> ResolvedPattern {
         assert!(self.is_resolved());
@@ -529,17 +302,28 @@ impl UnresolvedChildren {
     }
 }
 
-impl Children {
-    fn node_with_children(&self) -> Option<Node> {
-        match *self {
-            Children::Empty => None,
-            Children::WithChildren(ref wc) => Some(wc.upgrade().unwrap()),
-        }
+impl ResolvedPattern {
+    pub fn get_units(&self) -> PatternUnits {
+        self.units
     }
-}
 
-impl ResolvedPattern {
-    fn get_rect(&self, values: &ComputedValues, params: &ViewParams) -> Rect {
+    pub fn get_content_units(&self) -> PatternContentUnits {
+        self.content_units
+    }
+
+    pub fn get_vbox(&self) -> Option<ViewBox> {
+        self.vbox
+    }
+
+    pub fn get_preserve_aspect_ratio(&self) -> AspectRatio {
+        self.preserve_aspect_ratio
+    }
+
+    pub fn get_transform(&self) -> Transform {
+        self.affine
+    }
+
+    pub fn get_rect(&self, values: &ComputedValues, params: &ViewParams) -> Rect {
         let x = self.x.normalize(&values, &params);
         let y = self.y.normalize(&values, &params);
         let w = self.width.normalize(&values, &params);
@@ -547,6 +331,13 @@ impl ResolvedPattern {
 
         Rect::new(x, y, x + w, y + h)
     }
+
+    pub fn node_with_children(&self) -> Option<Node> {
+        match self.children {
+            Children::Empty => None,
+            Children::WithChildren(ref wc) => Some(wc.upgrade().unwrap()),
+        }
+    }
 }
 
 impl Pattern {
@@ -561,6 +352,68 @@ impl Pattern {
             fallback: self.fallback.clone(),
         }
     }
+
+    pub fn resolve(
+        &self,
+        node: &Node,
+        acquired_nodes: &mut AcquiredNodes,
+    ) -> Result<ResolvedPattern, AcquireError> {
+        let mut resolved = self.resolved.borrow_mut();
+        if let Some(ref pattern) = *resolved {
+            return Ok(pattern.clone());
+        }
+
+        let Unresolved {
+            mut pattern,
+            mut fallback,
+        } = self.get_unresolved(node);
+
+        let mut stack = NodeStack::new();
+
+        while !pattern.is_resolved() {
+            if let Some(ref fragment) = fallback {
+                match acquired_nodes.acquire(&fragment) {
+                    Ok(acquired) => {
+                        let acquired_node = acquired.get();
+
+                        if stack.contains(acquired_node) {
+                            return Err(AcquireError::CircularReference(acquired_node.clone()));
+                        }
+
+                        match *acquired_node.borrow_element() {
+                            Element::Pattern(ref p) => {
+                                let unresolved = p.get_unresolved(&acquired_node);
+                                pattern = pattern.resolve_from_fallback(&unresolved.pattern);
+                                fallback = unresolved.fallback;
+
+                                stack.push(acquired_node);
+                            }
+                            _ => return Err(AcquireError::InvalidLinkType(fragment.clone())),
+                        }
+                    }
+
+                    Err(AcquireError::MaxReferencesExceeded) => {
+                        return Err(AcquireError::MaxReferencesExceeded)
+                    }
+
+                    Err(e) => {
+                        rsvg_log!("Stopping pattern resolution: {}", e);
+                        pattern = pattern.resolve_from_defaults();
+                        break;
+                    }
+                }
+            } else {
+                pattern = pattern.resolve_from_defaults();
+                break;
+            }
+        }
+
+        let pattern = pattern.into_resolved();
+
+        *resolved = Some(pattern.clone());
+
+        Ok(pattern)
+    }
 }
 
 #[cfg(test)]



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]