/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(¶ms); |
139 | 1.29k | let y = values.y().0.to_user(¶ms); |
140 | | |
141 | 1.29k | let w = match values.width().0 { |
142 | 1.17k | LengthOrAuto::Length(l) => l.to_user(¶ms), |
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(¶ms), |
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(¶ms); |
204 | 0 | let y = values.y().0.to_user(¶ms); |
205 | | |
206 | 0 | let w = match values.width().0 { |
207 | 0 | LengthOrAuto::Length(l) => l.to_user(¶ms), |
208 | 0 | LengthOrAuto::Auto => dimensions.width.to_user(¶ms), |
209 | | }; |
210 | | |
211 | 0 | let h = match values.height().0 { |
212 | 0 | LengthOrAuto::Length(l) => l.to_user(¶ms), |
213 | 0 | LengthOrAuto::Auto => dimensions.height.to_user(¶ms), |
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 | } |