1
//! Compute an SVG document's size with the legacy logic.
2
//!
3
//! See the documentation for [`LegacySize`].  The legacy C API functions like
4
//! `rsvg_handle_render_cairo()` do not take a viewport argument: they do not know how big
5
//! the caller would like to render the document; instead they compute a "natural size"
6
//! for the document based on its `width`/`height`/`viewBox` and some heuristics for when
7
//! they are missing.
8
//!
9
//! The new style C functions like `rsvg_handle_render_document()` actually take a
10
//! viewport, which indicates how big the result should be.  This matches the expectations
11
//! of the web platform, which assumes that all embedded content goes into a viewport.
12

            
13
use float_cmp::approx_eq;
14

            
15
use rsvg::c_api_only::Dpi;
16
use rsvg::{CairoRenderer, IntrinsicDimensions, RenderingError};
17

            
18
use super::handle::CairoRectangleExt;
19

            
20
/// Extension methods to compute the SVG's size suitable for the legacy C API.
21
///
22
/// The legacy C API can compute an SVG document's size from the
23
/// `width`, `height`, and `viewBox` attributes of the toplevel `<svg>`
24
/// element.  If these are not available, then the size must be computed
25
/// by actually measuring the geometries of elements in the document.
26
///
27
/// See <https://www.w3.org/TR/css-images-3/#sizing-terms> for terminology and logic.
28
pub trait LegacySize {
29
6
    fn legacy_document_size(&self) -> Result<(f64, f64), RenderingError> {
30
6
        let (ink_r, _) = self.legacy_layer_geometry(None)?;
31
6
        Ok((ink_r.width(), ink_r.height()))
32
6
    }
33

            
34
    fn legacy_layer_geometry(
35
        &self,
36
        id: Option<&str>,
37
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError>;
38
}
39

            
40
impl<'a> LegacySize for CairoRenderer<'a> {
41
138
    fn legacy_layer_geometry(
42
        &self,
43
        id: Option<&str>,
44
    ) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
45
138
        match id {
46
11
            Some(id) => Ok(self.geometry_for_layer(Some(id), &unit_rectangle())?),
47

            
48
            None => {
49
                let size_from_intrinsic_dimensions =
50
140
                    self.intrinsic_size_in_pixels().or_else(|| {
51
13
                        size_in_pixels_from_percentage_width_and_height(
52
13
                            self,
53
13
                            &self.intrinsic_dimensions(),
54
13
                            self.dpi(),
55
                        )
56
13
                    });
57

            
58
127
                if let Some((w, h)) = size_from_intrinsic_dimensions {
59
                    // We have a size directly computed from the <svg> attributes
60
123
                    let rect = cairo::Rectangle::from_size(w, h);
61
123
                    Ok((rect, rect))
62
                } else {
63
4
                    self.geometry_for_layer(None, &unit_rectangle())
64
                }
65
            }
66
        }
67
138
    }
68
}
69

            
70
15
fn unit_rectangle() -> cairo::Rectangle {
71
15
    cairo::Rectangle::from_size(1.0, 1.0)
72
15
}
73

            
74
/// If the width and height are in percentage units, computes a size equal to the
75
/// `viewBox`'s aspect ratio if it exists, or else returns None.
76
///
77
/// For example, a `viewBox="0 0 100 200"` will yield `Some(100.0, 200.0)`.
78
///
79
/// Note that this only checks that the width and height are in percentage units, but
80
/// it actually ignores their values.  This is because at the point this function is
81
/// called, there is no viewport to embed the SVG document in, so those percentage
82
/// units cannot be resolved against anything in particular.  The idea is to return
83
/// some dimensions with the correct aspect ratio.
84
13
fn size_in_pixels_from_percentage_width_and_height(
85
    renderer: &CairoRenderer,
86
    dim: &IntrinsicDimensions,
87
    dpi: Dpi,
88
) -> Option<(f64, f64)> {
89
    let IntrinsicDimensions {
90
13
        width,
91
13
        height,
92
13
        vbox,
93
    } = *dim;
94

            
95
    use rsvg::LengthUnit::*;
96

            
97
    // Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it.
98
26
    let vbox = vbox?;
99

            
100
9
    let (w, h) = renderer.width_height_to_user(dpi);
101

            
102
    // Avoid division by zero below.  If the viewBox is zero-sized, there's
103
    // not much we can do.
104
9
    if approx_eq!(f64, vbox.width(), 0.0) || approx_eq!(f64, vbox.height(), 0.0) {
105
2
        return Some((0.0, 0.0));
106
    }
107

            
108
7
    match (width.unit, height.unit) {
109
5
        (Percent, Percent) => Some((vbox.width(), vbox.height())),
110
1
        (_, Percent) => Some((w, w * vbox.height() / vbox.width())),
111
1
        (Percent, _) => Some((h * vbox.width() / vbox.height(), h)),
112
        (_, _) => unreachable!("should have been called with percentage units"),
113
    }
114
13
}