Lines
96.46 %
Functions
14.81 %
Branches
46.47 %
//! The `image` element.
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::aspect_ratio::AspectRatio;
use crate::bbox::BoundingBox;
use crate::document::{AcquiredNodes, Document, Resource};
use crate::drawing_ctx::{DrawingCtx, SvgNesting, Viewport};
use crate::element::{set_attribute, ElementTrait};
use crate::error::*;
use crate::href::{is_href, set_href};
use crate::layout::{self, Layer, LayerKind, StackingContext};
use crate::length::*;
use crate::node::{CascadedValues, Node, NodeBorrow};
use crate::parsers::ParseValue;
use crate::rect::Rect;
use crate::rsvg_log;
use crate::session::Session;
use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
use crate::xml::Attributes;
/// The `<image>` element.
///
/// Note that its x/y/width/height are properties in SVG2, so they are
/// defined as part of [the properties machinery](properties.rs).
#[derive(Default)]
pub struct Image {
aspect: AspectRatio,
href: Option<String>,
}
impl ElementTrait for Image {
fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
for (attr, value) in attrs.iter() {
match attr.expanded() {
expanded_name!("", "preserveAspectRatio") => {
set_attribute(&mut self.aspect, attr.parse(value), session)
// "path" is used by some older Adobe Illustrator versions
ref a if is_href(a) || *a == expanded_name!("", "path") => {
set_href(a, &mut self.href, Some(value.to_string()))
_ => (),
fn layout(
&self,
node: &Node,
acquired_nodes: &mut AcquiredNodes<'_>,
cascaded: &CascadedValues<'_>,
viewport: &Viewport,
draw_ctx: &mut DrawingCtx,
_clipping: bool,
) -> Result<Option<Layer>, InternalRenderingError> {
if let Some(ref url) = self.href {
self.layout_from_url(url, node, acquired_nodes, cascaded, viewport, draw_ctx)
} else {
Ok(None)
fn draw(
clipping: bool,
) -> Result<BoundingBox, InternalRenderingError> {
let layer = self.layout(node, acquired_nodes, cascaded, viewport, draw_ctx, clipping)?;
if let Some(layer) = layer {
draw_ctx.draw_layer(&layer, acquired_nodes, clipping, viewport)
Ok(draw_ctx.empty_bbox())
impl Image {
fn layout_from_url(
url: &str,
match acquired_nodes.lookup_resource(url) {
Ok(Resource::Image(surface)) => self.layout_from_surface(
&surface,
node,
acquired_nodes,
cascaded,
viewport,
draw_ctx,
),
Ok(Resource::Document(document)) => self.layout_from_svg(
&document,
Err(e) => {
rsvg_log!(
draw_ctx.session(),
"could not load image \"{}\": {}",
url,
e
);
/// Draw an `<image>` from a raster image.
fn layout_from_surface(
surface: &SharedImageSurface,
let values = cascaded.get();
let params = NormalizeParams::new(values, viewport);
let x = values.x().0.to_user(¶ms);
let y = values.y().0.to_user(¶ms);
let w = match values.width().0 {
LengthOrAuto::Length(l) => l.to_user(¶ms),
LengthOrAuto::Auto => surface.width() as f64,
};
let h = match values.height().0 {
LengthOrAuto::Auto => surface.height() as f64,
let is_visible = values.is_visible();
let rect = Rect::new(x, y, x + w, y + h);
let overflow = values.overflow();
let image = Box::new(layout::Image {
surface: surface.clone(),
is_visible,
rect,
aspect: self.aspect,
overflow,
image_rendering: values.image_rendering(),
});
let elt = node.borrow_element();
let stacking_ctx = StackingContext::new(
&elt,
values.transform(),
None,
values,
let layer = Layer {
kind: LayerKind::Image(image),
stacking_ctx,
Ok(Some(layer))
/// Draw an `<image>` from an SVG image.
/// Per the [spec], we need to rasterize the SVG ("The result of processing an ‘image’
/// is always a four-channel RGBA result.") and then composite it as if it were a PNG
/// or JPEG.
/// [spec]: https://www.w3.org/TR/SVG2/embedded.html#ImageElement
fn layout_from_svg(
document: &Document,
let dimensions = document.get_intrinsic_dimensions();
LengthOrAuto::Auto => dimensions.width.to_user(¶ms),
LengthOrAuto::Auto => dimensions.height.to_user(¶ms),
let dest_rect = match dimensions.vbox {
None => Rect::from_size(w, h),
Some(vbox) => self.aspect.compute(&vbox, &Rect::new(x, y, x + w, y + h)),
let dest_size = dest_rect.size();
let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
// We use ceil() to avoid chopping off the last pixel if it is partially covered.
let surface_width = checked_i32(dest_size.0.ceil())?;
let surface_height = checked_i32(dest_size.1.ceil())?;
let surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
{
let cr = cairo::Context::new(&surface)?;
let options = draw_ctx.rendering_options(SvgNesting::ReferencedFromImageElement);
document.render_document(
&cr,
&cairo::Rectangle::from(surface_dest_rect),
&options,
)?;
let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
surface,
pub fn checked_i32(x: f64) -> Result<i32, cairo::Error> {
cast::i32(x).map_err(|_| cairo::Error::InvalidSize)