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 layout(
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<Option<Layer>, InternalRenderingError> {
59
111
        if let Some(ref url) = self.href {
60
110
            self.layout_from_url(url, node, acquired_nodes, cascaded, viewport, draw_ctx)
61
        } else {
62
1
            Ok(None)
63
        }
64
111
    }
65

            
66
111
    fn draw(
67
        &self,
68
        node: &Node,
69
        acquired_nodes: &mut AcquiredNodes<'_>,
70
        cascaded: &CascadedValues<'_>,
71
        viewport: &Viewport,
72
        draw_ctx: &mut DrawingCtx,
73
        clipping: bool,
74
    ) -> Result<BoundingBox, InternalRenderingError> {
75
111
        let layer = self.layout(node, acquired_nodes, cascaded, viewport, draw_ctx, clipping)?;
76

            
77
114
        if let Some(layer) = layer {
78
108
            draw_ctx.draw_layer(&layer, acquired_nodes, clipping, viewport)
79
108
        } else {
80
3
            Ok(draw_ctx.empty_bbox())
81
        }
82
111
    }
83
}
84

            
85
impl Image {
86
110
    fn layout_from_url(
87
        &self,
88
        url: &str,
89
        node: &Node,
90
        acquired_nodes: &mut AcquiredNodes<'_>,
91
        cascaded: &CascadedValues<'_>,
92
        viewport: &Viewport,
93
        draw_ctx: &mut DrawingCtx,
94
    ) -> Result<Option<Layer>, InternalRenderingError> {
95
110
        match acquired_nodes.lookup_resource(url) {
96
94
            Ok(Resource::Image(surface)) => self.layout_from_surface(
97
                &surface,
98
                node,
99
                acquired_nodes,
100
                cascaded,
101
                viewport,
102
                draw_ctx,
103
94
            ),
104

            
105
14
            Ok(Resource::Document(document)) => self.layout_from_svg(
106
14
                &document,
107
                node,
108
                acquired_nodes,
109
                cascaded,
110
                viewport,
111
                draw_ctx,
112
14
            ),
113

            
114
2
            Err(e) => {
115
2
                rsvg_log!(
116
2
                    draw_ctx.session(),
117
                    "could not load image \"{}\": {}",
118
                    url,
119
                    e
120
                );
121
2
                Ok(None)
122
2
            }
123
        }
124
110
    }
125

            
126
    /// Draw an `<image>` from a raster image.
127
94
    fn layout_from_surface(
128
        &self,
129
        surface: &SharedImageSurface,
130
        node: &Node,
131
        acquired_nodes: &mut AcquiredNodes<'_>,
132
        cascaded: &CascadedValues<'_>,
133
        viewport: &Viewport,
134
        draw_ctx: &mut DrawingCtx,
135
    ) -> Result<Option<Layer>, InternalRenderingError> {
136
94
        let values = cascaded.get();
137

            
138
94
        let params = NormalizeParams::new(values, viewport);
139

            
140
94
        let x = values.x().0.to_user(&params);
141
94
        let y = values.y().0.to_user(&params);
142

            
143
94
        let w = match values.width().0 {
144
93
            LengthOrAuto::Length(l) => l.to_user(&params),
145
1
            LengthOrAuto::Auto => surface.width() as f64,
146
        };
147
94
        let h = match values.height().0 {
148
93
            LengthOrAuto::Length(l) => l.to_user(&params),
149
1
            LengthOrAuto::Auto => surface.height() as f64,
150
        };
151

            
152
94
        let is_visible = values.is_visible();
153

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

            
156
94
        let overflow = values.overflow();
157

            
158
94
        let image = Box::new(layout::Image {
159
94
            surface: surface.clone(),
160
            is_visible,
161
            rect,
162
94
            aspect: self.aspect,
163
            overflow,
164
94
            image_rendering: values.image_rendering(),
165
94
        });
166

            
167
94
        let elt = node.borrow_element();
168
94
        let stacking_ctx = StackingContext::new(
169
94
            draw_ctx.session(),
170
            acquired_nodes,
171
94
            &elt,
172
94
            values.transform(),
173
94
            None,
174
            values,
175
        );
176

            
177
94
        let layer = Layer {
178
94
            kind: LayerKind::Image(image),
179
            stacking_ctx,
180
        };
181

            
182
94
        Ok(Some(layer))
183
94
    }
184

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

            
203
14
        let values = cascaded.get();
204

            
205
14
        let params = NormalizeParams::new(values, viewport);
206

            
207
14
        let x = values.x().0.to_user(&params);
208
14
        let y = values.y().0.to_user(&params);
209

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

            
215
14
        let h = match values.height().0 {
216
14
            LengthOrAuto::Length(l) => l.to_user(&params),
217
            LengthOrAuto::Auto => dimensions.height.to_user(&params),
218
        };
219

            
220
14
        let is_visible = values.is_visible();
221

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

            
224
14
        let overflow = values.overflow();
225

            
226
14
        let dest_rect = match dimensions.vbox {
227
            None => Rect::from_size(w, h),
228
14
            Some(vbox) => self.aspect.compute(&vbox, &Rect::new(x, y, x + w, y + h)),
229
        };
230

            
231
14
        let dest_size = dest_rect.size();
232

            
233
14
        let surface_dest_rect = Rect::from_size(dest_size.0, dest_size.1);
234

            
235
        // We use ceil() to avoid chopping off the last pixel if it is partially covered.
236
14
        let surface_width = checked_i32(dest_size.0.ceil())?;
237
14
        let surface_height = checked_i32(dest_size.1.ceil())?;
238
        let surface =
239
14
            cairo::ImageSurface::create(cairo::Format::ARgb32, surface_width, surface_height)?;
240

            
241
        {
242
14
            let cr = cairo::Context::new(&surface)?;
243

            
244
28
            document.render_document(
245
14
                draw_ctx.session(),
246
                &cr,
247
14
                &cairo::Rectangle::from(surface_dest_rect),
248
14
                draw_ctx.user_language(),
249
14
                viewport.dpi,
250
14
                SvgNesting::ReferencedFromImageElement,
251
14
                draw_ctx.is_testing(),
252
            )?;
253
14
        }
254

            
255
14
        let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
256

            
257
14
        let image = Box::new(layout::Image {
258
14
            surface,
259
            is_visible,
260
            rect,
261
14
            aspect: self.aspect,
262
            overflow,
263
14
            image_rendering: values.image_rendering(),
264
14
        });
265

            
266
14
        let elt = node.borrow_element();
267
14
        let stacking_ctx = StackingContext::new(
268
14
            draw_ctx.session(),
269
            acquired_nodes,
270
14
            &elt,
271
14
            values.transform(),
272
14
            None,
273
            values,
274
        );
275

            
276
14
        let layer = Layer {
277
14
            kind: LayerKind::Image(image),
278
            stacking_ctx,
279
        };
280

            
281
14
        Ok(Some(layer))
282
14
    }
283
}
284

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