/src/librsvg/rsvg/src/structure.rs
Line | Count | Source |
1 | | //! Structural elements in SVG: the `g`, `switch`, `svg`, `use`, `symbol`, `clip_path`, `mask`, `link` elements. |
2 | | |
3 | | use markup5ever::{expanded_name, local_name, ns}; |
4 | | |
5 | | use crate::aspect_ratio::*; |
6 | | use crate::bbox::BoundingBox; |
7 | | use crate::coord_units; |
8 | | use crate::coord_units::CoordUnits; |
9 | | use crate::document::{AcquiredNodes, NodeId}; |
10 | | use crate::drawing_ctx::{DrawingCtx, SvgNesting, Viewport}; |
11 | | use crate::element::{DrawResult, ElementData, ElementTrait, set_attribute}; |
12 | | use crate::error::*; |
13 | | use crate::href::{is_href, set_href}; |
14 | | use crate::layout::{self, Layer, LayerKind, LayoutViewport, StackingContext}; |
15 | | use crate::length::*; |
16 | | use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw}; |
17 | | use crate::parsers::{Parse, ParseValue}; |
18 | | use crate::properties::ComputedValues; |
19 | | use crate::rect::Rect; |
20 | | use crate::session::Session; |
21 | | use crate::viewbox::*; |
22 | | use crate::xml::Attributes; |
23 | | |
24 | | #[derive(Default)] |
25 | | pub struct Group(); |
26 | | |
27 | | impl ElementTrait for Group { |
28 | 16.9k | fn draw( |
29 | 16.9k | &self, |
30 | 16.9k | node: &Node, |
31 | 16.9k | acquired_nodes: &mut AcquiredNodes<'_>, |
32 | 16.9k | cascaded: &CascadedValues<'_>, |
33 | 16.9k | viewport: &Viewport, |
34 | 16.9k | draw_ctx: &mut DrawingCtx, |
35 | 16.9k | _clipping: bool, |
36 | 16.9k | ) -> DrawResult { |
37 | 16.9k | let values = cascaded.get(); |
38 | | |
39 | 16.9k | let elt = node.borrow_element(); |
40 | 16.9k | let stacking_ctx = Box::new(StackingContext::new( |
41 | 16.9k | draw_ctx, |
42 | 16.9k | acquired_nodes, |
43 | 16.9k | &elt, |
44 | 16.9k | values.transform(), |
45 | 16.9k | None, |
46 | 16.9k | values, |
47 | 16.9k | viewport, |
48 | | )); |
49 | | |
50 | 16.9k | draw_ctx.with_discrete_layer( |
51 | 16.9k | &stacking_ctx, |
52 | 16.9k | acquired_nodes, |
53 | 16.9k | viewport, |
54 | 16.9k | None, |
55 | | false, |
56 | 16.8k | &mut |an, dc, new_viewport| node.draw_children(an, cascaded, new_viewport, dc), |
57 | | ) |
58 | 16.9k | } |
59 | | |
60 | 0 | fn layout( |
61 | 0 | &self, |
62 | 0 | node: &Node, |
63 | 0 | acquired_nodes: &mut AcquiredNodes<'_>, |
64 | 0 | cascaded: &CascadedValues<'_>, |
65 | 0 | viewport: &Viewport, |
66 | 0 | draw_ctx: &mut DrawingCtx, |
67 | 0 | ) -> Result<Option<Layer>, Box<InternalRenderingError>> { |
68 | 0 | let mut child_layers = Vec::new(); |
69 | | |
70 | 0 | for child in node.children().filter(|c| c.is_element()) { |
71 | 0 | let elt = child.borrow_element(); |
72 | | |
73 | 0 | let layer = elt.layout( |
74 | 0 | &child, |
75 | 0 | acquired_nodes, |
76 | 0 | &CascadedValues::clone_with_node(cascaded, &child), |
77 | 0 | viewport, |
78 | 0 | draw_ctx, |
79 | 0 | )?; |
80 | | |
81 | 0 | if let Some(layer) = layer { |
82 | 0 | child_layers.push(layer); |
83 | 0 | } |
84 | | } |
85 | | |
86 | 0 | self.layout_with_children( |
87 | 0 | draw_ctx, |
88 | 0 | node, |
89 | 0 | acquired_nodes, |
90 | 0 | cascaded, |
91 | 0 | child_layers, |
92 | 0 | viewport, |
93 | | ) |
94 | 0 | } |
95 | | } |
96 | | |
97 | 0 | fn extents_of_transformed_children(layers: &[Layer]) -> Option<Rect> { |
98 | 0 | let mut result_bbox = BoundingBox::new(); |
99 | | |
100 | 0 | for layer in layers { |
101 | 0 | if let Some(extents) = layer.kind.local_extents() { |
102 | 0 | let bbox = BoundingBox::new() |
103 | 0 | .with_transform(layer.stacking_ctx.transform) |
104 | 0 | .with_rect(extents); |
105 | 0 | result_bbox.insert(&bbox); |
106 | 0 | } |
107 | | } |
108 | | |
109 | 0 | result_bbox.rect |
110 | 0 | } |
111 | | |
112 | | impl Group { |
113 | 0 | fn layout_with_children( |
114 | 0 | &self, |
115 | 0 | draw_ctx: &DrawingCtx, |
116 | 0 | node: &Node, |
117 | 0 | acquired_nodes: &mut AcquiredNodes<'_>, |
118 | 0 | cascaded: &CascadedValues<'_>, |
119 | 0 | child_layers: Vec<Layer>, |
120 | 0 | viewport: &Viewport, |
121 | 0 | ) -> Result<Option<Layer>, Box<InternalRenderingError>> { |
122 | 0 | let values = cascaded.get(); |
123 | | |
124 | 0 | let extents = extents_of_transformed_children(&child_layers); |
125 | | |
126 | 0 | let group = Box::new(layout::Group { |
127 | 0 | children: child_layers, |
128 | 0 | establish_viewport: None, |
129 | 0 | extents, |
130 | 0 | }); |
131 | | |
132 | 0 | let elt = node.borrow_element(); |
133 | 0 | let stacking_ctx = StackingContext::new( |
134 | 0 | draw_ctx, |
135 | 0 | acquired_nodes, |
136 | 0 | &elt, |
137 | 0 | values.transform(), |
138 | 0 | None, |
139 | 0 | values, |
140 | 0 | viewport, |
141 | | ); |
142 | | |
143 | 0 | Ok(Some(Layer { |
144 | 0 | kind: LayerKind::Group(group), |
145 | 0 | stacking_ctx, |
146 | 0 | })) |
147 | 0 | } |
148 | | } |
149 | | |
150 | | /// A no-op node that does not render anything |
151 | | /// |
152 | | /// Sometimes we just need a node that can contain children, but doesn't |
153 | | /// render itself or its children. This is just that kind of node. |
154 | | #[derive(Default)] |
155 | | pub struct NonRendering; |
156 | | |
157 | | impl ElementTrait for NonRendering {} |
158 | | |
159 | | /// The `<switch>` element. |
160 | | #[derive(Default)] |
161 | | pub struct Switch(); |
162 | | |
163 | | impl ElementTrait for Switch { |
164 | 0 | fn draw( |
165 | 0 | &self, |
166 | 0 | node: &Node, |
167 | 0 | acquired_nodes: &mut AcquiredNodes<'_>, |
168 | 0 | cascaded: &CascadedValues<'_>, |
169 | 0 | viewport: &Viewport, |
170 | 0 | draw_ctx: &mut DrawingCtx, |
171 | 0 | _clipping: bool, |
172 | 0 | ) -> DrawResult { |
173 | 0 | let values = cascaded.get(); |
174 | | |
175 | 0 | let switch_elt = node.borrow_element(); |
176 | 0 | let child_that_matches = node.children().filter(|c| c.is_element()).find(|c| { |
177 | 0 | let elt = c.borrow_element(); |
178 | 0 | elt.get_cond(draw_ctx.user_language(), draw_ctx.session()) |
179 | 0 | }); |
180 | | |
181 | 0 | if let Some(child) = child_that_matches { |
182 | 0 | let stacking_ctx = Box::new(StackingContext::new( |
183 | 0 | draw_ctx, |
184 | 0 | acquired_nodes, |
185 | 0 | &switch_elt, |
186 | 0 | values.transform(), |
187 | 0 | None, |
188 | 0 | values, |
189 | 0 | viewport, |
190 | | )); |
191 | | |
192 | 0 | draw_ctx.with_discrete_layer( |
193 | 0 | &stacking_ctx, |
194 | 0 | acquired_nodes, |
195 | 0 | viewport, |
196 | 0 | None, |
197 | | false, |
198 | 0 | &mut |an, dc, new_viewport| { |
199 | 0 | child.draw( |
200 | 0 | an, |
201 | 0 | &CascadedValues::clone_with_node(cascaded, &child), |
202 | 0 | new_viewport, |
203 | 0 | dc, |
204 | | false, |
205 | | ) |
206 | 0 | }, |
207 | | ) |
208 | | } else { |
209 | 0 | Ok(viewport.empty_bbox()) |
210 | | } |
211 | 0 | } |
212 | | } |
213 | | |
214 | | /// Intrinsic dimensions of an SVG document fragment: its `width/height` properties and `viewBox` attribute. |
215 | | /// |
216 | | /// Note that in SVG2, `width` and `height` are properties, not |
217 | | /// attributes. If either is omitted, it defaults to `auto`. which |
218 | | /// computes to `100%`. |
219 | | /// |
220 | | /// The `viewBox` attribute can also be omitted, hence an `Option`. |
221 | | #[derive(Debug, Copy, Clone, PartialEq)] |
222 | | pub struct IntrinsicDimensions { |
223 | | /// Computed value of the `width` property. |
224 | | pub width: ULength<Horizontal>, |
225 | | |
226 | | /// Computed value of the `height` property. |
227 | | pub height: ULength<Vertical>, |
228 | | |
229 | | /// Contents of the `viewBox` attribute. |
230 | | pub vbox: Option<ViewBox>, |
231 | | } |
232 | | |
233 | | /// The `<svg>` element. |
234 | | /// |
235 | | /// Note that its x/y/width/height are properties in SVG2, so they are |
236 | | /// defined as part of [the properties machinery](properties.rs). |
237 | | #[derive(Default)] |
238 | | pub struct Svg { |
239 | | preserve_aspect_ratio: AspectRatio, |
240 | | vbox: Option<ViewBox>, |
241 | | } |
242 | | |
243 | | impl Svg { |
244 | 0 | pub fn get_intrinsic_dimensions(&self, values: &ComputedValues) -> IntrinsicDimensions { |
245 | 0 | let w = match values.width().0 { |
246 | 0 | LengthOrAuto::Auto => ULength::<Horizontal>::parse_str("100%").unwrap(), |
247 | 0 | LengthOrAuto::Length(l) => l, |
248 | | }; |
249 | | |
250 | 0 | let h = match values.height().0 { |
251 | 0 | LengthOrAuto::Auto => ULength::<Vertical>::parse_str("100%").unwrap(), |
252 | 0 | LengthOrAuto::Length(l) => l, |
253 | | }; |
254 | | |
255 | 0 | IntrinsicDimensions { |
256 | 0 | width: w, |
257 | 0 | height: h, |
258 | 0 | vbox: self.vbox, |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | 47.3k | fn get_unnormalized_offset( |
263 | 47.3k | &self, |
264 | 47.3k | values: &ComputedValues, |
265 | 47.3k | ) -> (Length<Horizontal>, Length<Vertical>) { |
266 | | // these defaults are per the spec |
267 | 47.3k | let x = values.x().0; |
268 | 47.3k | let y = values.y().0; |
269 | | |
270 | 47.3k | (x, y) |
271 | 47.3k | } |
272 | | |
273 | 47.4k | fn get_unnormalized_size( |
274 | 47.4k | &self, |
275 | 47.4k | values: &ComputedValues, |
276 | 47.4k | ) -> (ULength<Horizontal>, ULength<Vertical>) { |
277 | | // these defaults are per the spec |
278 | 47.4k | let w = match values.width().0 { |
279 | 46.5k | LengthOrAuto::Auto => ULength::<Horizontal>::parse_str("100%").unwrap(), |
280 | 975 | LengthOrAuto::Length(l) => l, |
281 | | }; |
282 | 47.4k | let h = match values.height().0 { |
283 | 47.4k | LengthOrAuto::Auto => ULength::<Vertical>::parse_str("100%").unwrap(), |
284 | 71 | LengthOrAuto::Length(l) => l, |
285 | | }; |
286 | 47.4k | (w, h) |
287 | 47.4k | } |
288 | | |
289 | 47.4k | fn get_viewport( |
290 | 47.4k | &self, |
291 | 47.4k | params: &NormalizeParams, |
292 | 47.4k | values: &ComputedValues, |
293 | 47.4k | outermost: bool, |
294 | 47.4k | ) -> Rect { |
295 | | // x & y attributes have no effect on outermost svg |
296 | | // http://www.w3.org/TR/SVG/struct.html#SVGElement |
297 | 47.4k | let (nx, ny) = if outermost { |
298 | 188 | (0.0, 0.0) |
299 | | } else { |
300 | 47.3k | let (x, y) = self.get_unnormalized_offset(values); |
301 | 47.3k | (x.to_user(params), y.to_user(params)) |
302 | | }; |
303 | | |
304 | 47.4k | let (w, h) = self.get_unnormalized_size(values); |
305 | 47.4k | let (nw, nh) = (w.to_user(params), h.to_user(params)); |
306 | | |
307 | 47.4k | Rect::new(nx, ny, nx + nw, ny + nh) |
308 | 47.4k | } |
309 | | |
310 | 0 | pub fn get_viewbox(&self) -> Option<ViewBox> { |
311 | 0 | self.vbox |
312 | 0 | } |
313 | | |
314 | 0 | pub fn get_preserve_aspect_ratio(&self) -> AspectRatio { |
315 | 0 | self.preserve_aspect_ratio |
316 | 0 | } |
317 | | |
318 | 47.4k | fn make_svg_viewport( |
319 | 47.4k | &self, |
320 | 47.4k | node: &Node, |
321 | 47.4k | cascaded: &CascadedValues<'_>, |
322 | 47.4k | current_viewport: &Viewport, |
323 | 47.4k | draw_ctx: &mut DrawingCtx, |
324 | 47.4k | ) -> LayoutViewport { |
325 | 47.4k | let values = cascaded.get(); |
326 | | |
327 | 47.4k | let params = NormalizeParams::new(values, current_viewport); |
328 | | |
329 | 47.4k | let has_parent = node.parent().is_some(); |
330 | | |
331 | | // From https://www.w3.org/TR/SVG2/embedded.html#ImageElement: |
332 | | // |
333 | | // For `image` elements embedding an SVG image, the `preserveAspectRatio` |
334 | | // attribute on the root element in the referenced SVG image must be ignored, |
335 | | // and instead treated as if it had a value of `none`. (see |
336 | | // `preserveAspectRatio` for details). This ensures that the |
337 | | // `preserveAspectRatio` attribute on the referencing `image` has its |
338 | | // intended effect, even if it is none. |
339 | | // |
340 | 47.4k | let preserve_aspect_ratio = match (has_parent, draw_ctx.svg_nesting()) { |
341 | | // we are a toplevel, and referenced from <image> => preserveAspectRatio=none |
342 | 0 | (false, SvgNesting::ReferencedFromImageElement) => AspectRatio::none(), |
343 | | |
344 | | // otherwise just use our specified preserveAspectRatio |
345 | 47.4k | _ => self.preserve_aspect_ratio, |
346 | | }; |
347 | | |
348 | 47.4k | let svg_viewport = self.get_viewport(¶ms, values, !has_parent); |
349 | | |
350 | 47.4k | let is_measuring_toplevel_svg = !has_parent && draw_ctx.is_measuring(); |
351 | | |
352 | 47.4k | let (geometry, vbox) = if is_measuring_toplevel_svg { |
353 | | // We are obtaining the toplevel SVG's geometry. This means, don't care about the |
354 | | // DrawingCtx's viewport, just use the SVG's intrinsic dimensions and see how far |
355 | | // it wants to extend. |
356 | 0 | (svg_viewport, self.vbox) |
357 | | } else { |
358 | | ( |
359 | | // The client's viewport overrides the toplevel's x/y/w/h viewport |
360 | 47.4k | if has_parent { |
361 | 47.3k | svg_viewport |
362 | | } else { |
363 | 188 | draw_ctx.toplevel_viewport() |
364 | | }, |
365 | | // Use our viewBox if available, or try to derive one from |
366 | | // the intrinsic dimensions. |
367 | 47.4k | self.vbox.or_else(|| { |
368 | 47.4k | Some(ViewBox::from(Rect::from_size( |
369 | 47.4k | svg_viewport.width(), |
370 | 47.4k | svg_viewport.height(), |
371 | 47.4k | ))) |
372 | 47.4k | }), |
373 | | ) |
374 | | }; |
375 | | |
376 | 47.4k | LayoutViewport { |
377 | 47.4k | geometry, |
378 | 47.4k | vbox, |
379 | 47.4k | preserve_aspect_ratio, |
380 | 47.4k | overflow: values.overflow(), |
381 | 47.4k | } |
382 | 47.4k | } |
383 | | } |
384 | | |
385 | | impl ElementTrait for Svg { |
386 | 46.8k | fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { |
387 | 58.2k | for (attr, value) in attrs.iter() { |
388 | 58.2k | match attr.expanded() { |
389 | | expanded_name!("", "preserveAspectRatio") => { |
390 | 1.25k | set_attribute(&mut self.preserve_aspect_ratio, attr.parse(value), session) |
391 | | } |
392 | | expanded_name!("", "viewBox") => { |
393 | 89 | set_attribute(&mut self.vbox, attr.parse(value), session) |
394 | | } |
395 | 56.9k | _ => (), |
396 | | } |
397 | | } |
398 | 46.8k | } |
399 | | |
400 | 47.4k | fn draw( |
401 | 47.4k | &self, |
402 | 47.4k | node: &Node, |
403 | 47.4k | acquired_nodes: &mut AcquiredNodes<'_>, |
404 | 47.4k | cascaded: &CascadedValues<'_>, |
405 | 47.4k | viewport: &Viewport, |
406 | 47.4k | draw_ctx: &mut DrawingCtx, |
407 | 47.4k | _clipping: bool, |
408 | 47.4k | ) -> DrawResult { |
409 | 47.4k | let values = cascaded.get(); |
410 | | |
411 | 47.4k | let elt = node.borrow_element(); |
412 | 47.4k | let stacking_ctx = Box::new(StackingContext::new( |
413 | 47.4k | draw_ctx, |
414 | 47.4k | acquired_nodes, |
415 | 47.4k | &elt, |
416 | 47.4k | values.transform(), |
417 | 47.4k | None, |
418 | 47.4k | values, |
419 | 47.4k | viewport, |
420 | | )); |
421 | | |
422 | 47.4k | let layout_viewport = self.make_svg_viewport(node, cascaded, viewport, draw_ctx); |
423 | | |
424 | 47.4k | draw_ctx.with_discrete_layer( |
425 | 47.4k | &stacking_ctx, |
426 | 47.4k | acquired_nodes, |
427 | 47.4k | viewport, |
428 | 47.4k | Some(layout_viewport), |
429 | | false, |
430 | 47.4k | &mut |an, dc, new_viewport| node.draw_children(an, cascaded, new_viewport, dc), |
431 | | ) |
432 | 47.4k | } |
433 | | |
434 | 0 | fn layout( |
435 | 0 | &self, |
436 | 0 | node: &Node, |
437 | 0 | acquired_nodes: &mut AcquiredNodes<'_>, |
438 | 0 | cascaded: &CascadedValues<'_>, |
439 | 0 | viewport: &Viewport, |
440 | 0 | draw_ctx: &mut DrawingCtx, |
441 | 0 | ) -> Result<Option<Layer>, Box<InternalRenderingError>> { |
442 | 0 | let mut child_layers = Vec::new(); |
443 | | |
444 | 0 | for child in node.children().filter(|c| c.is_element()) { |
445 | 0 | let elt = child.borrow_element(); |
446 | | |
447 | 0 | let layer = elt.layout( |
448 | 0 | &child, |
449 | 0 | acquired_nodes, |
450 | 0 | &CascadedValues::clone_with_node(cascaded, &child), |
451 | 0 | viewport, |
452 | 0 | draw_ctx, |
453 | 0 | )?; |
454 | | |
455 | 0 | if let Some(layer) = layer { |
456 | 0 | child_layers.push(layer); |
457 | 0 | } |
458 | | } |
459 | | |
460 | 0 | let values = cascaded.get(); |
461 | 0 | let extents = extents_of_transformed_children(&child_layers); |
462 | | |
463 | 0 | let layout_viewport = self.make_svg_viewport(node, cascaded, viewport, draw_ctx); |
464 | | |
465 | 0 | let group = Box::new(layout::Group { |
466 | 0 | children: child_layers, |
467 | 0 | establish_viewport: Some(layout_viewport), |
468 | 0 | extents, |
469 | 0 | }); |
470 | | |
471 | 0 | let elt = node.borrow_element(); |
472 | 0 | let stacking_ctx = StackingContext::new( |
473 | 0 | draw_ctx, |
474 | 0 | acquired_nodes, |
475 | 0 | &elt, |
476 | 0 | values.transform(), |
477 | 0 | None, |
478 | 0 | values, |
479 | 0 | viewport, |
480 | | ); |
481 | | |
482 | 0 | Ok(Some(Layer { |
483 | 0 | kind: LayerKind::Group(group), |
484 | 0 | stacking_ctx, |
485 | 0 | })) |
486 | 0 | } |
487 | | } |
488 | | |
489 | | /// The `<use>` element. |
490 | | pub struct Use { |
491 | | link: Option<NodeId>, |
492 | | x: Length<Horizontal>, |
493 | | y: Length<Vertical>, |
494 | | width: ULength<Horizontal>, |
495 | | height: ULength<Vertical>, |
496 | | } |
497 | | |
498 | | impl Use { |
499 | 1.36k | pub fn get_rect(&self, params: &NormalizeParams) -> Rect { |
500 | 1.36k | let x = self.x.to_user(params); |
501 | 1.36k | let y = self.y.to_user(params); |
502 | 1.36k | let w = self.width.to_user(params); |
503 | 1.36k | let h = self.height.to_user(params); |
504 | | |
505 | 1.36k | Rect::new(x, y, x + w, y + h) |
506 | 1.36k | } |
507 | | |
508 | 21 | pub fn get_link(&self) -> Option<NodeId> { |
509 | 21 | self.link.clone() |
510 | 21 | } |
511 | | } |
512 | | |
513 | | impl Default for Use { |
514 | 1.49k | fn default() -> Use { |
515 | 1.49k | Use { |
516 | 1.49k | link: None, |
517 | 1.49k | x: Default::default(), |
518 | 1.49k | y: Default::default(), |
519 | 1.49k | width: ULength::<Horizontal>::parse_str("100%").unwrap(), |
520 | 1.49k | height: ULength::<Vertical>::parse_str("100%").unwrap(), |
521 | 1.49k | } |
522 | 1.49k | } |
523 | | } |
524 | | |
525 | | impl ElementTrait for Use { |
526 | 1.49k | fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { |
527 | 2.93k | for (attr, value) in attrs.iter() { |
528 | 2.93k | match attr.expanded() { |
529 | 2.93k | ref a if is_href(a) => { |
530 | 1.39k | let mut href = None; |
531 | 1.39k | set_attribute( |
532 | 1.39k | &mut href, |
533 | 1.39k | NodeId::parse(value).map(Some).attribute(attr.clone()), |
534 | 1.39k | session, |
535 | 1.39k | ); |
536 | 1.39k | set_href(a, &mut self.link, href); |
537 | 1.39k | } |
538 | 0 | expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session), |
539 | 0 | expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session), |
540 | | expanded_name!("", "width") => { |
541 | 0 | set_attribute(&mut self.width, attr.parse(value), session) |
542 | | } |
543 | | expanded_name!("", "height") => { |
544 | 517 | set_attribute(&mut self.height, attr.parse(value), session) |
545 | | } |
546 | 1.02k | _ => (), |
547 | | } |
548 | | } |
549 | 1.49k | } |
550 | | |
551 | 1.49k | fn draw( |
552 | 1.49k | &self, |
553 | 1.49k | node: &Node, |
554 | 1.49k | acquired_nodes: &mut AcquiredNodes<'_>, |
555 | 1.49k | cascaded: &CascadedValues<'_>, |
556 | 1.49k | viewport: &Viewport, |
557 | 1.49k | draw_ctx: &mut DrawingCtx, |
558 | 1.49k | clipping: bool, |
559 | 1.49k | ) -> DrawResult { |
560 | 1.49k | if let Some(link) = self.link.as_ref() { |
561 | 1.34k | let values = cascaded.get(); |
562 | 1.34k | let params = NormalizeParams::new(values, viewport); |
563 | 1.34k | let rect = self.get_rect(¶ms); |
564 | | |
565 | 1.34k | let use_node_name = format!("{node}"); |
566 | | |
567 | 1.34k | let stroke_paint = values.stroke().0.resolve( |
568 | 1.34k | acquired_nodes, |
569 | 1.34k | &use_node_name, |
570 | 1.34k | values.stroke_opacity().0, |
571 | 1.34k | values.color().0, |
572 | 1.34k | cascaded.context_fill.clone(), |
573 | 1.34k | cascaded.context_stroke.clone(), |
574 | 1.34k | draw_ctx.session(), |
575 | | ); |
576 | | |
577 | 1.34k | let fill_paint = values.fill().0.resolve( |
578 | 1.34k | acquired_nodes, |
579 | 1.34k | &use_node_name, |
580 | 1.34k | values.fill_opacity().0, |
581 | 1.34k | values.color().0, |
582 | 1.34k | cascaded.context_fill.clone(), |
583 | 1.34k | cascaded.context_stroke.clone(), |
584 | 1.34k | draw_ctx.session(), |
585 | | ); |
586 | | |
587 | 1.34k | draw_ctx.draw_from_use_node( |
588 | 1.34k | node, |
589 | 1.34k | acquired_nodes, |
590 | 1.34k | values, |
591 | 1.34k | rect, |
592 | 1.34k | link, |
593 | 1.34k | clipping, |
594 | 1.34k | viewport, |
595 | 1.34k | fill_paint, |
596 | 1.34k | stroke_paint, |
597 | | ) |
598 | | } else { |
599 | 144 | Ok(viewport.empty_bbox()) |
600 | | } |
601 | 1.49k | } |
602 | | } |
603 | | |
604 | | /// The `<symbol>` element. |
605 | | #[derive(Default)] |
606 | | pub struct Symbol { |
607 | | preserve_aspect_ratio: AspectRatio, |
608 | | vbox: Option<ViewBox>, |
609 | | } |
610 | | |
611 | | impl Symbol { |
612 | 0 | pub fn get_viewbox(&self) -> Option<ViewBox> { |
613 | 0 | self.vbox |
614 | 0 | } |
615 | | |
616 | 0 | pub fn get_preserve_aspect_ratio(&self) -> AspectRatio { |
617 | 0 | self.preserve_aspect_ratio |
618 | 0 | } |
619 | | } |
620 | | |
621 | | impl ElementTrait for Symbol { |
622 | 0 | fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { |
623 | 0 | for (attr, value) in attrs.iter() { |
624 | 0 | match attr.expanded() { |
625 | | expanded_name!("", "preserveAspectRatio") => { |
626 | 0 | set_attribute(&mut self.preserve_aspect_ratio, attr.parse(value), session) |
627 | | } |
628 | | expanded_name!("", "viewBox") => { |
629 | 0 | set_attribute(&mut self.vbox, attr.parse(value), session) |
630 | | } |
631 | 0 | _ => (), |
632 | | } |
633 | | } |
634 | 0 | } |
635 | | } |
636 | | |
637 | | coord_units!(ClipPathUnits, CoordUnits::UserSpaceOnUse); |
638 | | |
639 | | /// The `<clipPath>` element. |
640 | | #[derive(Default)] |
641 | | pub struct ClipPath { |
642 | | units: ClipPathUnits, |
643 | | } |
644 | | |
645 | | impl ClipPath { |
646 | 126 | pub fn get_units(&self) -> CoordUnits { |
647 | 126 | CoordUnits::from(self.units) |
648 | 126 | } |
649 | | } |
650 | | |
651 | | impl ElementTrait for ClipPath { |
652 | 124 | fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { |
653 | 239 | for (attr, value) in attrs.iter() { |
654 | 239 | if attr.expanded() == expanded_name!("", "clipPathUnits") { |
655 | 115 | set_attribute(&mut self.units, attr.parse(value), session); |
656 | 124 | } |
657 | | } |
658 | 124 | } |
659 | | } |
660 | | |
661 | | coord_units!(MaskUnits, CoordUnits::ObjectBoundingBox); |
662 | | coord_units!(MaskContentUnits, CoordUnits::UserSpaceOnUse); |
663 | | |
664 | | /// The `<mask>` element. |
665 | | pub struct Mask { |
666 | | x: Length<Horizontal>, |
667 | | y: Length<Vertical>, |
668 | | width: ULength<Horizontal>, |
669 | | height: ULength<Vertical>, |
670 | | |
671 | | units: MaskUnits, |
672 | | content_units: MaskContentUnits, |
673 | | } |
674 | | |
675 | | impl Default for Mask { |
676 | 5.01k | fn default() -> Mask { |
677 | 5.01k | Mask { |
678 | 5.01k | // these values are per the spec |
679 | 5.01k | x: Length::<Horizontal>::parse_str("-10%").unwrap(), |
680 | 5.01k | y: Length::<Vertical>::parse_str("-10%").unwrap(), |
681 | 5.01k | width: ULength::<Horizontal>::parse_str("120%").unwrap(), |
682 | 5.01k | height: ULength::<Vertical>::parse_str("120%").unwrap(), |
683 | 5.01k | |
684 | 5.01k | units: MaskUnits::default(), |
685 | 5.01k | content_units: MaskContentUnits::default(), |
686 | 5.01k | } |
687 | 5.01k | } |
688 | | } |
689 | | |
690 | | impl Mask { |
691 | 15 | pub fn get_units(&self) -> CoordUnits { |
692 | 15 | CoordUnits::from(self.units) |
693 | 15 | } |
694 | | |
695 | 0 | pub fn get_content_units(&self) -> CoordUnits { |
696 | 0 | CoordUnits::from(self.content_units) |
697 | 0 | } |
698 | | |
699 | 15 | pub fn get_rect(&self, params: &NormalizeParams) -> Rect { |
700 | 15 | let x = self.x.to_user(params); |
701 | 15 | let y = self.y.to_user(params); |
702 | 15 | let w = self.width.to_user(params); |
703 | 15 | let h = self.height.to_user(params); |
704 | | |
705 | 15 | Rect::new(x, y, x + w, y + h) |
706 | 15 | } |
707 | | } |
708 | | |
709 | | impl ElementTrait for Mask { |
710 | 5.01k | fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { |
711 | 9.80k | for (attr, value) in attrs.iter() { |
712 | 9.80k | match attr.expanded() { |
713 | 0 | expanded_name!("", "x") => set_attribute(&mut self.x, attr.parse(value), session), |
714 | 0 | expanded_name!("", "y") => set_attribute(&mut self.y, attr.parse(value), session), |
715 | | expanded_name!("", "width") => { |
716 | 92 | set_attribute(&mut self.width, attr.parse(value), session) |
717 | | } |
718 | | expanded_name!("", "height") => { |
719 | 0 | set_attribute(&mut self.height, attr.parse(value), session) |
720 | | } |
721 | | expanded_name!("", "maskUnits") => { |
722 | 337 | set_attribute(&mut self.units, attr.parse(value), session) |
723 | | } |
724 | | expanded_name!("", "maskContentUnits") => { |
725 | 0 | set_attribute(&mut self.content_units, attr.parse(value), session) |
726 | | } |
727 | 9.37k | _ => (), |
728 | | } |
729 | | } |
730 | 5.01k | } |
731 | | } |
732 | | |
733 | | /// The `<a>` element. |
734 | | #[derive(Default)] |
735 | | pub struct Link { |
736 | | pub link: Option<String>, |
737 | | } |
738 | | |
739 | | impl ElementTrait for Link { |
740 | 539 | fn set_attributes(&mut self, attrs: &Attributes, _session: &Session) { |
741 | 1.09k | for (attr, value) in attrs.iter() { |
742 | 1.09k | let expanded = attr.expanded(); |
743 | 1.09k | if is_href(&expanded) { |
744 | 530 | set_href(&expanded, &mut self.link, Some(value.to_owned())); |
745 | 566 | } |
746 | | } |
747 | 539 | } |
748 | | |
749 | 539 | fn draw( |
750 | 539 | &self, |
751 | 539 | node: &Node, |
752 | 539 | acquired_nodes: &mut AcquiredNodes<'_>, |
753 | 539 | cascaded: &CascadedValues<'_>, |
754 | 539 | viewport: &Viewport, |
755 | 539 | draw_ctx: &mut DrawingCtx, |
756 | 539 | _clipping: bool, |
757 | 539 | ) -> DrawResult { |
758 | | // If this element is inside of <text>, do not draw it. |
759 | | // The <text> takes care of it. |
760 | 1.07k | for an in node.ancestors() { |
761 | 1.07k | if matches!(&*an.borrow_element_data(), ElementData::Text(_)) { |
762 | 0 | return Ok(viewport.empty_bbox()); |
763 | 1.07k | } |
764 | | } |
765 | | |
766 | 539 | let cascaded = CascadedValues::clone_with_node(cascaded, node); |
767 | 539 | let values = cascaded.get(); |
768 | | |
769 | 539 | let elt = node.borrow_element(); |
770 | | |
771 | 539 | let link_is_empty = self.link.as_ref().map(|l| l.is_empty()).unwrap_or(true); |
772 | | |
773 | 539 | let link_target = if link_is_empty { |
774 | 35 | None |
775 | | } else { |
776 | 504 | self.link.clone() |
777 | | }; |
778 | | |
779 | 539 | let stacking_ctx = Box::new(StackingContext::new_with_link( |
780 | 539 | draw_ctx, |
781 | 539 | acquired_nodes, |
782 | 539 | &elt, |
783 | 539 | values.transform(), |
784 | 539 | values, |
785 | 539 | viewport, |
786 | 539 | link_target, |
787 | | )); |
788 | | |
789 | 539 | draw_ctx.with_discrete_layer( |
790 | 539 | &stacking_ctx, |
791 | 539 | acquired_nodes, |
792 | 539 | viewport, |
793 | 539 | None, |
794 | | false, |
795 | 539 | &mut |an, dc, new_viewport| node.draw_children(an, &cascaded, new_viewport, dc), |
796 | | ) |
797 | 539 | } |
798 | | } |
799 | | |
800 | | #[cfg(test)] |
801 | | mod tests { |
802 | | use super::*; |
803 | | |
804 | | use crate::accept_language::UserLanguage; |
805 | | use crate::document::Document; |
806 | | use crate::dpi::Dpi; |
807 | | use crate::drawing_ctx::{RenderingConfiguration, SvgNesting}; |
808 | | |
809 | | #[test] |
810 | | fn computes_group_extents() { |
811 | | let document = Document::load_from_bytes( |
812 | | br#"<?xml version="1.0" encoding="UTF-8"?> |
813 | | <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> |
814 | | <g id="a"> |
815 | | <g transform="translate(10, 10) scale(2, 3)"> |
816 | | <rect x="0" y="0" width="5" height="10"/> |
817 | | </g> |
818 | | <rect x="0" y="0" width="5" height="10" transform="scale(2) translate(-10, -20)"/> |
819 | | </g> |
820 | | </svg> |
821 | | "#, |
822 | | ); |
823 | | |
824 | | let a = document.lookup_internal_node("a").unwrap(); |
825 | | |
826 | | let elt = a.borrow_element(); |
827 | | |
828 | | let mut acquired_nodes = AcquiredNodes::new(&document, None); |
829 | | let cascaded = CascadedValues::new_from_node(&a); |
830 | | |
831 | | let dpi = Dpi::new(96.0, 96.0); |
832 | | |
833 | | let viewport = Viewport::new(dpi.clone(), 100.0, 100.0); |
834 | | |
835 | | let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None).unwrap(); |
836 | | let cr = cairo::Context::new(&surface).unwrap(); |
837 | | |
838 | | let config = RenderingConfiguration { |
839 | | dpi, |
840 | | cancellable: None, |
841 | | user_language: UserLanguage::LanguageTags(crate::accept_language::LanguageTags::empty()), |
842 | | svg_nesting: SvgNesting::Standalone, |
843 | | measuring: false, |
844 | | testing: true, |
845 | | }; |
846 | | |
847 | | let mut draw_ctx = DrawingCtx::new(Session::default(), &cr, &viewport, config, Vec::new()); |
848 | | |
849 | | let layout = elt.layout(&a, &mut acquired_nodes, &cascaded, &viewport, &mut draw_ctx); |
850 | | |
851 | | match layout { |
852 | | Ok(Some(Layer { |
853 | | kind: LayerKind::Group(ref group), |
854 | | .. |
855 | | })) => { |
856 | | assert_eq!(group.extents, Some(Rect::new(-20.0, -40.0, 20.0, 40.0))); |
857 | | } |
858 | | |
859 | | Err(_) => panic!("layout should not produce an InternalRenderingError"), |
860 | | |
861 | | _ => panic!("layout object is not a LayerKind::Group"), |
862 | | } |
863 | | } |
864 | | } |