[librsvg: 5/15] CairoRenderer::render_element() - new API function



commit 12194fa4629ba73b284a35680c0b22e4a7d514ff
Author: Federico Mena Quintero <federico gnome org>
Date:   Thu Jul 25 14:28:11 2019 -0500

    CairoRenderer::render_element() - new API function
    
    New test/API for obtaining the geometry of an untransformed element

 librsvg_crate/src/lib.rs     |  39 +++++++++++-
 librsvg_crate/tests/api.rs   | 145 ++++++++++++++++++++++++++++++++++++++++++-
 rsvg_internals/src/handle.rs | 137 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 318 insertions(+), 3 deletions(-)
---
diff --git a/librsvg_crate/src/lib.rs b/librsvg_crate/src/lib.rs
index 980a35ce..dae6eb4d 100644
--- a/librsvg_crate/src/lib.rs
+++ b/librsvg_crate/src/lib.rs
@@ -425,7 +425,7 @@ impl<'a> CairoRenderer<'a> {
         self.handle.0.render_document(cr, viewport, self.dpi, false)
     }
 
-    /// Renders a single SVG element within an SVG document
+    /// Renders a single SVG element in the same place as for a whole SVG document
     ///
     /// This is equivalent to `render_document`, but renders only a single
     /// element and its children.  The element is rendered with the same
@@ -454,4 +454,41 @@ impl<'a> CairoRenderer<'a> {
             .0
             .snapshot_element(cr, id, viewport, self.dpi, false)
     }
+
+    /// Renders a single SVG element to a given viewport
+    ///
+    /// This function can be used to extract individual element subtrees and render them,
+    /// scaled to a given `element_viewport`.  This is useful for applications which have
+    /// reusable objects in an SVG and want to render them individually; for example, an
+    /// SVG full of icons that are meant to be be rendered independently of each other.
+    ///
+    /// Note that the `id` must be a plain fragment identifier like `#foo`, with
+    /// a leading `#` character.
+    ///
+    /// The `element_viewport` gives the position and size at which the named element will
+    /// be rendered.  FIXME: mention proportional scaling.
+    ///
+    /// The `cr` must be in a `cairo::Status::Success` state, or this function
+    /// will not render anything, and instead will return
+    /// `RenderingError::Cairo` with the `cr`'s current error state.
+    pub fn render_element(
+        &self,
+        cr: &cairo::Context,
+        id: Option<&str>,
+        element_viewport: &cairo::Rectangle,
+    ) -> Result<(), RenderingError> {
+        self.handle
+            .0
+            .render_element(cr, id, element_viewport, self.dpi, false)
+    }
+
+    pub fn geometry_for_untransformed_element(
+        &self,
+        id: Option<&str>,
+    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
+        self.handle
+            .0
+            .get_untransformed_node_geometry(id, self.dpi, false)
+            .map(|(i, l)| (i.into(), l.into()))
+    }
 }
diff --git a/librsvg_crate/tests/api.rs b/librsvg_crate/tests/api.rs
index 2da279be..02ddc8da 100644
--- a/librsvg_crate/tests/api.rs
+++ b/librsvg_crate/tests/api.rs
@@ -1,7 +1,10 @@
-use librsvg::{DefsLookupErrorKind, HrefError, RenderingError};
+use cairo;
+use librsvg::{CairoRenderer, DefsLookupErrorKind, HrefError, RenderingError};
+
+use rsvg_internals::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
 
 mod utils;
-use self::utils::load_svg;
+use self::utils::{compare_to_surface, load_svg};
 
 #[test]
 fn has_element_with_id_works() {
@@ -37,3 +40,141 @@ fn has_element_with_id_works() {
         ))
     );
 }
+
+#[test]
+fn snapshot_element() {
+    let svg = load_svg(
+        br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="100" height="100">
+  <rect id="foo" x="10" y="10" width="30" height="30" fill="#00ff00"/>
+  <rect id="bar" x="20" y="20" width="30" height="30" fill="#0000ff"/>
+</svg>
+"##,
+    );
+
+    let renderer = CairoRenderer::new(&svg);
+
+    let output = cairo::ImageSurface::create(cairo::Format::ARgb32, 300, 300).unwrap();
+
+    let res = {
+        let cr = cairo::Context::new(&output);
+        let viewport = cairo::Rectangle {
+            x: 100.0,
+            y: 100.0,
+            width: 100.0,
+            height: 100.0,
+        };
+
+        renderer.snapshot_element(&cr, Some("#bar"), &viewport)
+    };
+
+    let output_surf = res
+        .and_then(|_| Ok(SharedImageSurface::new(output, SurfaceType::SRgb).unwrap()))
+        .unwrap();
+
+    let reference_surf = cairo::ImageSurface::create(cairo::Format::ARgb32, 300, 300).unwrap();
+
+    {
+        let cr = cairo::Context::new(&reference_surf);
+
+        cr.translate(100.0, 100.0);
+
+        cr.rectangle(20.0, 20.0, 30.0, 30.0);
+        cr.set_source_rgba(0.0, 0.0, 1.0, 1.0);
+        cr.fill();
+    }
+
+    let reference_surf = SharedImageSurface::new(reference_surf, SurfaceType::SRgb).unwrap();
+
+    compare_to_surface(&output_surf, &reference_surf, "snapshot_element");
+}
+
+#[test]
+fn untransformed_element() {
+    // This has a rectangle inside a transformed group.  The rectangle
+    // inherits its stroke-width from the group.
+    //
+    // The idea is that we'll be able to extract the geometry of the rectangle
+    // as if it were not transformed by its ancestors, but still retain the
+    // cascade from the ancestors.
+    let svg = load_svg(
+        br##"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="100" height="100">
+  <g transform="rotate(45)" stroke-width="10" stroke="#000000">
+    <rect id="foo" x="10" y="20" width="30" height="40" fill="#0000ff"/>
+  </g>
+</svg>
+"##,
+    );
+
+    let renderer = CairoRenderer::new(&svg);
+
+    /* Measuring */
+
+    let (ink_r, logical_r) = renderer
+        .geometry_for_untransformed_element(Some("#foo"))
+        .unwrap();
+
+    assert_eq!(
+        ink_r,
+        cairo::Rectangle {
+            x: 0.0,
+            y: 0.0,
+            width: 40.0,
+            height: 50.0,
+        }
+    );
+
+    assert_eq!(
+        logical_r,
+        cairo::Rectangle {
+            x: 5.0,
+            y: 5.0,
+            width: 30.0,
+            height: 40.0,
+        }
+    );
+
+    /* Rendering */
+
+    let output = cairo::ImageSurface::create(cairo::Format::ARgb32, 300, 300).unwrap();
+
+    let res = {
+        let cr = cairo::Context::new(&output);
+        let viewport = cairo::Rectangle {
+            x: 100.0,
+            y: 100.0,
+            width: 100.0,
+            height: 100.0,
+        };
+
+        renderer.render_element(&cr, Some("#foo"), &viewport)
+    };
+
+    let output_surf =
+        res.and_then(|_| Ok(SharedImageSurface::new(output, SurfaceType::SRgb).unwrap())).unwrap();
+
+    let reference_surf = cairo::ImageSurface::create(cairo::Format::ARgb32, 300, 300).unwrap();
+
+    {
+        let cr = cairo::Context::new(&reference_surf);
+
+        cr.translate(100.0, 100.0);
+
+        cr.rectangle(10.0, 10.0, 60.0, 80.0);
+        cr.set_source_rgba(0.0, 0.0, 1.0, 1.0);
+        cr.fill_preserve();
+
+        cr.set_line_width(20.0);
+        cr.set_source_rgba(0.0, 0.0, 0.0, 1.0);
+        cr.stroke();
+    }
+
+    let reference_surf = SharedImageSurface::new(reference_surf, SurfaceType::SRgb).unwrap();
+
+    compare_to_surface(
+        &output_surf,
+        &reference_surf,
+        "untransformed_element",
+    );
+}
diff --git a/rsvg_internals/src/handle.rs b/rsvg_internals/src/handle.rs
index 989b959d..a5f745b8 100644
--- a/rsvg_internals/src/handle.rs
+++ b/rsvg_internals/src/handle.rs
@@ -368,6 +368,143 @@ impl Handle {
         res
     }
 
+    /// Returns (ink_rect, logical_rect)
+    pub fn get_untransformed_node_geometry(
+        &self,
+        id: Option<&str>,
+        dpi: Dpi,
+        is_testing: bool,
+    ) -> Result<(RsvgRectangle, RsvgRectangle), RenderingError> {
+        let node = self.get_node_or_root(id)?;
+
+        let target = ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
+        let cr = cairo::Context::new(&target);
+
+        let viewport = cairo::Rectangle {
+            x: 0.0,
+            y: 0.0,
+            width: 1.0,
+            height: 1.0,
+        };
+
+        let mut draw_ctx = DrawingCtx::new(
+            self.svg.clone(),
+            None,
+            &cr,
+            &viewport,
+            dpi,
+            true,
+            is_testing,
+        );
+
+        draw_ctx.draw_node_from_stack(&CascadedValues::new_from_node(&node), &node, false)?;
+
+        let bbox = draw_ctx.get_bbox();
+
+        let mut ink_rect = bbox
+            .ink_rect
+            .map(|r| RsvgRectangle::from(r))
+            .unwrap_or_default();
+        let mut logical_rect = bbox
+            .rect
+            .map(|r| RsvgRectangle::from(r))
+            .unwrap_or_default();
+
+        // Translate so ink_rect is always at offset (0, 0)
+
+        let xofs = ink_rect.x;
+        let yofs = ink_rect.y;
+
+        ink_rect.x -= xofs;
+        ink_rect.y -= yofs;
+
+        logical_rect.x -= xofs;
+        logical_rect.y -= yofs;
+
+        Ok((ink_rect, logical_rect))
+    }
+
+    pub fn render_element(
+        &self,
+        cr: &cairo::Context,
+        id: Option<&str>,
+        element_viewport: &cairo::Rectangle,
+        dpi: Dpi,
+        is_testing: bool,
+    ) -> Result<(), RenderingError> {
+        check_cairo_context(cr)?;
+
+        let node = self.get_node_or_root(id)?;
+
+        // Measure the element
+
+        let target = ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
+        let measure_cr = cairo::Context::new(&target);
+
+        let viewport = cairo::Rectangle {
+            x: 0.0,
+            y: 0.0,
+            width: 1.0,
+            height: 1.0,
+        };
+
+        let mut draw_ctx = DrawingCtx::new(
+            self.svg.clone(),
+            None,
+            &measure_cr,
+            &viewport,
+            dpi,
+            true,
+            is_testing,
+        );
+
+        draw_ctx.draw_node_from_stack(&CascadedValues::new_from_node(&node), &node, false)?;
+
+        let bbox = draw_ctx.get_bbox();
+
+        if bbox.ink_rect.is_none() || bbox.rect.is_none() {
+            // Nothing to draw
+            return Ok(());
+        }
+
+        let ink_r = bbox
+            .ink_rect
+            .map(|r| RsvgRectangle::from(r))
+            .unwrap_or_default();
+
+        if ink_r.width == 0.0 || ink_r.height == 0.0 {
+            return Ok(());
+        }
+
+        // Render, transforming so element is at the new viewport's origin
+
+        cr.save();
+
+        let factor =
+            (element_viewport.width / ink_r.width).min(element_viewport.height / ink_r.height);
+
+        cr.translate(element_viewport.x, element_viewport.y);
+        cr.scale(factor, factor);
+        cr.translate(-ink_r.x, -ink_r.y);
+
+        let mut draw_ctx = DrawingCtx::new(
+            self.svg.clone(),
+            None,
+            &cr,
+            &viewport,
+            dpi,
+            false,
+            is_testing,
+        );
+
+        let res =
+            draw_ctx.draw_node_from_stack(&CascadedValues::new_from_node(&node), &node, false);
+
+        cr.restore();
+
+        res
+    }
+
     pub fn get_pixbuf_sub(
         &self,
         id: Option<&str>,


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