summaryrefslogtreecommitdiff
path: root/librsvg-c/src/c_api/sizing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'librsvg-c/src/c_api/sizing.rs')
-rw-r--r--librsvg-c/src/c_api/sizing.rs116
1 files changed, 116 insertions, 0 deletions
diff --git a/librsvg-c/src/c_api/sizing.rs b/librsvg-c/src/c_api/sizing.rs
new file mode 100644
index 00000000..e0c85bd1
--- /dev/null
+++ b/librsvg-c/src/c_api/sizing.rs
@@ -0,0 +1,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"),
+ }
+}