[librsvg/rustify-rsvg-convert: 4/18] rsvg-convert: Start work on the actual rendering




commit d8e87e55b0358810e7d95565424a07daaabd25c8
Author: Sven Neumann <sven svenfoo org>
Date:   Tue Oct 27 22:57:10 2020 +0100

    rsvg-convert: Start work on the actual rendering
    
    Handles input and output, but pretty much ignores sizing so far.

 src/bin/rsvg-convert/cli.rs       | 45 +++++++++++--------
 src/bin/rsvg-convert/input.rs     | 94 +++++++++++++++++++++++++++++++++++++++
 src/bin/rsvg-convert/main.rs      | 55 ++++++++++++++++++++++-
 src/bin/rsvg-convert/output.rs    | 44 ++++++++++++++++++
 src/bin/rsvg-convert/surface.rs   | 76 +++++++++++++++++++++++++++++++
 tests/src/cmdline/rsvg_convert.rs | 10 +----
 6 files changed, 295 insertions(+), 29 deletions(-)
---
diff --git a/src/bin/rsvg-convert/cli.rs b/src/bin/rsvg-convert/cli.rs
index d5b05731..27a4f439 100644
--- a/src/bin/rsvg-convert/cli.rs
+++ b/src/bin/rsvg-convert/cli.rs
@@ -1,11 +1,13 @@
 // command-line interface for rsvg-convert
 
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 
 use librsvg::{Color, Parse};
 
+use crate::input::Input;
+
 arg_enum! {
-    #[derive(Debug)]
+    #[derive(Clone, Copy, Debug)]
     pub enum Format {
         Png,
         Pdf,
@@ -17,18 +19,19 @@ arg_enum! {
 
 #[derive(Debug)]
 pub struct Args {
-    resolution: (f32, f32),
-    zoom: (f32, f32),
-    width: Option<u32>,
-    height: Option<u32>,
-    format: Format,
+    pub dpi_x: f64,
+    pub dpi_y: f64,
+    pub zoom: (f32, f32),
+    pub width: Option<u32>,
+    pub height: Option<u32>,
+    pub format: Format,
+    pub export_id: Option<String>,
+    pub keep_aspect_ratio: bool,
+    pub background_color: Option<Color>,
+    pub stylesheet: Option<PathBuf>,
+    pub unlimited: bool,
+    pub keep_image_data: bool,
     output: Option<PathBuf>,
-    export_id: Option<String>,
-    keep_aspect_ratio: bool,
-    background_color: Option<Color>,
-    stylesheet: Option<PathBuf>,
-    unlimited: bool,
-    keep_image_data: bool,
     input: Vec<PathBuf>,
 }
 
@@ -195,10 +198,8 @@ impl Args {
         };
 
         let args = Args {
-            resolution: (
-                value_t!(matches, "res_x", f32)?,
-                value_t!(matches, "res_y", f32)?,
-            ),
+            dpi_x: value_t!(matches, "res_x", f64)?,
+            dpi_y: value_t!(matches, "res_y", f64)?,
             zoom: if matches.is_present("zoom") {
                 let zoom = value_t!(matches, "zoom", f32)?;
                 (zoom, zoom)
@@ -210,7 +211,6 @@ impl Args {
             width: value_t!(matches, "size_x", u32).or_none()?,
             height: value_t!(matches, "size_y", u32).or_none()?,
             format,
-            output: matches.value_of_os("output").map(PathBuf::from),
             export_id: value_t!(matches, "export_id", String)
                 .or_none()?
                 .map(lookup_id),
@@ -219,6 +219,7 @@ impl Args {
             stylesheet: matches.value_of_os("stylesheet").map(PathBuf::from),
             unlimited: matches.is_present("unlimited"),
             keep_image_data,
+            output: matches.value_of_os("output").map(PathBuf::from),
             input: match matches.values_of_os("FILE") {
                 Some(values) => values.map(PathBuf::from).collect(),
                 None => Vec::new(),
@@ -239,6 +240,14 @@ impl Args {
 
         Ok(args)
     }
+
+    pub fn output(&self) -> Option<&Path> {
+        self.output.as_deref()
+    }
+
+    pub fn input(&self) -> Input<'_> {
+        Input::new(&self.input)
+    }
 }
 
 trait NotFound {
diff --git a/src/bin/rsvg-convert/input.rs b/src/bin/rsvg-convert/input.rs
new file mode 100644
index 00000000..0e7637f4
--- /dev/null
+++ b/src/bin/rsvg-convert/input.rs
@@ -0,0 +1,94 @@
+// input file handling for rsvg-convert
+
+use core::ops::Deref;
+use gio::FileExt;
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+
+struct Stdin;
+
+impl Stdin {
+    pub fn stream() -> gio::UnixInputStream {
+        unsafe { gio::UnixInputStream::new(Self {}) }
+    }
+}
+
+impl std::os::unix::io::IntoRawFd for Stdin {
+    fn into_raw_fd(self) -> RawFd {
+        0 as RawFd
+    }
+}
+
+pub enum Input<'a> {
+    Paths(std::slice::Iter<'a, PathBuf>),
+    Stdin(std::iter::Once<gio::UnixInputStream>),
+}
+
+impl<'a> Input<'a> {
+    pub fn new(paths: &'a [PathBuf]) -> Self {
+        match paths.len() {
+            0 => Input::Stdin(std::iter::once(Stdin::stream())),
+            _ => Input::Paths(paths.iter()),
+        }
+    }
+}
+
+enum Stream {
+    File(gio::FileInputStream),
+    Unix(gio::UnixInputStream),
+}
+
+impl Deref for Stream {
+    type Target = gio::InputStream;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            Self::File(stream) => stream.as_ref(),
+            Self::Unix(stream) => stream.as_ref(),
+        }
+    }
+}
+
+pub struct Item {
+    stream: Stream,
+    file: Option<gio::File>,
+}
+
+impl Item {
+    fn from_file(file: gio::File) -> Self {
+        Self {
+            // TODO: unwrap
+            stream: Stream::File(file.read(None::<&gio::Cancellable>).unwrap()),
+            file: Some(file),
+        }
+    }
+    fn from_path(path: &PathBuf) -> Self {
+        Self::from_file(gio::File::new_for_path(path))
+    }
+
+    fn from_unix_stream(stream: gio::UnixInputStream) -> Self {
+        Self {
+            stream: Stream::Unix(stream),
+            file: None,
+        }
+    }
+
+    pub fn stream(&self) -> &gio::InputStream {
+        self.stream.deref()
+    }
+
+    pub fn file(&self) -> Option<&gio::File> {
+        self.file.as_ref()
+    }
+}
+
+impl Iterator for Input<'_> {
+    type Item = Item;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self {
+            Input::Paths(paths) => paths.next().map(Item::from_path),
+            Input::Stdin(iter) => iter.next().map(Item::from_unix_stream),
+        }
+    }
+}
diff --git a/src/bin/rsvg-convert/main.rs b/src/bin/rsvg-convert/main.rs
index c6e6cb7c..1c12fb55 100644
--- a/src/bin/rsvg-convert/main.rs
+++ b/src/bin/rsvg-convert/main.rs
@@ -2,9 +2,60 @@
 extern crate clap;
 
 mod cli;
+mod input;
+mod output;
+mod surface;
+
+use librsvg::{CairoRenderer, Loader};
+
+use crate::cli::Args;
+use crate::output::Stream;
+use crate::surface::Surface;
+
+fn load_stylesheet(args: &Args) -> std::io::Result<Option<String>> {
+    match args.stylesheet {
+        Some(ref filename) => std::fs::read_to_string(filename).map(Some),
+        None => Ok(None),
+    }
+}
 
 fn main() {
-    let args = cli::Args::new().unwrap_or_else(|e| e.exit());
+    let args = Args::new().unwrap_or_else(|e| e.exit());
+
+    let stylesheet = load_stylesheet(&args).expect("could not load stylesheet");
+    let mut target = None;
+
+    for input in args.input() {
+        let mut handle = Loader::new()
+            .with_unlimited_size(args.unlimited)
+            .keep_image_data(args.keep_image_data)
+            .read_stream(input.stream(), input.file(), None::<&gio::Cancellable>)
+            .expect("error loading SVG file");
+
+        if let Some(ref css) = stylesheet {
+            handle.set_stylesheet(&css).unwrap();
+        }
+
+        let renderer = CairoRenderer::new(&handle).with_dpi(args.dpi_x, args.dpi_y);
+
+        if target.is_none() {
+            target = match renderer.intrinsic_size_in_pixels() {
+                Some((width, height)) => {
+                    let output = Stream::new(args.output()).unwrap();
+                    Some(Surface::new(args.format, width, height, output).unwrap())
+                }
+                None => None,
+            };
+        }
+
+        if let Some(ref surface) = target {
+            surface
+                .render(&renderer, args.export_id.as_deref())
+                .unwrap();
+        }
+    }
 
-    println!("{:?}", args);
+    if let Some(ref mut surface) = target {
+        surface.finish().unwrap();
+    }
 }
diff --git a/src/bin/rsvg-convert/output.rs b/src/bin/rsvg-convert/output.rs
new file mode 100644
index 00000000..e6862ec4
--- /dev/null
+++ b/src/bin/rsvg-convert/output.rs
@@ -0,0 +1,44 @@
+// output stream for rsvg-convert
+
+use std::fs;
+use std::io;
+
+pub enum Stream {
+    File(fs::File),
+    Stdout(io::Stdout),
+}
+
+impl Stream {
+    pub fn new(path: Option<&std::path::Path>) -> io::Result<Self> {
+        match path {
+            Some(path) => {
+                let file = fs::File::create(path)?;
+                Ok(Self::File(file))
+            }
+            None => Ok(Self::Stdout(io::stdout())),
+        }
+    }
+}
+
+impl io::Write for Stream {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        match self {
+            Self::File(file) => file.write(buf),
+            Self::Stdout(stream) => stream.write(buf),
+        }
+    }
+
+    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+        match self {
+            Self::File(file) => file.write_all(buf),
+            Self::Stdout(stream) => stream.write_all(buf),
+        }
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        match self {
+            Self::File(file) => file.flush(),
+            Self::Stdout(stream) => stream.flush(),
+        }
+    }
+}
diff --git a/src/bin/rsvg-convert/surface.rs b/src/bin/rsvg-convert/surface.rs
new file mode 100644
index 00000000..262bb15c
--- /dev/null
+++ b/src/bin/rsvg-convert/surface.rs
@@ -0,0 +1,76 @@
+use core::ops::Deref;
+use std::io;
+
+use librsvg::{CairoRenderer, RenderingError};
+
+use crate::cli;
+use crate::output::Stream;
+
+pub enum Surface {
+    Png(cairo::ImageSurface, Stream),
+    Pdf(cairo::PdfSurface, (f64, f64)),
+}
+
+impl Deref for Surface {
+    type Target = cairo::Surface;
+
+    fn deref(&self) -> &cairo::Surface {
+        match self {
+            Self::Png(surface, _) => surface.deref(),
+            Self::Pdf(surface, _) => surface.deref(),
+        }
+    }
+}
+
+impl Surface {
+    pub fn new(
+        format: cli::Format,
+        width: f64,
+        height: f64,
+        stream: Stream,
+    ) -> Result<Self, cairo::Status> {
+        match format {
+            cli::Format::Png => {
+                cairo::ImageSurface::create(cairo::Format::ARgb32, width as i32, height as i32)
+                    .map(|s| Self::Png(s, stream))
+            }
+            cli::Format::Pdf => cairo::PdfSurface::for_stream(width, height, stream)
+                .map(|s| Self::Pdf(s, (width, height))),
+            _ => Err(cairo::Status::InvalidFormat),
+        }
+    }
+
+    fn size(&self) -> (f64, f64) {
+        match self {
+            Self::Png(s, _) => (s.get_width() as f64, s.get_height() as f64),
+            Self::Pdf(_, size) => *size,
+        }
+    }
+
+    pub fn render(&self, renderer: &CairoRenderer, id: Option<&str>) -> Result<(), RenderingError> {
+        let cr = cairo::Context::new(self);
+
+        let (width, height) = self.size();
+        let viewport = cairo::Rectangle {
+            x: 0.0,
+            y: 0.0,
+            width,
+            height,
+        };
+
+        renderer.render_layer(&cr, id, &viewport)?;
+        cr.show_page();
+
+        Ok(())
+    }
+
+    pub fn finish(&mut self) -> Result<(), cairo::IoError> {
+        match self {
+            Self::Png(surface, stream) => surface.write_to_png(stream),
+            Self::Pdf(surface, _) => surface
+                .finish_output_stream()
+                .map(|_| ())
+                .map_err(|e| cairo::IoError::Io(io::Error::from(e))),
+        }
+    }
+}
diff --git a/tests/src/cmdline/rsvg_convert.rs b/tests/src/cmdline/rsvg_convert.rs
index 36a764eb..1e1396c1 100644
--- a/tests/src/cmdline/rsvg_convert.rs
+++ b/tests/src/cmdline/rsvg_convert.rs
@@ -34,16 +34,8 @@ use tempfile::Builder;
 struct RsvgConvert {}
 
 impl RsvgConvert {
-    fn binary_location() -> &'static Path {
-        match option_env!("LIBRSVG_BUILD_DIR") {
-            Some(dir) => Path::new(dir),
-            None => Path::new(env!("CARGO_MANIFEST_DIR")),
-        }
-    }
-
     fn new() -> Command {
-        let path = Self::binary_location().join("rsvg-convert");
-        let mut command = Command::new(path);
+        let mut command = Command::cargo_bin("rsvg-convert").unwrap();
         command.env_clear();
         command
     }


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