Coverage Report

Created: 2026-06-30 06:37

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