[librsvg: 5/15] CairoRenderer::render_element() - new API function
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 5/15] CairoRenderer::render_element() - new API function
- Date: Tue, 30 Jul 2019 17:32:41 +0000 (UTC)
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]