Skip to main content

rsvg/
image.rs

1//! The `image` element.
2
3use markup5ever::{expanded_name, local_name, ns};
4
5use crate::aspect_ratio::AspectRatio;
6use crate::document::{AcquiredNodes, Document, Resource};
7use crate::drawing_ctx::{DrawingCtx, SvgNesting, Viewport};
8use crate::element::{DrawResult, ElementTrait, set_attribute};
9use crate::error::*;
10use crate::href::{is_href, set_href};
11use crate::layout::{self, Layer, LayerKind, StackingContext};
12use crate::length::*;
13use crate::node::{CascadedValues, Node, NodeBorrow};
14use crate::parsers::ParseValue;
15use crate::rect::Rect;
16use crate::rsvg_log;
17use crate::session::Session;
18use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
19use crate::xml::Attributes;
20
21/// The `<image>` element.
22///
23/// Note that its x/y/width/height are properties in SVG2, so they are
24/// defined as part of [the properties machinery](properties.rs).
25#[derive(Default)]
26pub struct Image {
27    aspect: AspectRatio,
28    href: Option<String>,
29}
30
31impl ElementTrait for Image {
32    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
33        for (attr, value) in attrs.iter() {
34            match attr.expanded() {
35                expanded_name!("", "preserveAspectRatio") => {
36                    set_attribute(&mut self.aspect, attr.parse(value), session)
37                }
38
39                // "path" is used by some older Adobe Illustrator versions
40                ref a if is_href(a) || *a == expanded_name!("", "path") => {
41                    set_href(a, &mut self.href, Some(value.to_string()))
42                }
43
44                _ => (),
45            }
46        }
47    }
48
49    fn layout(
50        &self,
51        node: &Node,
52        acquired_nodes: &mut AcquiredNodes<'_>,
53        cascaded: &CascadedValues<'_>,
54        viewport: &Viewport,
55        draw_ctx: &mut DrawingCtx,
56        _clipping: bool,
57    ) -> Result<Option<Layer>, Box<InternalRenderingError>> {
58        if let Some(ref url) = self.href {
59            self.layout_from_url(url, node, acquired_nodes, cascaded, viewport, draw_ctx)
60        } else {
61            Ok(None)
62        }
63    }
64
65    fn draw(
66        &self,
67        node: &Node,
68        acquired_nodes: &mut AcquiredNodes<'_>,
69        cascaded: &CascadedValues<'_>,
70        viewport: &Viewport,
71        draw_ctx: &mut DrawingCtx,
72        clipping: bool,
73    ) -> DrawResult {
74        let layer = self.layout(node, acquired_nodes, cascaded, viewport, draw_ctx, clipping)?;
75
76        if let Some(layer) = layer {
77            draw_ctx.draw_layer(&layer, acquired_nodes, clipping, viewport)
78        } else {
79            Ok(viewport.empty_bbox())
80        }
81    }
82}
83
84impl Image {
85    fn layout_from_url(
86        &self,
87        url: &str,
88        node: &Node,
89        acquired_nodes: &mut AcquiredNodes<'_>,
90        cascaded: &CascadedValues<'_>,
91        viewport: &Viewport,
92        draw_ctx: &mut DrawingCtx,
93    ) -> Result<Option<Layer>, Box<InternalRenderingError>> {
94        match acquired_nodes.lookup_resource(url) {
95            Ok(Resource::Image(surface)) => self.layout_from_surface(
96                &surface,
97                node,
98                acquired_nodes,
99                cascaded,
100                viewport,
101                draw_ctx,
102            ),
103
104            Ok(Resource::Document(document)) => self.layout_from_svg(
105                &document,
106                node,
107                acquired_nodes,
108                cascaded,
109                viewport,
110                draw_ctx,
111            ),
112
113            Err(e) => {
114                rsvg_log!(
115                    draw_ctx.session(),
116                    "could not load image \"{}\": {}",
117                    url,
118                    e
119                );
120                Ok(None)
121            }
122        }
123    }
124
125    /// Draw an `<image>` from a raster image.
126    fn layout_from_surface(
127        &self,
128        surface: &SharedImageSurface,
129        node: &Node,
130        acquired_nodes: &mut AcquiredNodes<'_>,
131        cascaded: &CascadedValues<'_>,
132        viewport: &Viewport,
133        draw_ctx: &mut DrawingCtx,
134    ) -> Result<Option<Layer>, Box<InternalRenderingError>> {
135        let values = cascaded.get();
136
137        let params = NormalizeParams::new(values, viewport);
138
139        let x = values.x().0.to_user(&params);
140        let y = values.y().0.to_user(&params);
141
142        let w = match values.width().0 {
143            LengthOrAuto::Length(l) => l.to_user(&params),
144            LengthOrAuto::Auto => surface.width() as f64,
145        };
146        let h = match values.height().0 {
147            LengthOrAuto::Length(l) => l.to_user(&params),
148            LengthOrAuto::Auto => surface.height() as f64,
149        };
150
151        let rect = Rect::new(x, y, x + w, y + h);
152
153        let overflow = values.overflow();
154
155        let image = Box::new(layout::Image {
156            surface: surface.clone(),
157            rect,
158            aspect: self.aspect,
159            overflow,
160            image_rendering: values.image_rendering(),
161        });
162
163        let elt = node.borrow_element();
164        let stacking_ctx = StackingContext::new(
165            draw_ctx.session(),
166            acquired_nodes,
167            &elt,
168            values.transform(),
169            None,
170            values,
171        );
172
173        let layer = Layer {
174            kind: LayerKind::Image(image),
175            stacking_ctx,
176        };
177
178        Ok(Some(layer))
179    }
180
181    /// Draw an `<image>` from an SVG image.
182    ///
183    /// Per the [spec], we need to rasterize the SVG ("The result of processing an ‘image’
184    /// is always a four-channel RGBA result.")  and then composite it as if it were a PNG
185    /// or JPEG.
186    ///
187    /// [spec]: https://www.w3.org/TR/SVG2/embedded.html#ImageElement
188    fn layout_from_svg(
189        &self,
190        document: &Document,
191        node: &Node,
192        acquired_nodes: &mut AcquiredNodes<'_>,
193        cascaded: &CascadedValues<'_>,
194        viewport: &Viewport,
195        draw_ctx: &mut DrawingCtx,
196    ) -> Result<Option<Layer>, Box<InternalRenderingError>> {
197        let dimensions = document.get_intrinsic_dimensions();
198
199        let values = cascaded.get();
200
201        let params = NormalizeParams::new(values, viewport);
202
203        let x = values.x().0.to_user(&params);
204        let y = values.y().0.to_user(&params);
205
206        let w = match values.width().0 {
207            LengthOrAuto::Length(l) => l.to_user(&params),
208            LengthOrAuto::Auto => dimensions.width.to_user(&params),
209        };
210
211        let h = match values.height().0 {
212            LengthOrAuto::Length(l) => l.to_user(&params),
213            LengthOrAuto::Auto => dimensions.height.to_user(&params),
214        };
215
216        let rect = Rect::new(x, y, x + w, y + h);
217
218        let overflow = values.overflow();
219
220        let dest_rect = match dimensions.vbox {
221            None => Rect::from_size(w, h),
222            Some(vbox) => self.aspect.compute(&vbox, &Rect::new(x, y, x + w, y + h)),
223        };
224
225        let dest_size = dest_rect.size();
226
227        let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
228
229        // We use ceil() to avoid chopping off the last pixel if it is partially covered.
230        let surface_width = checked_i32(dest_size.0.ceil())?;
231        let surface_height = checked_i32(dest_size.1.ceil())?;
232        let surface =
233            cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
234
235        {
236            let cr = cairo::Context::new(&surface)?;
237
238            let options = draw_ctx.rendering_options(SvgNesting::ReferencedFromImageElement);
239
240            document.render_document(&cr, &cairo::Rectangle::from(surface_dest_rect), &options)?;
241        }
242
243        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
244
245        let image = Box::new(layout::Image {
246            surface,
247            rect,
248            aspect: self.aspect,
249            overflow,
250            image_rendering: values.image_rendering(),
251        });
252
253        let elt = node.borrow_element();
254        let stacking_ctx = StackingContext::new(
255            draw_ctx.session(),
256            acquired_nodes,
257            &elt,
258            values.transform(),
259            None,
260            values,
261        );
262
263        let layer = Layer {
264            kind: LayerKind::Image(image),
265            stacking_ctx,
266        };
267
268        Ok(Some(layer))
269    }
270}
271
272pub fn checked_i32(x: f64) -> Result<i32, cairo::Error> {
273    cast::i32(x).map_err(|_| cairo::Error::InvalidSize)
274}