diff options
author | Marge Bot <marge-bot@gnome.org> | 2023-01-31 03:05:13 +0000 |
---|---|---|
committer | Marge Bot <marge-bot@gnome.org> | 2023-01-31 03:05:13 +0000 |
commit | 7906bf841f0dfe1a2957f3c0d681223a9802fd71 (patch) | |
tree | 697a6b5f8458952b4d2bcd9fe622fba869f98dfa | |
parent | 3b93fce5929a83bc23cc652570d9f0478b0fd9e8 (diff) | |
parent | c3732c27163da108db978d4436e1da9efbb7f0bb (diff) | |
download | librsvg-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.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/bin/rsvg-bench.rs | 262 |
3 files changed, 265 insertions, 0 deletions
@@ -1125,6 +1125,7 @@ dependencies = [ "string_cache", "system-deps", "tempfile", + "thiserror", "tinyvec", "url", "xml5ever", @@ -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); + } + } +} |