RFC: render_element_to_viewport() should follow the <use> semantics
Seeing the following in @jsparber's svago-export made me realize that the current incarnation of render_element_to_viewport()
is awkward to use:
fn render(handle: &SvgHandle, item: &Icon, file: &String) -> bool {
let group = format!("#{}", item.id);
let rect = format!("#{}", item.square);
let renderer = CairoRenderer::new(handle);
/* FIXME: vbox of intrinsic_dimensions() is for some reason None */
let viewport = {
let doc = renderer.intrinsic_dimensions();
cairo::Rectangle {
x: 0.0,
y: 0.0,
width: doc.width.unwrap().get_unitless(),
height: doc.height.unwrap().get_unitless(),
}
};
let (rect, _) = renderer
.geometry_for_element(Some(&rect), &viewport) // 1
.unwrap();
/* don't render icons outside the viewport */
if rect.x < 0. || rect.y < 0. || rect.x > viewport.width || rect.y > viewport.height {
return false;
}
let mut surface = svg::File::new(rect.width, rect.height, file); // 2
surface.set_document_unit(SvgUnit::Px);
let cr = cairo::Context::new(&surface);
cr.translate(-rect.x, -rect.y); // 3
let _ = renderer.render_element_to_viewport(&cr, Some(group.as_str()), &viewport); // 4
true
}
In (1), the code gets the geometry of one of the unstroked/unfilled squares that Adwaita uses to define each icon's bounding box (look for fill:none;stroke:none;
in there). This call to geometry_for_element
really means "get me the geometry of the element #id
as if you were rendering the whole SVG to a viewport
of the given size".
In (2), the code creates a Cairo SVG surface with the pixel size returned by (1) for the icon's square. No problems so far.
But in (3), we translate so the icon will be rendered at the origin...
... Because in (4), the call to render_element_to_viewport
means, "render just the element #id
as if you were rendering the whole SVG to a viewport
of the given size". What the user wants to do is to extract that element!
Within SVG, one can "instance" an element using the <use>
element. It has x/y/width/height
attributes to define a viewport, but width/height
are only used if the referenced element is <svg>
or <symbol>
. In turn, each of those can have its own x/y/width/height
viewport, a viewBox
, and preserveAspectRatio
.
The spec says:
The width and height properties on the
use
element have no effect if the referenced element does not establish a new viewport. In particular, theuse
element does not itself establish a new viewport, and therefore does not affect the interpretation of percentages in the re-used graphics.
I think librsvg can "augment" this for its public API by actually using the width/height even if the referenced element for render_element_in_viewport
is not an <svg>
or <symbol>
. I think librsvg would actually have to figure out untransformed dimensions for the element in case it doesn't have its own viewport, just like the toplevel geometry computation code.
The idea is that a call like renderer.render_element_to_viewport("#id", Rectangle { ... })
would actually scale/translate the element to fit in that rectangle.
For the old semantics of rendering just a single element as if one were rendering the whole SVG to a viewport of a given size, I think one could just pass the returned element's geometry to render_element_to_viewport
: roundtripping the geometry to the desired viewport should have the effect of rendering the element "in its original place".
Comments appreciated