summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarge Bot <marge-bot@gnome.org>2023-01-31 03:05:13 +0000
committerMarge Bot <marge-bot@gnome.org>2023-01-31 03:05:13 +0000
commit7906bf841f0dfe1a2957f3c0d681223a9802fd71 (patch)
tree697a6b5f8458952b4d2bcd9fe622fba869f98dfa
parent3b93fce5929a83bc23cc652570d9f0478b0fd9e8 (diff)
parentc3732c27163da108db978d4436e1da9efbb7f0bb (diff)
downloadlibrsvg-7906bf841f0dfe1a2957f3c0d681223a9802fd71.tar.gz
Merge branch 'rsvg-bench' into 'main'
Copy the sources of rsvg-bench to here. See merge request GNOME/librsvg!789
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml2
-rw-r--r--src/bin/rsvg-bench.rs262
3 files changed, 265 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 22dc34cf..d15b94e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1125,6 +1125,7 @@ dependencies = [
"string_cache",
"system-deps",
"tempfile",
+ "thiserror",
"tinyvec",
"url",
"xml5ever",
diff --git a/Cargo.toml b/Cargo.toml
index 811b25c6..20babd44 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -51,6 +51,7 @@ crate-type = [ "staticlib", "rlib" ]
[dependencies]
# Keep these in sync with respect to the cairo-rs version:
# src/lib.rs - toplevel example in the docs
+anyhow = "1.0"
byteorder = "1.4"
cairo-rs = { version = "0.16", features=["v1_16", "png", "pdf", "ps", "svg"] }
cast = "0.3.0"
@@ -80,6 +81,7 @@ regex = "1"
rgb = { version="0.8", features=["argb"] }
selectors = "0.23.0"
string_cache = "0.8.0"
+thiserror = "1.0"
tinyvec = { version = "1.2.0", features = ["alloc"] }
url = "2"
xml5ever = "0.17.0"
diff --git a/src/bin/rsvg-bench.rs b/src/bin/rsvg-bench.rs
new file mode 100644
index 00000000..b641eaa4
--- /dev/null
+++ b/src/bin/rsvg-bench.rs
@@ -0,0 +1,262 @@
+#![allow(unknown_lints)]
+#![deny(clippy)]
+#![warn(unused)]
+
+use anyhow::Result;
+use clap::{crate_version, value_parser};
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::process;
+use std::thread;
+use std::time::Duration;
+use thiserror::Error;
+
+#[derive(Debug)]
+/// Command-line options for `rsvg-bench`.
+struct Opt {
+ /// Number of seconds to sleep before starting to process SVGs.
+ sleep_secs: usize,
+
+ /// Number of times to parse each file.
+ num_parse: usize,
+
+ /// Number of times to render each file.
+ num_render: usize,
+
+ /// Whether to stop all processing when a file cannot be rendered.
+ hard_failures: bool,
+
+ /// Input files or directories.
+ inputs: Vec<PathBuf>,
+}
+
+#[derive(Debug)]
+enum LoadingError {
+ Skipped,
+ Rsvg(librsvg::LoadingError),
+}
+
+#[derive(Debug, Error)]
+enum ProcessingError {
+ #[error("Cairo error: {error:?}")]
+ CairoError { error: cairo::Error },
+
+ #[error("Rendering error")]
+ RenderingError,
+}
+
+impl From<cairo::Error> for ProcessingError {
+ fn from(error: cairo::Error) -> ProcessingError {
+ ProcessingError::CairoError { error }
+ }
+}
+
+impl From<librsvg::RenderingError> for ProcessingError {
+ fn from(_: librsvg::RenderingError) -> ProcessingError {
+ ProcessingError::RenderingError
+ }
+}
+
+fn process_path<P: AsRef<Path>>(opt: &Opt, path: P) -> Result<()> {
+ let meta = fs::metadata(&path)?;
+
+ if meta.is_dir() {
+ process_directory(opt, path)?;
+ } else if let Some(ext) = path.as_ref().extension() {
+ if ext == "svg" || ext == "SVG" {
+ process_file(opt, &path)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn process_directory<P: AsRef<Path>>(opt: &Opt, path: P) -> Result<()> {
+ println!("Processing {:?}", path.as_ref());
+
+ for entry in fs::read_dir(path)? {
+ let entry = entry?;
+ process_path(opt, &entry.path())?;
+ }
+
+ Ok(())
+}
+
+fn read_svg(opt: &Opt, path: &Path) -> Result<librsvg::SvgHandle, LoadingError> {
+ match (opt.hard_failures, librsvg::Loader::new().read_path(path)) {
+ (_, Ok(h)) => Ok(h),
+ (false, Err(e)) => {
+ println!(
+ "skipping {} due to error when loading: {}",
+ path.to_string_lossy(),
+ e
+ );
+ Err(LoadingError::Skipped)
+ }
+ (true, Err(e)) => Err(LoadingError::Rsvg(e)),
+ }
+}
+
+fn process_file<P: AsRef<Path>>(opt: &Opt, path: P) -> Result<()> {
+ println!("Processing {:?}", path.as_ref());
+
+ assert!(opt.num_parse > 0);
+
+ let path = path.as_ref();
+
+ for _ in 0..opt.num_parse - 1 {
+ match read_svg(opt, path) {
+ Ok(_) => (),
+ Err(LoadingError::Skipped) => return Ok(()),
+ Err(LoadingError::Rsvg(e)) => return Err(e.into()),
+ }
+ }
+
+ let handle = match read_svg(opt, path) {
+ Ok(h) => h,
+ Err(LoadingError::Skipped) => return Ok(()),
+ Err(LoadingError::Rsvg(e)) => return Err(e.into()),
+ };
+
+ for _ in 0..opt.num_render {
+ render_to_cairo(opt, &handle)?;
+ }
+
+ Ok(())
+}
+
+fn render_to_cairo(opt: &Opt, handle: &librsvg::SvgHandle) -> Result<(), ProcessingError> {
+ let renderer = librsvg::CairoRenderer::new(handle);
+
+ let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 100, 100)?;
+ let cr = cairo::Context::new(&surface)?;
+
+ let viewport = cairo::Rectangle::new(0.0, 0.0, 100.0, 100.0);
+
+ match (opt.hard_failures, renderer.render_document(&cr, &viewport)) {
+ (_, Ok(_)) => Ok(()),
+ (false, Err(e)) => {
+ println!("could not render: {}", e);
+ Ok(())
+ }
+ (true, Err(e)) => Err(e.into()),
+ }
+}
+
+fn sleep(secs: usize) {
+ thread::sleep(Duration::from_secs(secs as u64))
+}
+
+fn print_options(opt: &Opt) {
+ println!("Will parse each file {} times", opt.num_parse);
+ println!("Will render each file {} times", opt.num_render);
+ if opt.num_render > 0 {
+ println!("Rendering to Cairo image surface");
+ }
+ println!(
+ "Sleeping for {} seconds before processing SVGs...",
+ opt.sleep_secs
+ );
+}
+
+fn run(opt: &Opt) -> Result<()> {
+ print_options(opt);
+
+ sleep(opt.sleep_secs);
+ println!("Processing files!");
+
+ for path in &opt.inputs {
+ process_path(opt, &path)?;
+ }
+
+ Ok(())
+}
+
+fn build_cli() -> clap::Command {
+ clap::Command::new("rsvg-bench")
+ .version(concat!("version ", crate_version!()))
+ .about("Benchmarking utility for librsvg.")
+ .arg(
+ clap::Arg::new("sleep")
+ .long("sleep")
+ .help("Number of seconds to sleep before starting to process SVGs")
+ .default_value("0")
+ .value_parser(str::parse::<usize>),
+ )
+ .arg(
+ clap::Arg::new("num-parse")
+ .long("num-parse")
+ .help("Number of times to parse each file")
+ .default_value("1")
+ .value_parser(str::parse::<usize>),
+ )
+ .arg(
+ clap::Arg::new("num-render")
+ .long("num-render")
+ .help("Number of times to render each file")
+ .default_value("1")
+ .value_parser(str::parse::<usize>),
+ )
+ .arg(
+ clap::Arg::new("hard-failures")
+ .long("hard-failures")
+ .help("Stop all processing when a file cannot be rendered")
+ .action(clap::ArgAction::SetTrue),
+ )
+ .arg(
+ clap::Arg::new("inputs")
+ .help("Input files or directories")
+ .value_parser(value_parser!(PathBuf))
+ .action(clap::ArgAction::Append),
+ )
+}
+
+fn main() {
+ let cli = build_cli();
+
+ let matches = cli.get_matches();
+
+ let sleep_secs = matches
+ .get_one("sleep")
+ .copied()
+ .expect("already provided default_value");
+ let num_parse = matches
+ .get_one("num-parse")
+ .copied()
+ .expect("already provided default_value");
+ let num_render = matches
+ .get_one("num-render")
+ .copied()
+ .expect("already provided default_value");
+ let hard_failures = matches.get_flag("hard-failures");
+
+ let inputs = if let Some(inputs) = matches.get_many("inputs") {
+ inputs.cloned().collect()
+ } else {
+ eprintln!("Must specify at least one SVG file or directory to process\n");
+ process::exit(1);
+ };
+
+ let opt = Opt {
+ sleep_secs,
+ num_parse,
+ num_render,
+ hard_failures,
+ inputs,
+ };
+
+ if opt.num_parse < 1 {
+ eprintln!("Must parse files at least 1 time; please specify a higher number\n");
+ process::exit(1);
+ }
+
+ println!("hard_failures: {:?}", opt.hard_failures);
+
+ match run(&opt) {
+ Ok(_) => (),
+ Err(e) => {
+ eprintln!("{}", e);
+ process::exit(1);
+ }
+ }
+}