[librsvg: 5/8] (#797) - Handle the case where the toplevel SVG has only one of width/height missing




commit d3f0ac17b189f98b1213e93958afb873470359e3
Author: Federico Mena Quintero <federico gnome org>
Date:   Fri Oct 1 12:36:57 2021 -0500

    (#797) - Handle the case where the toplevel SVG has only one of width/height missing
    
    Consider this:
    
      <svg xmlns="http://www.w3.org/2000/svg"; width="60" viewBox="0 0 30 40">
    
    The height should default to 100%.  If this were being rendered to a
    viewport, then no problem - use 60 units for the width, and the
    viewport's entire height.
    
    But we are being asked to produce a "natural size", and we don't have
    a viewport size.  So, just produce a size based on the width and the
    viewBox's aspect ratio.
    
    Fixes https://gitlab.gnome.org/GNOME/librsvg/-/issues/797
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/595>

 src/api.rs                 |   6 +-
 src/c_api/sizing.rs        |  37 ++++----
 src/handle.rs              |  26 ++++++
 tests/src/legacy_sizing.rs | 204 ++++++++++++++++++++++++++++++++-------------
 4 files changed, 196 insertions(+), 77 deletions(-)
---
diff --git a/src/api.rs b/src/api.rs
index 12da4b03..b38880d4 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -214,7 +214,7 @@ fn url_from_file(file: &gio::File) -> Result<Url, LoadingError> {
 ///
 /// You can create this from one of the `read` methods in
 /// [`Loader`].
-pub struct SvgHandle(Handle);
+pub struct SvgHandle(pub(crate) Handle);
 
 impl SvgHandle {
     /// Checks if the SVG has an element with the specified `id`.
@@ -243,8 +243,8 @@ impl SvgHandle {
 
 /// Can render an `SvgHandle` to a Cairo context.
 pub struct CairoRenderer<'a> {
-    handle: &'a SvgHandle,
-    dpi: Dpi,
+    pub(crate) handle: &'a SvgHandle,
+    pub(crate) dpi: Dpi,
     user_language: UserLanguage,
     is_testing: bool,
 }
diff --git a/src/c_api/sizing.rs b/src/c_api/sizing.rs
index adb95ce4..7095fc45 100644
--- a/src/c_api/sizing.rs
+++ b/src/c_api/sizing.rs
@@ -11,7 +11,9 @@
 //! of the web platform, which assumes that all embedded content goes into a viewport.
 
 use crate::api::{CairoRenderer, IntrinsicDimensions, Length, RenderingError};
-use float_cmp::approx_eq;
+use crate::dpi::Dpi;
+use crate::handle::Handle;
+use crate::length::*;
 
 use super::handle::CairoRectangleExt;
 
@@ -47,7 +49,9 @@ impl<'a> LegacySize for CairoRenderer<'a> {
                 let size_from_intrinsic_dimensions =
                     self.intrinsic_size_in_pixels().or_else(|| {
                         size_in_pixels_from_percentage_width_and_height(
+                            &self.handle.0,
                             &self.intrinsic_dimensions(),
+                            self.dpi,
                         )
                     });
 
@@ -78,7 +82,9 @@ fn unit_rectangle() -> cairo::Rectangle {
 /// 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,
@@ -86,26 +92,21 @@ fn size_in_pixels_from_percentage_width_and_height(
         vbox,
     } = *dim;
 
-    use crate::api::LengthUnit::*;
+    use LengthUnit::*;
 
-    // If both width and height are 100%, just use the vbox size as a pixel size.
-    // This gives a size with the correct aspect ratio.
+    // Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it.
+    let vbox = vbox?;
 
-    match (width, height, vbox) {
-        (None, None, Some(vbox)) => Some((vbox.width, vbox.height)),
+    let (w, h) = handle.width_height_to_user(dpi);
 
-        (
-            Some(Length {
-                length: w,
-                unit: Percent,
-            }),
-            Some(Length {
-                length: h,
-                unit: Percent,
-            }),
-            Some(vbox),
-        ) if approx_eq!(f64, w, 1.0) && approx_eq!(f64, h, 1.0) => Some((vbox.width, vbox.height)),
+    // missing width/height default to "auto", which compute to "100%"
+    let width = width.unwrap_or_else(|| Length::new(1.0, Percent));
+    let height = height.unwrap_or_else(|| Length::new(1.0, Percent));
 
-        _ => None,
+    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"),
     }
 }
diff --git a/src/handle.rs b/src/handle.rs
index 2bd121da..4c6a5325 100644
--- a/src/handle.rs
+++ b/src/handle.rs
@@ -141,6 +141,32 @@ impl Handle {
         Some((w.to_user(&params), h.to_user(&params)))
     }
 
+    /// Normalizes the svg's width/height properties with a 0-sized viewport
+    ///
+    /// This assumes that if one of the properties is in percentage units, then
+    /// its corresponding value will not be used.  E.g. if width=100%, the caller
+    /// will ignore the resulting width value.
+    pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
+        let dimensions = self.get_intrinsic_dimensions();
+
+        // missing width/height default to "auto", which compute to "100%"
+        let width = dimensions
+            .width
+            .unwrap_or_else(|| ULength::new(1.0, LengthUnit::Percent));
+        let height = dimensions
+            .height
+            .unwrap_or_else(|| ULength::new(1.0, LengthUnit::Percent));
+
+        let view_params = ViewParams::new(dpi, 0.0, 0.0);
+        let root = self.document.root();
+        let cascaded = CascadedValues::new_from_node(&root);
+        let values = cascaded.get();
+
+        let params = NormalizeParams::new(values, &view_params);
+
+        (width.to_user(&params), height.to_user(&params))
+    }
+
     fn get_node_or_root(&self, id: Option<&str>) -> Result<Node, RenderingError> {
         if let Some(id) = id {
             Ok(self.lookup_node(id)?)
diff --git a/tests/src/legacy_sizing.rs b/tests/src/legacy_sizing.rs
index f36cccff..3f33f7d1 100644
--- a/tests/src/legacy_sizing.rs
+++ b/tests/src/legacy_sizing.rs
@@ -11,22 +11,26 @@ fn just_viewbox_uses_viewbox_size() {
 <svg xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 100 200"/>
 "#,
     )
-        .unwrap();
+    .unwrap();
 
     assert_eq!(
-        CairoRenderer::new(&svg).legacy_layer_geometry(None).unwrap(),
-        (cairo::Rectangle {
-            x: 0.0,
-            y: 0.0,
-            width: 100.0,
-            height: 200.0,
-        },
-         cairo::Rectangle {
-             x: 0.0,
-             y: 0.0,
-             width: 100.0,
-             height: 200.0,
-         })
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 100.0,
+                height: 200.0,
+            },
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 100.0,
+                height: 200.0,
+            }
+        )
     );
 }
 
@@ -39,22 +43,26 @@ fn no_intrinsic_size_uses_element_geometries() {
 </svg>
 "#,
     )
-        .unwrap();
+    .unwrap();
 
     assert_eq!(
-        CairoRenderer::new(&svg).legacy_layer_geometry(None).unwrap(),
-        (cairo::Rectangle {
-            x: 10.0,
-            y: 20.0,
-            width: 30.0,
-            height: 40.0,
-        },
-         cairo::Rectangle {
-             x: 10.0,
-             y: 20.0,
-             width: 30.0,
-             height: 40.0,
-         })
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 10.0,
+                y: 20.0,
+                width: 30.0,
+                height: 40.0,
+            },
+            cairo::Rectangle {
+                x: 10.0,
+                y: 20.0,
+                width: 30.0,
+                height: 40.0,
+            }
+        )
     );
 }
 
@@ -65,22 +73,26 @@ fn hundred_percent_width_height_uses_viewbox() {
 <svg xmlns="http://www.w3.org/2000/svg"; width="100%" height="100%" viewBox="0 0 100 200"/>
 "#,
     )
-        .unwrap();
+    .unwrap();
 
     assert_eq!(
-        CairoRenderer::new(&svg).legacy_layer_geometry(None).unwrap(),
-        (cairo::Rectangle {
-            x: 0.0,
-            y: 0.0,
-            width: 100.0,
-            height: 200.0,
-        },
-         cairo::Rectangle {
-             x: 0.0,
-             y: 0.0,
-             width: 100.0,
-             height: 200.0,
-         })
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 100.0,
+                height: 200.0,
+            },
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 100.0,
+                height: 200.0,
+            }
+        )
     );
 }
 
@@ -93,21 +105,101 @@ fn hundred_percent_width_height_no_viewbox_uses_element_geometries() {
 </svg>
 "#,
     )
-        .unwrap();
+    .unwrap();
 
     assert_eq!(
-        CairoRenderer::new(&svg).legacy_layer_geometry(None).unwrap(),
-        (cairo::Rectangle {
-            x: 10.0,
-            y: 20.0,
-            width: 30.0,
-            height: 40.0,
-        },
-         cairo::Rectangle {
-             x: 10.0,
-             y: 20.0,
-             width: 30.0,
-             height: 40.0,
-         })
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 10.0,
+                y: 20.0,
+                width: 30.0,
+                height: 40.0,
+            },
+            cairo::Rectangle {
+                x: 10.0,
+                y: 20.0,
+                width: 30.0,
+                height: 40.0,
+            }
+        )
+    );
+}
+
+#[test]
+fn width_and_viewbox_preserves_aspect_ratio() {
+    let svg = load_svg(
+        br#"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; width="60" viewBox="0 0 30 40">
+  <rect x="10" y="20" width="30" height="40" fill="black"/>
+</svg>
+"#,
+    )
+    .unwrap();
+
+    // Per the spec, the height property should default to 100%, so the above would end up
+    // like <svg width="60" height="100%" viewBox="0 0 30 40">
+    //
+    // If that were being *rendered* to a viewport, no problem, just use units horizontally
+    // and 100% of the viewport vertically.
+    //
+    // But we are being asked to compute the SVG's natural geometry, so the best we can do
+    // is to take the aspect ratio defined by the viewBox and apply it to the width.
+
+    assert_eq!(
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 60.0,
+                height: 80.0,
+            },
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 60.0,
+                height: 80.0,
+            }
+        )
+    );
+}
+
+#[test]
+fn height_and_viewbox_preserves_aspect_ratio() {
+    let svg = load_svg(
+        br#"<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; height="80" viewBox="0 0 30 40">
+  <rect x="10" y="20" width="30" height="40" fill="black"/>
+</svg>
+"#,
+    )
+    .unwrap();
+
+    // See the comment above in width_and_viewbox_preserves_aspect_ratio(); this
+    // is equivalent but for the height.
+
+    assert_eq!(
+        CairoRenderer::new(&svg)
+            .legacy_layer_geometry(None)
+            .unwrap(),
+        (
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 60.0,
+                height: 80.0,
+            },
+            cairo::Rectangle {
+                x: 0.0,
+                y: 0.0,
+                width: 60.0,
+                height: 80.0,
+            }
+        )
     );
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]