[librsvg/rustify-rsvg-convert] rsvg-convert: implement the different sizing strategies



commit be326f60ed2f8ba2cd8e49928cd27f3b5e46cbbf
Author: Paolo Borelli <pborelli gnome org>
Date:   Fri Jan 8 20:27:03 2021 +0100

    rsvg-convert: implement the different sizing strategies

 src/bin/rsvg-convert/cli.rs  | 18 +++-------
 src/bin/rsvg-convert/main.rs | 42 ++++++++++++----------
 src/bin/rsvg-convert/size.rs | 86 +++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 110 insertions(+), 36 deletions(-)
---
diff --git a/src/bin/rsvg-convert/cli.rs b/src/bin/rsvg-convert/cli.rs
index 7b2ecbbd..d6a01d42 100644
--- a/src/bin/rsvg-convert/cli.rs
+++ b/src/bin/rsvg-convert/cli.rs
@@ -21,8 +21,7 @@ arg_enum! {
 #[derive(Debug)]
 pub struct Args {
     pub dpi: Dpi,
-    zoom_x: Option<f64>,
-    zoom_y: Option<f64>,
+    pub zoom: Scale,
     pub width: Option<u32>,
     pub height: Option<u32>,
     pub format: Format,
@@ -209,8 +208,10 @@ impl Args {
                 x: value_t!(matches, "res_x", f64)?,
                 y: value_t!(matches, "res_y", f64)?,
             },
-            zoom_x: zoom.or(zoom_x),
-            zoom_y: zoom.or(zoom_y),
+            zoom: Scale {
+                x: zoom.or(zoom_x).unwrap_or(1.0),
+                y: zoom.or(zoom_y).unwrap_or(1.0),
+            },
             width: value_t!(matches, "size_x", u32).or_none()?,
             height: value_t!(matches, "size_y", u32).or_none()?,
             format,
@@ -255,15 +256,6 @@ impl Args {
     pub fn input(&self) -> Input<'_> {
         Input::new(&self.input)
     }
-
-    pub fn zoom(&self) -> Scale {
-        match (self.zoom_x, self.zoom_y) {
-            (None, None) => Scale { x: 1.0, y: 1.0 },
-            (Some(x), None) => Scale { x, y: x },
-            (None, Some(y)) => Scale { x: y, y },
-            (Some(x), Some(y)) => Scale { x, y },
-        }
-    }
 }
 
 fn is_valid_resolution(v: String) -> Result<(), String> {
diff --git a/src/bin/rsvg-convert/main.rs b/src/bin/rsvg-convert/main.rs
index 837050ef..35377dbe 100644
--- a/src/bin/rsvg-convert/main.rs
+++ b/src/bin/rsvg-convert/main.rs
@@ -9,11 +9,11 @@ mod surface;
 
 use cssparser::Color;
 use librsvg::rsvg_convert_only::LegacySize;
-use librsvg::{CairoRenderer, Loader, RenderingError, SvgHandle};
+use librsvg::{CairoRenderer, Loader, RenderingError};
 
 use crate::cli::Args;
 use crate::output::Stream;
-use crate::size::Size;
+use crate::size::{ResizeStrategy, Size};
 use crate::surface::Surface;
 
 #[macro_export]
@@ -40,16 +40,6 @@ fn load_stylesheet(args: &Args) -> std::io::Result<Option<String>> {
     }
 }
 
-fn get_size(
-    _handle: &SvgHandle,
-    renderer: &CairoRenderer,
-    args: &Args,
-) -> Result<Size, RenderingError> {
-    renderer
-        .legacy_document_size_in_pixels()
-        .map(|(w, h)| Size::new(w, h).scale(args.zoom()))
-}
-
 fn main() {
     let args = Args::new().unwrap_or_else(|e| e.exit());
 
@@ -74,17 +64,34 @@ fn main() {
         let renderer = CairoRenderer::new(&handle).with_dpi(args.dpi.x, args.dpi.y);
 
         if target.is_none() {
-            let size = get_size(&handle, &renderer, &args)
+            let (width, height) = renderer
+                .legacy_document_size_in_pixels()
                 .unwrap_or_else(|e| exit!("Error rendering SVG {}: {}", input, e));
 
-            if size.w == 0.0 && size.h == 0.0 {
-                exit!("The SVG {} has no dimensions", input);
-            }
+            let strategy = match (args.width, args.height) {
+                // when w and h are not specified, scale to the requested zoom (if any)
+                (None, None) => ResizeStrategy::Scale(args.zoom),
+
+                // when w and h are specified, but zoom is not, scale to the requestd size
+                (Some(w), Some(h)) if args.zoom.is_identity() => ResizeStrategy::Fit(w, h),
+
+                // if only one between w and h is specified and there is no zoom, scale to the
+                // requested w or h and that use the same scaling factor for the other
+                (Some(w), None) if args.zoom.is_identity() => ResizeStrategy::FitWidth(w),
+                (None, Some(h)) if args.zoom.is_identity() => ResizeStrategy::FitHeight(h),
+
+                // otherwise scale the image, but cap the zoom to match the requested size
+                _ => ResizeStrategy::FitLargestScale(args.zoom, args.width, args.height),
+            };
 
             target = {
                 let output = Stream::new(args.output())
                     .unwrap_or_else(|e| exit!("Error opening output: {}", e));
 
+                let size = strategy
+                    .apply(Size::new(width, height), args.keep_aspect_ratio)
+                    .unwrap_or_else(|_| exit!("The SVG {} has no dimensions", input));
+
                 match Surface::new(args.format, size, output) {
                     Ok(surface) => Some(surface),
                     Err(cairo::Status::InvalidSize) => size_limit_exceeded(),
@@ -105,8 +112,7 @@ fn main() {
                 );
             }
 
-            let scale = args.zoom();
-            cr.scale(scale.x, scale.y);
+            cr.scale(args.zoom.x, args.zoom.y);
 
             surface
                 .render(&renderer, &cr, args.export_id())
diff --git a/src/bin/rsvg-convert/size.rs b/src/bin/rsvg-convert/size.rs
index 94ff5def..f30bb7e5 100644
--- a/src/bin/rsvg-convert/size.rs
+++ b/src/bin/rsvg-convert/size.rs
@@ -4,12 +4,19 @@ pub struct Dpi {
     pub y: f64,
 }
 
+#[derive(Clone, Copy, Debug)]
 pub struct Scale {
     pub x: f64,
     pub y: f64,
 }
 
-#[derive(Clone, Debug)]
+impl Scale {
+    pub fn is_identity(&self) -> bool {
+        self.x == 1.0 && self.y == 1.0
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
 pub struct Size {
     pub w: f64,
     pub h: f64,
@@ -19,11 +26,80 @@ impl Size {
     pub fn new(w: f64, h: f64) -> Self {
         Self { w, h }
     }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum ResizeStrategy {
+    Scale(Scale),
+    Fit(u32, u32),
+    FitWidth(u32),
+    FitHeight(u32),
+    FitLargestScale(Scale, Option<u32>, Option<u32>),
+}
+
+impl ResizeStrategy {
+    pub fn apply(self, input: Size, keep_aspect_ratio: bool) -> Result<Size, ()> {
+        if input.w == 0.0 && input.h == 0.0 {
+            return Err(());
+        }
+
+        let output = match self {
+            ResizeStrategy::Scale(s) => Size {
+                w: input.w * s.x,
+                h: input.h * s.y,
+            },
+            ResizeStrategy::Fit(w, h) => Size {
+                w: f64::from(w),
+                h: f64::from(h),
+            },
+            ResizeStrategy::FitWidth(w) => Size {
+                w: f64::from(w),
+                h: input.h * f64::from(w) / input.w,
+            },
+            ResizeStrategy::FitHeight(h) => Size {
+                w: input.w * f64::from(h) / input.h,
+                h: f64::from(h),
+            },
+            ResizeStrategy::FitLargestScale(s, w, h) => {
+                let scaled_input_w = input.w * s.x;
+                let scaled_input_h = input.h * s.y;
+
+                let f = match (w.map(f64::from), h.map(f64::from)) {
+                    (Some(w), Some(h)) if w < scaled_input_w || h < scaled_input_h => {
+                        let sx = f64::from(w) / scaled_input_w;
+                        let sy = f64::from(h) / scaled_input_h;
+                        if sx > sy {
+                            sy
+                        } else {
+                            sx
+                        }
+                    }
+                    (Some(w), None) if w < scaled_input_w => f64::from(w) / scaled_input_w,
+                    (None, Some(h)) if h < scaled_input_h => f64::from(h) / scaled_input_h,
+                    _ => 1.0,
+                };
+
+                Size {
+                    w: input.w * f * s.x,
+                    h: input.h * f * s.y,
+                }
+            }
+        };
+
+        if !keep_aspect_ratio {
+            return Ok(output);
+        }
 
-    pub fn scale(&self, scale: Scale) -> Self {
-        Self {
-            w: self.w * scale.x,
-            h: self.h * scale.y,
+        if output.w < output.h {
+            Ok(Size {
+                w: output.w,
+                h: input.h * (output.w / input.w),
+            })
+        } else {
+            Ok(Size {
+                w: input.w * (output.h / input.h),
+                h: output.h,
+            })
         }
     }
 }


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