1
//! The `image` element.
2

            
3
use markup5ever::{expanded_name, local_name, namespace_url, ns};
4

            
5
use crate::aspect_ratio::AspectRatio;
6
use crate::bbox::BoundingBox;
7
use crate::document::{AcquiredNodes, Document, Resource};
8
use crate::drawing_ctx::{DrawingCtx, SvgNesting, Viewport};
9
use crate::element::{set_attribute, ElementTrait};
10
use crate::error::*;
11
use crate::href::{is_href, set_href};
12
use crate::layout::{self, Layer, LayerKind, StackingContext};
13
use crate::length::*;
14
use crate::node::{CascadedValues, Node, NodeBorrow};
15
use crate::parsers::ParseValue;
16
use crate::rect::Rect;
17
use crate::rsvg_log;
18
use crate::session::Session;
19
use crate::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
20
use crate::xml::Attributes;
21

            
22
/// The `<image>` element.
23
///
24
/// Note that its x/y/width/height are properties in SVG2, so they are
25
/// defined as part of [the properties machinery](properties.rs).
26
222
#[derive(Default)]
27
pub struct Image {
28
111
    aspect: AspectRatio,
29
111
    href: Option<String>,
30
}
31

            
32
impl ElementTrait for Image {
33
111
    fn set_attributes(&mut self, attrs: &Attributes, session: &Session) {
34
687
        for (attr, value) in attrs.iter() {
35
576
            match attr.expanded() {
36
                expanded_name!("", "preserveAspectRatio") => {
37
31
                    set_attribute(&mut self.aspect, attr.parse(value), session)
38
                }
39

            
40
                // "path" is used by some older Adobe Illustrator versions
41
545
                ref a if is_href(a) || *a == expanded_name!("", "path") => {
42
110
                    set_href(a, &mut self.href, Some(value.to_string()))
43
                }
44

            
45
                _ => (),
46
            }
47
576
        }
48
111
    }
49

            
50
111
    fn draw(
51
        &self,
52
        node: &Node,
53
        acquired_nodes: &mut AcquiredNodes<'_>,
54
        cascaded: &CascadedValues<'_>,
55
        viewport: &Viewport,
56
        draw_ctx: &mut DrawingCtx,
57
        clipping: bool,
58
    ) -> Result<BoundingBox, InternalRenderingError> {
59
111
        if let Some(ref url) = self.href {
60
110
            self.draw_from_url(
61
110
                url,
62
                node,
63
                acquired_nodes,
64
                cascaded,
65
                viewport,
66
                draw_ctx,
67
                clipping,
68
            )
69
        } else {
70
1
            Ok(draw_ctx.empty_bbox())
71
        }
72
111
    }
73
}
74

            
75
impl Image {
76
110
    fn draw_from_url(
77
        &self,
78
        url: &str,
79
        node: &Node,
80
        acquired_nodes: &mut AcquiredNodes<'_>,
81
        cascaded: &CascadedValues<'_>,
82
        viewport: &Viewport,
83
        draw_ctx: &mut DrawingCtx,
84
        clipping: bool,
85
    ) -> Result<BoundingBox, InternalRenderingError> {
86
110
        match acquired_nodes.lookup_resource(url) {
87
94
            Ok(Resource::Image(surface)) => self.draw_from_surface(
88
                &surface,
89
                node,
90
                acquired_nodes,
91
                cascaded,
92
                viewport,
93
                draw_ctx,
94
                clipping,
95
94
            ),
96

            
97
14
            Ok(Resource::Document(document)) => self.draw_from_svg(
98
14
                &document,
99
                node,
100
                acquired_nodes,
101
                cascaded,
102
                viewport,
103
                draw_ctx,
104
                clipping,
105
14
            ),
106

            
107
2
            Err(e) => {
108
2
                rsvg_log!(
109
2
                    draw_ctx.session(),
110
                    "could not load image \"{}\": {}",
111
                    url,
112
                    e
113
                );
114
2
                Ok(draw_ctx.empty_bbox())
115
2
            }
116
        }
117
110
    }
118

            
119
    /// Draw an `<image>` from a raster image.
120
94
    fn draw_from_surface(
121
        &self,
122
        surface: &SharedImageSurface,
123
        node: &Node,
124
        acquired_nodes: &mut AcquiredNodes<'_>,
125
        cascaded: &CascadedValues<'_>,
126
        viewport: &Viewport,
127
        draw_ctx: &mut DrawingCtx,
128
        clipping: bool,
129
    ) -> Result<BoundingBox, InternalRenderingError> {
130
94
        let values = cascaded.get();
131

            
132
94
        let params = NormalizeParams::new(values, viewport);
133

            
134
94
        let x = values.x().0.to_user(&params);
135
94
        let y = values.y().0.to_user(&params);
136

            
137
94
        let w = match values.width().0 {
138
93
            LengthOrAuto::Length(l) => l.to_user(&params),
139
1
            LengthOrAuto::Auto => surface.width() as f64,
140
        };
141
94
        let h = match values.height().0 {
142
93
            LengthOrAuto::Length(l) => l.to_user(&params),
143
1
            LengthOrAuto::Auto => surface.height() as f64,
144
        };
145

            
146
94
        let is_visible = values.is_visible();
147

            
148
94
        let rect = Rect::new(x, y, x + w, y + h);
149

            
150
94
        let overflow = values.overflow();
151

            
152
94
        let image = Box::new(layout::Image {
153
94
            surface: surface.clone(),
154
            is_visible,
155
            rect,
156
94
            aspect: self.aspect,
157
            overflow,
158
94
            image_rendering: values.image_rendering(),
159
94
        });
160

            
161
94
        let elt = node.borrow_element();
162
94
        let stacking_ctx = StackingContext::new(
163
94
            draw_ctx.session(),
164
            acquired_nodes,
165
94
            &elt,
166
94
            values.transform(),
167
94
            None,
168
            values,
169
        );
170

            
171
94
        let layer = Layer {
172
94
            kind: LayerKind::Image(image),
173
            stacking_ctx,
174
        };
175

            
176
94
        draw_ctx.draw_layer(&layer, acquired_nodes, clipping, viewport)
177
94
    }
178

            
179
    /// Draw an `<image>` from an SVG image.
180
    ///
181
    /// Per the [spec], we need to rasterize the SVG ("The result of processing an ‘image’
182
    /// is always a four-channel RGBA result.")  and then composite it as if it were a PNG
183
    /// or JPEG.
184
    ///
185
    /// [spec]: https://www.w3.org/TR/SVG2/embedded.html#ImageElement
186
14
    fn draw_from_svg(
187
        &self,
188
        document: &Document,
189
        node: &Node,
190
        acquired_nodes: &mut AcquiredNodes<'_>,
191
        cascaded: &CascadedValues<'_>,
192
        viewport: &Viewport,
193
        draw_ctx: &mut DrawingCtx,
194
        clipping: bool,
195
    ) -> Result<BoundingBox, InternalRenderingError> {
196
14
        let dimensions = document.get_intrinsic_dimensions();
197

            
198
14
        let values = cascaded.get();
199

            
200
14
        let params = NormalizeParams::new(values, viewport);
201

            
202
14
        let x = values.x().0.to_user(&params);
203
14
        let y = values.y().0.to_user(&params);
204

            
205
14
        let w = match values.width().0 {
206
14
            LengthOrAuto::Length(l) => l.to_user(&params),
207
            LengthOrAuto::Auto => dimensions.width.to_user(&params),
208
        };
209

            
210
14
        let h = match values.height().0 {
211
14
            LengthOrAuto::Length(l) => l.to_user(&params),
212
            LengthOrAuto::Auto => dimensions.height.to_user(&params),
213
        };
214

            
215
14
        let is_visible = values.is_visible();
216

            
217
14
        let rect = Rect::new(x, y, x + w, y + h);
218

            
219
14
        let overflow = values.overflow();
220

            
221
14
        let dest_rect = match dimensions.vbox {
222
            None => Rect::from_size(w, h),
223
14
            Some(vbox) => self.aspect.compute(&vbox, &Rect::new(x, y, x + w, y + h)),
224
        };
225

            
226
14
        let dest_size = dest_rect.size();
227

            
228
14
        let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
229

            
230
        // We use ceil() to avoid chopping off the last pixel if it is partially covered.
231
14
        let surface_width = checked_i32(dest_size.0.ceil())?;
232
14
        let surface_height = checked_i32(dest_size.1.ceil())?;
233
        let surface =
234
14
            cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
235

            
236
        {
237
14
            let cr = cairo::Context::new(&surface)?;
238

            
239
28
            document.render_document(
240
14
                draw_ctx.session(),
241
                &cr,
242
14
                &cairo::Rectangle::from(surface_dest_rect),
243
14
                draw_ctx.user_language(),
244
14
                viewport.dpi,
245
14
                SvgNesting::ReferencedFromImageElement,
246
14
                draw_ctx.is_testing(),
247
            )?;
248
14
        }
249

            
250
14
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
251

            
252
14
        let image = Box::new(layout::Image {
253
14
            surface,
254
            is_visible,
255
            rect,
256
14
            aspect: self.aspect,
257
            overflow,
258
14
            image_rendering: values.image_rendering(),
259
14
        });
260

            
261
14
        let elt = node.borrow_element();
262
14
        let stacking_ctx = StackingContext::new(
263
14
            draw_ctx.session(),
264
            acquired_nodes,
265
14
            &elt,
266
14
            values.transform(),
267
14
            None,
268
            values,
269
        );
270

            
271
14
        let layer = Layer {
272
14
            kind: LayerKind::Image(image),
273
            stacking_ctx,
274
        };
275

            
276
14
        draw_ctx.draw_layer(&layer, acquired_nodes, clipping, viewport)
277
14
    }
278
}
279

            
280
28
fn checked_i32(x: f64) -> Result<i32, cairo::Error> {
281
28
    cast::i32(x).map_err(|_| cairo::Error::InvalidSize)
282
28
}