[librsvg: 1/4] Make multi-page PDFs with mixed sizes




commit 3e9d129bf5251225030d55e419ec5a08914eb9e3
Author: Michael Howell <michael notriddle com>
Date:   Thu Oct 7 14:21:46 2021 -0700

    Make multi-page PDFs with mixed sizes
    
    Fixes #783
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/600>

 src/bin/rsvg-convert.rs           | 35 +++++++++++++++++++++++----
 tests/src/cmdline/rsvg_convert.rs | 16 +++++++++----
 tests/src/predicates/pdf.rs       | 50 ++++++++++++++++++++++++++-------------
 3 files changed, 74 insertions(+), 27 deletions(-)
---
diff --git a/src/bin/rsvg-convert.rs b/src/bin/rsvg-convert.rs
index 3aeb74ec..d073bd19 100644
--- a/src/bin/rsvg-convert.rs
+++ b/src/bin/rsvg-convert.rs
@@ -27,7 +27,6 @@ use librsvg::rsvg_convert_only::{
 use librsvg::{
     AcceptLanguage, CairoRenderer, Color, Language, LengthUnit, Loader, Parse, RenderingError,
 };
-use once_cell::unsync::OnceCell;
 use std::ops::Deref;
 use std::path::PathBuf;
 
@@ -450,9 +449,9 @@ impl Converter {
             None => None,
         };
 
-        let mut surface: OnceCell<Surface> = OnceCell::new();
+        let mut surface: Option<Surface> = None;
 
-        for input in &self.input {
+        for (page_idx, input) in self.input.iter().enumerate() {
             let (stream, basefile) = match input {
                 Input::Stdin => (Stdin::stream(), None),
                 Input::Named(p) => {
@@ -553,9 +552,35 @@ impl Converter {
 
             let final_size = self.final_size(&strategy, &natural_size, input)?;
 
-            // Create the surface once on the first input
+            // Create the surface once on the first input,
+            // except for PDF, PS, and EPS, which allow differently-sized pages.
             let page_size = page_size.unwrap_or(final_size);
-            let s = surface.get_or_try_init(|| self.create_surface(page_size))?;
+            let s = match &mut surface {
+                Some(s) => {
+                    match s {
+                        #[cfg(system_deps_have_cairo_pdf)]
+                        Surface::Pdf(pdf, size) => {
+                            pdf.set_size(final_size.w, final_size.h).map_err(|e| {
+                                error!(
+                                    "Error setting PDF page #{} size {}: {}",
+                                    page_idx + 1,
+                                    input,
+                                    e
+                                )
+                            })?;
+                            *size = final_size;
+                        }
+                        #[cfg(system_deps_have_cairo_ps)]
+                        Surface::Ps(ps, size) => {
+                            ps.set_size(final_size.w, final_size.h);
+                            *size = final_size;
+                        }
+                        _ => {}
+                    }
+                    s
+                }
+                surface @ None => surface.insert(self.create_surface(page_size)?),
+            };
 
             let left = self.left.map(|l| l.to_user(&params)).unwrap_or(0.0);
             let top = self.top.map(|l| l.to_user(&params)).unwrap_or(0.0);
diff --git a/tests/src/cmdline/rsvg_convert.rs b/tests/src/cmdline/rsvg_convert.rs
index 7794bb37..0ad21b3e 100644
--- a/tests/src/cmdline/rsvg_convert.rs
+++ b/tests/src/cmdline/rsvg_convert.rs
@@ -286,7 +286,13 @@ fn multiple_input_files_create_multi_page_pdf_output() {
         .arg(three)
         .assert()
         .success()
-        .stdout(file::is_pdf().with_page_count(3));
+        .stdout(
+            file::is_pdf()
+                .with_page_count(3)
+                .and(file::is_pdf().with_page_size(0, 150.0, 75.0))
+                .and(file::is_pdf().with_page_size(1, 123.0, 123.0))
+                .and(file::is_pdf().with_page_size(2, 75.0, 300.0)),
+        );
 }
 
 #[cfg(system_deps_have_cairo_pdf)]
@@ -610,7 +616,7 @@ fn unscaled_pdf_size() {
         .arg("--format=pdf")
         .assert()
         .success()
-        .stdout(file::is_pdf().with_page_size(72.0, 72.0));
+        .stdout(file::is_pdf().with_page_size(0, 72.0, 72.0));
 }
 
 #[cfg(system_deps_have_cairo_pdf)]
@@ -622,7 +628,7 @@ fn pdf_size_width_height() {
         .arg("--height=3in")
         .assert()
         .success()
-        .stdout(file::is_pdf().with_page_size(144.0, 216.0));
+        .stdout(file::is_pdf().with_page_size(0, 144.0, 216.0));
 }
 
 #[cfg(system_deps_have_cairo_pdf)]
@@ -635,7 +641,7 @@ fn pdf_size_width_height_proportional() {
         .arg("--keep-aspect-ratio")
         .assert()
         .success()
-        .stdout(file::is_pdf().with_page_size(144.0, 144.0));
+        .stdout(file::is_pdf().with_page_size(0, 144.0, 144.0));
 }
 
 #[cfg(system_deps_have_cairo_pdf)]
@@ -647,7 +653,7 @@ fn pdf_page_size() {
         .arg("--page-height=297mm")
         .assert()
         .success()
-        .stdout(file::is_pdf().with_page_size(210.0 / 25.4 * 72.0, 297.0 / 25.4 * 72.0));
+        .stdout(file::is_pdf().with_page_size(0, 210.0 / 25.4 * 72.0, 297.0 / 25.4 * 72.0));
 }
 
 #[cfg(system_deps_have_cairo_pdf)]
diff --git a/tests/src/predicates/pdf.rs b/tests/src/predicates/pdf.rs
index 14ba46f7..3398e623 100644
--- a/tests/src/predicates/pdf.rs
+++ b/tests/src/predicates/pdf.rs
@@ -19,16 +19,20 @@ impl PdfPredicate {
 
     pub fn with_page_size(
         self: Self,
+        idx: usize,
         width_in_points: f64,
         height_in_points: f64,
     ) -> DetailPredicate<Self> {
         DetailPredicate::<Self> {
             p: self,
-            d: Detail::PageSize(Dimensions {
-                w: width_in_points,
-                h: height_in_points,
-                unit: 1.0,
-            }),
+            d: Detail::PageSize(
+                Dimensions {
+                    w: width_in_points,
+                    h: height_in_points,
+                    unit: 1.0,
+                },
+                idx,
+            ),
         }
     }
 
@@ -78,7 +82,7 @@ pub struct DetailPredicate<PdfPredicate> {
 #[derive(Debug)]
 enum Detail {
     PageCount(usize),
-    PageSize(Dimensions),
+    PageSize(Dimensions, usize),
     CreationDate(DateTime<Utc>),
     Link(String),
 }
@@ -138,17 +142,21 @@ impl cmp::Eq for Dimensions {}
 
 trait Details {
     fn get_page_count(&self) -> usize;
-    fn get_page_size(&self) -> Option<Dimensions>;
+    fn get_page_size(&self, idx: usize) -> Option<Dimensions>;
     fn get_creation_date(&self) -> Option<DateTime<Utc>>;
     fn get_from_trailer<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object>;
-    fn get_from_first_page<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object>;
+    fn get_from_page<'a>(
+        self: &'a Self,
+        idx: usize,
+        key: &[u8],
+    ) -> lopdf::Result<&'a lopdf::Object>;
 }
 
 impl DetailPredicate<PdfPredicate> {
     fn eval_doc(&self, doc: &lopdf::Document) -> bool {
         match &self.d {
             Detail::PageCount(n) => doc.get_page_count() == *n,
-            Detail::PageSize(d) => doc.get_page_size().map_or(false, |dim| dim == *d),
+            Detail::PageSize(d, idx) => doc.get_page_size(*idx).map_or(false, |dim| dim == *d),
             Detail::CreationDate(d) => doc.get_creation_date().map_or(false, |date| date == *d),
             Detail::Link(link) => document_has_link(doc, &link),
         }
@@ -169,9 +177,9 @@ impl DetailPredicate<PdfPredicate> {
                 "actual page count",
                 format!("{} page(s)", doc.get_page_count()),
             ),
-            Detail::PageSize(_) => Product::new(
+            Detail::PageSize(_, idx) => Product::new(
                 "actual page size",
-                match doc.get_page_size() {
+                match doc.get_page_size(*idx) {
                     Some(dim) => format!("{}", dim),
                     None => "None".to_string(),
                 },
@@ -210,11 +218,11 @@ impl Details for lopdf::Document {
         self.get_pages().len()
     }
 
-    fn get_page_size(self: &Self) -> Option<Dimensions> {
-        match self.get_from_first_page(b"MediaBox") {
+    fn get_page_size(self: &Self, idx: usize) -> Option<Dimensions> {
+        match self.get_from_page(idx, b"MediaBox") {
             Ok(obj) => {
                 let unit = self
-                    .get_from_first_page(b"UserUnit")
+                    .get_from_page(idx, b"UserUnit")
                     .and_then(ObjExt::as_float)
                     .ok();
                 Dimensions::from_media_box(obj, unit).ok()
@@ -235,8 +243,16 @@ impl Details for lopdf::Document {
         self.get_object(id)?.as_dict()?.get(key)
     }
 
-    fn get_from_first_page<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object> {
-        match self.page_iter().next() {
+    fn get_from_page<'a>(
+        self: &'a Self,
+        idx: usize,
+        key: &[u8],
+    ) -> lopdf::Result<&'a lopdf::Object> {
+        let mut iter = self.page_iter();
+        for _ in 0..idx {
+            let _ = iter.next();
+        }
+        match iter.next() {
             Some(id) => self.get_object(id)?.as_dict()?.get(key),
             None => Err(lopdf::Error::ObjectNotFound),
         }
@@ -270,7 +286,7 @@ impl fmt::Display for DetailPredicate<PdfPredicate> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match &self.d {
             Detail::PageCount(n) => write!(f, "is a PDF with {} page(s)", n),
-            Detail::PageSize(d) => write!(f, "is a PDF sized {}", d),
+            Detail::PageSize(d, _) => write!(f, "is a PDF sized {}", d),
             Detail::CreationDate(d) => write!(f, "is a PDF created {:?}", d),
             Detail::Link(l) => write!(f, "is a PDF with a link to {}", l),
         }


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