1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
//! Compute an SVG document's size with the legacy logic.
//!
//! See the documentation for [`LegacySize`]. The legacy C API functions like
//! `rsvg_handle_render_cairo()` do not take a viewport argument: they do not know how big
//! the caller would like to render the document; instead they compute a "natural size"
//! for the document based on its `width`/`height`/`viewBox` and some heuristics for when
//! they are missing.
//!
//! The new style C functions like `rsvg_handle_render_document()` actually take a
//! viewport, which indicates how big the result should be. This matches the expectations
//! of the web platform, which assumes that all embedded content goes into a viewport.
use float_cmp::approx_eq;
use crate::api::{CairoRenderer, IntrinsicDimensions, RenderingError};
use crate::dpi::Dpi;
use crate::handle::Handle;
use crate::length::*;
use super::handle::CairoRectangleExt;
/// Extension methods to compute the SVG's size suitable for the legacy C API.
///
/// The legacy C API can compute an SVG document's size from the
/// `width`, `height`, and `viewBox` attributes of the toplevel `<svg>`
/// element. If these are not available, then the size must be computed
/// by actually measuring the geometries of elements in the document.
///
/// See <https://www.w3.org/TR/css-images-3/#sizing-terms> for terminology and logic.
pub trait LegacySize {
fn legacy_document_size(&self) -> Result<(f64, f64), RenderingError> {
let (ink_r, _) = self.legacy_layer_geometry(None)?;
Ok((ink_r.width, ink_r.height))
}
fn legacy_layer_geometry(
&self,
id: Option<&str>,
) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError>;
}
impl<'a> LegacySize for CairoRenderer<'a> {
fn legacy_layer_geometry(
&self,
id: Option<&str>,
) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
match id {
Some(id) => Ok(self.geometry_for_layer(Some(id), &unit_rectangle())?),
None => {
let size_from_intrinsic_dimensions =
self.intrinsic_size_in_pixels().or_else(|| {
size_in_pixels_from_percentage_width_and_height(
&self.handle.handle,
&self.intrinsic_dimensions(),
self.dpi,
)
});
if let Some((w, h)) = size_from_intrinsic_dimensions {
// We have a size directly computed from the <svg> attributes
let rect = cairo::Rectangle::from_size(w, h);
Ok((rect, rect))
} else {
self.geometry_for_layer(None, &unit_rectangle())
}
}
}
}
}
fn unit_rectangle() -> cairo::Rectangle {
cairo::Rectangle::from_size(1.0, 1.0)
}
/// If the width and height are in percentage units, computes a size equal to the
/// `viewBox`'s aspect ratio if it exists, or else returns None.
///
/// For example, a `viewBox="0 0 100 200"` will yield `Some(100.0, 200.0)`.
///
/// Note that this only checks that the width and height are in percentage units, but
/// it actually ignores their values. This is because at the point this function is
/// called, there is no viewport to embed the SVG document in, so those percentage
/// units cannot be resolved against anything in particular. The idea is to return
/// some dimensions with the correct aspect ratio.
fn size_in_pixels_from_percentage_width_and_height(
handle: &Handle,
dim: &IntrinsicDimensions,
dpi: Dpi,
) -> Option<(f64, f64)> {
let IntrinsicDimensions {
width,
height,
vbox,
} = *dim;
use LengthUnit::*;
// Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it.
let vbox = vbox?;
let (w, h) = handle.width_height_to_user(dpi);
// Avoid division by zero below. If the viewBox is zero-sized, there's
// not much we can do.
if approx_eq!(f64, vbox.width, 0.0) || approx_eq!(f64, vbox.height, 0.0) {
return Some((0.0, 0.0));
}
match (width.unit, height.unit) {
(Percent, Percent) => Some((vbox.width, vbox.height)),
(_, Percent) => Some((w, w * vbox.height / vbox.width)),
(Percent, _) => Some((h * vbox.width / vbox.height, h)),
(_, _) => unreachable!("should have been called with percentage units"),
}
}
|