//use crate::predicates::ends_with_pkg_version; mod internal_predicates; use internal_predicates::file; use assert_cmd::Command; use assert_cmd::assert::IntoOutputPredicate; use assert_cmd::cargo::cargo_bin_cmd; #[cfg(system_deps_have_cairo_pdf)] use chrono::{TimeZone, Utc}; use predicates::boolean::*; use predicates::prelude::*; use predicates::str::*; use rsvg::{Length, LengthUnit}; use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use tempfile::{Builder, NamedTempFile}; use url::Url; // What should be tested here? // The goal is to test the code in rsvg-convert, not the entire library. // // - command-line options that affect size (width, height, zoom, resolution) ✔ // - pixel dimensions of the output (should be sufficient to do that for PNG) ✔ // - limit on output size (32767 pixels) ✔ // - output formats (PNG, PDF, PS, EPS, SVG) ✔ // - multi-page output (for PDF) ✔ // - output file option ✔ // - SOURCE_DATA_EPOCH environment variable for PDF output ✔ // - background color option ✔ // - optional CSS stylesheet ✔ // - error handling for missing SVG dimensions ✔ // - error handling for export lookup ID ✔ // - error handling for invalid input ✔ struct RsvgConvert { command: Command, // This field is never read; it is just to store the temporary file, which will // be deleted when the struct is dropped. So, we mark it with dead_code. #[allow(dead_code)] config_file: NamedTempFile, } impl RsvgConvert { fn new() -> RsvgConvert { let config_file = setup_fontconfig_file(); let config_file_path: &Path = config_file.as_ref(); let path_str = config_file_path.to_str().unwrap(); eprintln!("FONTCONFIG_FILE={path_str}"); let mut command = cargo_bin_cmd!("rsvg-convert"); command.env("FONTCONFIG_FILE", config_file_path); RsvgConvert { command, config_file, } } fn new_with_input

(file: P) -> RsvgConvert where P: AsRef, { let mut rsvg_convert = RsvgConvert::new(); match rsvg_convert.command.pipe_stdin(&file) { Ok(_) => (), Err(e) => panic!("Error opening file '{}': {}", file.as_ref().display(), e), } rsvg_convert } fn accepts_arg(option: &str) { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert.command.arg(option).assert().success(); } fn option_yields_output(option: &str, output_pred: I) where I: IntoOutputPredicate

, P: Predicate<[u8]>, { let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg(option) .assert() .success() .stdout(output_pred); } } /// Creaates a temporary `fonts.conf` for the rsvg-convert tests suite and returns the path /// to that file. /// /// Explanation: a few of the tests for rsvg-convert need fonts to be installed, since /// otherwise Cairo/fontconfig won't generate the expected output. So, we need to /// /// * Create a fonts.conf. /// * Ensure that that fonts.conf has a `

` tag with the location of the font files. /// /// However the location of the font files is rsvg/tests/resources with respect to the /// toplevel directory of this librsvg source tree. The librsvg source tree is structured as a Cargo workspace, /// with these in the toplevel: /// /// * Cargo.toml /// * rsvg/tests/resources /// * rsvg_convert/Cargo.toml (manifest path for *this* rsvg_convert project) /// /// So, at this point, `CARGO_MANIFEST_DIR` points to /// `librsvg/rsvg_convert`, and we need to find /// `../rsvg/tests/resources` and canonicalize it into an absolute /// path. Then, we need to make a `fonts.conf` with that absolute /// path. fn setup_fontconfig_file() -> NamedTempFile { let fonts_dir = rsvg_tests_resources_path().into_os_string(); let fonts_dir_bytes = fonts_dir.as_encoded_bytes(); let mut file = NamedTempFile::new().expect("create temporary file for fonts.conf"); write!( file, r#" /tmp/rsvg_tests_fontconfig_cache "# ) .unwrap(); write!(file, "").unwrap(); file.write(fonts_dir_bytes).unwrap(); writeln!(file, "").unwrap(); write!( file, r#" sans sans-serif serif sans-serif sans-serif serif monospace sans-serif sans-serif Roboto "# ) .unwrap(); file } /// Computes the absolute path for the rsvg/tests/resources directory in the source tree fn rsvg_tests_resources_path() -> PathBuf { let relative_resources_path: PathBuf = [ env::var("CARGO_MANIFEST_DIR") .expect("Manifest directory unknown") .as_str(), "..", "rsvg", "tests", "resources", ] .iter() .collect(); let absolute_resources_path = relative_resources_path .canonicalize() .expect("canonicalizing rsvg/tests/resources path"); absolute_resources_path } #[test] fn converts_svg_from_stdin_to_png() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .assert() .success() .stdout(file::is_png()); } #[test] fn converts_svg_from_stdin_to_png_using_stdin_argument() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("-") .assert() .success() .stdout(file::is_png()); } #[test] fn argument_is_input_filename() { let input = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg(input) .assert() .success() .stdout(file::is_png()); } #[test] fn argument_is_url() { let path = Path::new("tests/fixtures/bug521-with-viewbox.svg") .canonicalize() .unwrap(); let url = Url::from_file_path(path).unwrap(); let stringified = url.as_str(); assert!(stringified.starts_with("file://")); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg(stringified) .assert() .success() .stdout(file::is_png()); } #[test] fn output_format_png() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=png") .assert() .success() .stdout(file::is_png()); } #[cfg(system_deps_have_cairo_ps)] #[test] fn output_format_ps() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=ps") .assert() .success() .stdout(file::is_ps()); } #[cfg(system_deps_have_cairo_ps)] #[test] fn output_format_eps() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=eps") .assert() .success() .stdout(file::is_eps()); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn output_format_pdf() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=pdf") .assert() .success() .stdout(file::is_pdf()); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn output_format_pdf_1_7() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=pdf1.7") .assert() .success() .stdout(file::is_pdf().with_version("1.7")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn output_format_pdf_1_6() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=pdf1.6") .assert() .success() .stdout(file::is_pdf().with_version("1.6")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn output_format_pdf_1_5() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=pdf1.5") .assert() .success() .stdout(file::is_pdf().with_version("1.5")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn output_format_pdf_1_4() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format=pdf1.4") .assert() .success() .stdout(file::is_pdf().with_version("1.4")); } #[cfg(system_deps_have_cairo_svg)] #[test] fn output_format_svg_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("-f") .arg("svg") .assert() .success() .stdout(file::is_svg()); } #[cfg(system_deps_have_cairo_svg)] #[test] fn user_specified_width_and_height() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format") .arg("svg") .arg("--width") .arg("42cm") .arg("--height") .arg("43cm") .assert() .success() .stdout(file::is_svg().with_size( Length::new(42.0, LengthUnit::Cm), Length::new(43.0, LengthUnit::Cm), )); } #[cfg(system_deps_have_cairo_svg)] #[test] fn user_specified_width_and_height_px_output() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format") .arg("svg") .arg("--width") .arg("1920") .arg("--height") .arg("508mm") .assert() .success() .stdout(file::is_svg().with_size( Length::new(1920.0, LengthUnit::Px), Length::new(1920.0, LengthUnit::Px), )); } #[cfg(system_deps_have_cairo_svg)] #[test] fn user_specified_width_and_height_a4() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--format") .arg("svg") .arg("--page-width") .arg("210mm") .arg("--page-height") .arg("297mm") .arg("--left") .arg("1cm") .arg("--top") .arg("1cm") .arg("--width") .arg("190mm") .arg("--height") .arg("277mm") .assert() .success() .stdout(file::is_svg().with_size( Length::new(210.0, LengthUnit::Mm), Length::new(297.0, LengthUnit::Mm), )); } #[test] fn output_file_option() { let output = { let tempfile = Builder::new().suffix(".png").tempfile().unwrap(); tempfile.path().to_path_buf() }; assert!(predicates::path::is_file().not().eval(&output)); let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg(format!("--output={}", output.display())) .assert() .success() .stdout(is_empty()); assert!(predicates::path::is_file().eval(&output)); std::fs::remove_file(&output).unwrap(); } #[test] fn output_file_short_option() { let output = { let tempfile = Builder::new().suffix(".png").tempfile().unwrap(); tempfile.path().to_path_buf() }; assert!(predicates::path::is_file().not().eval(&output)); let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("-o") .arg(format!("{}", output.display())) .assert() .success() .stdout(is_empty()); assert!(predicates::path::is_file().eval(&output)); std::fs::remove_file(&output).unwrap(); } #[test] fn overwrites_existing_output_file() { let output = { let tempfile = Builder::new().suffix(".png").tempfile().unwrap(); tempfile.path().to_path_buf() }; assert!(predicates::path::is_file().not().eval(&output)); for _ in 0..2 { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg(format!("--output={}", output.display())) .assert() .success() .stdout(is_empty()); assert!(predicates::path::is_file().eval(&output)); } std::fs::remove_file(&output).unwrap(); } #[test] fn empty_input_yields_error() { let starts_with = starts_with("Error reading SVG"); let ends_with = ends_with("Input file is too short").trim(); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .assert() .failure() .stderr(starts_with.and(ends_with)); } #[test] fn empty_svg_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty.svg"); rsvg_convert .command .assert() .failure() .stderr("The SVG stdin has no dimensions\n"); } #[test] fn multiple_input_files_not_allowed_for_png_output() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg(one) .arg(two) .assert() .failure() .stderr(contains( "Multiple SVG files are only allowed for PDF and (E)PS output", )); } #[cfg(system_deps_have_cairo_ps)] #[test] fn multiple_input_files_accepted_for_eps_output() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=eps") .arg(one) .arg(two) .assert() .success() .stdout(file::is_eps()); } #[cfg(system_deps_have_cairo_ps)] #[test] fn multiple_input_files_accepted_for_ps_output() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=ps") .arg(one) .arg(two) .assert() .success() .stdout(file::is_ps()); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn multiple_input_files_create_multi_page_pdf_output() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let three = Path::new("tests/fixtures/example.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg(one) .arg(two) .arg(three) .assert() .success() .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)] #[test] fn multiple_input_files_create_multi_page_pdf_output_fixed_size() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let three = Path::new("tests/fixtures/example.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg("--page-width=8.5in") .arg("--page-height=11in") .arg("--width=7.5in") .arg("--height=10in") .arg("--left=0.5in") .arg("--top=0.5in") .arg("--keep-aspect-ratio") .arg(one) .arg(two) .arg(three) .assert() .success() .stdout( file::is_pdf() .with_page_count(3) // https://www.wolframalpha.com/input/?i=convert+11+inches+to+desktop+publishing+points .and(file::is_pdf().with_page_size(0, 612.0, 792.0)) .and(file::is_pdf().with_page_size(1, 612.0, 792.0)) .and(file::is_pdf().with_page_size(2, 612.0, 792.0)), ); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_has_link() { let input = Path::new("tests/fixtures/a-link.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg(input) .assert() .success() .stdout(file::is_pdf().with_link("https://example.com")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_has_link_inside_text() { let input = Path::new("tests/fixtures/text-a-link.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg(input) .assert() .success() .stdout( file::is_pdf() .with_link("https://example.com") .and(file::is_pdf().with_link("https://another.example.com")), ); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_has_text() { let input = Path::new("tests/fixtures/hello-world.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg(input) .assert() .success() .stdout( file::is_pdf() .with_text("Hello world!") .and(file::is_pdf().with_text("Hello again!")), ); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn env_source_data_epoch_controls_pdf_creation_date() { let input = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let date = 1581411039; // seconds since epoch let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .env("SOURCE_DATE_EPOCH", format!("{}", date)) .arg("--format=pdf") .arg(input) .assert() .success() .stdout(file::is_pdf().with_creation_date(Utc.timestamp_opt(date, 0).unwrap())); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn env_source_data_epoch_no_digits() { // intentionally not testing for the full error string here let input = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .env("SOURCE_DATE_EPOCH", "foobar") .arg("--format=pdf") .arg(input) .assert() .failure() .stderr(starts_with("Environment variable $SOURCE_DATE_EPOCH")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn env_source_data_epoch_trailing_garbage() { // intentionally not testing for the full error string here let input = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .env("SOURCE_DATE_EPOCH", "1234556+") .arg(input) .assert() .failure() .stderr(starts_with("Environment variable $SOURCE_DATE_EPOCH")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn env_source_data_epoch_empty() { // intentionally not testing for the full error string here let input = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .env("SOURCE_DATE_EPOCH", "") .arg(input) .assert() .failure() .stderr(starts_with("Environment variable $SOURCE_DATE_EPOCH")); } #[test] fn width_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--width=300") .assert() .success() .stdout(file::is_png().with_size(300, 150)); } #[test] fn height_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--height=200") .assert() .success() .stdout(file::is_png().with_size(400, 200)); } #[test] fn width_and_height_options() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--width=300") .arg("--height=200") .assert() .success() .stdout(file::is_png().with_size(300, 200)); } #[test] fn unsupported_unit_in_width_and_height() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--height=200ex") .assert() .failure() .stderr(contains("supported units")); } #[test] fn invalid_length() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--page-width=foo") .assert() .failure() .stderr(contains("can not be parsed as a length")); } #[test] fn zoom_factor() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--zoom=0.8") .assert() .success() .stdout(file::is_png().with_size(160, 80)); } #[test] fn zoom_factor_and_larger_size() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--width=400") .arg("--height=200") .arg("--zoom=1.5") .assert() .success() .stdout(file::is_png().with_size(300, 150)); } #[test] fn zoom_factor_and_smaller_size() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--width=400") .arg("--height=200") .arg("--zoom=3.5") .assert() .success() .stdout(file::is_png().with_size(400, 200)); } #[test] fn x_zoom_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--x-zoom=2") .assert() .success() .stdout(file::is_png().with_size(400, 100)); } #[test] fn x_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("-x") .arg("2.0") .assert() .success() .stdout(file::is_png().with_size(400, 100)); } #[test] fn y_zoom_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--y-zoom=2.0") .assert() .success() .stdout(file::is_png().with_size(200, 200)); } #[test] fn y_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("-y") .arg("2") .assert() .success() .stdout(file::is_png().with_size(200, 200)); } #[test] fn huge_zoom_factor_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--zoom=1000") .assert() .failure() .stderr(starts_with( "The resulting image would be larger than 32767 pixels", )); } #[test] fn negative_zoom_factor_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--zoom=-2") .assert() .failure() .stderr(contains("Invalid zoom")); } #[test] fn invalid_zoom_factor_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug521-with-viewbox.svg"); rsvg_convert .command .arg("--zoom=foo") .assert() .failure() .stderr(contains("invalid value")); } #[test] fn default_resolution_is_96dpi() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .assert() .success() .stdout(file::is_png().with_size(96, 384)); } #[test] fn x_resolution() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-x=300") .assert() .success() .stdout(file::is_png().with_size(300, 384)); } #[test] fn x_resolution_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-d") .arg("45") .assert() .success() .stdout(file::is_png().with_size(45, 384)); } #[test] fn y_resolution() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-y=300") .assert() .success() .stdout(file::is_png().with_size(96, 1200)); } #[test] fn y_resolution_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-p") .arg("45") .assert() .success() .stdout(file::is_png().with_size(96, 180)); } #[test] fn x_and_y_resolution() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-x=300") .arg("--dpi-y=150") .assert() .success() .stdout(file::is_png().with_size(300, 600)); } #[test] fn zero_resolution_is_invalid() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-x=0") .arg("--dpi-y=0") .assert() .failure() .stderr(contains("Invalid resolution")); } #[test] fn negative_resolution_is_invalid() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-x=-100") .arg("--dpi-y=-100") .assert() .failure() .stderr(contains("Invalid resolution")); } #[test] fn unparsable_resolution_is_invalid() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--dpi-x=blah") .arg("--dpi-y=blah") .assert() .failure() .stderr(contains("invalid value")); // this comes from clap } #[test] fn zero_offset_png() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--page-width=640") .arg("--page-height=480") .arg("--width=200") .arg("--height=100") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/zero-offset-png.png")); } #[test] fn offset_png() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--page-width=640") .arg("--page-height=480") .arg("--width=200") .arg("--height=100") .arg("--left=100") .arg("--top=50") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/offset-png.png")); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn unscaled_pdf_size() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .assert() .success() .stdout(file::is_pdf().with_page_size(0, 72.0, 72.0)); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_size_width_height() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .arg("--width=2in") .arg("--height=3in") .assert() .success() .stdout(file::is_pdf().with_page_size(0, 144.0, 216.0)); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_size_width_height_proportional() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .arg("--width=2in") .arg("--height=3in") .arg("--keep-aspect-ratio") .assert() .success() .stdout(file::is_pdf().with_page_size(0, 144.0, 144.0)); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn pdf_page_size() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .arg("--page-width=210mm") .arg("--page-height=297mm") .assert() .success() .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)] #[test] fn multiple_input_files_create_multi_page_pdf_size_override() { let one = Path::new("tests/fixtures/bug521-with-viewbox.svg"); let two = Path::new("tests/fixtures/sub-rect-no-unit.svg"); let three = Path::new("tests/fixtures/example.svg"); let mut rsvg_convert = RsvgConvert::new(); rsvg_convert .command .arg("--format=pdf") .arg("--width=300pt") .arg("--height=200pt") .arg(one) .arg(two) .arg(three) .assert() .success() .stdout( file::is_pdf() .with_page_count(3) .and(file::is_pdf().with_page_size(0, 300.0, 200.0)) .and(file::is_pdf().with_page_size(1, 300.0, 200.0)) .and(file::is_pdf().with_page_size(2, 300.0, 200.0)), ); } #[cfg(system_deps_have_cairo_pdf)] #[test] fn missing_page_size_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .arg("--page-width=210mm") .assert() .failure() .stderr(contains("both").and(contains("options"))); let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dimensions-in.svg"); rsvg_convert .command .arg("--format=pdf") .arg("--page-height=297mm") .assert() .failure() .stderr(contains("both").and(contains("options"))); } #[test] fn does_not_clip_partial_coverage_pixels() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug677-partial-pixel.svg"); rsvg_convert .command .assert() .success() .stdout(file::is_png().with_size(2, 2)); } #[test] fn background_color_option_with_valid_color() { RsvgConvert::accepts_arg("--background-color=LimeGreen"); } #[test] fn background_color_option_none() { RsvgConvert::accepts_arg("--background-color=None"); } #[test] fn background_color_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-b") .arg("#aabbcc") .assert() .success(); } #[test] fn background_color_option_invalid_color_yields_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--background-color=foobar") .assert() .failure() .stderr(contains("Invalid").and(contains("color"))); } #[test] fn background_color_is_rendered() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/gimp-wilber.svg"); rsvg_convert .command .arg("--background-color=purple") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/gimp-wilber-ref.png")); } #[test] fn background_color_rgb() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=rgb(0, 255, 0)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-ref.png")); } #[test] fn background_color_rgba() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=rgba(0, 255, 0, 0.5)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-transparent-ref.png")); } #[test] fn background_color_hsl() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=hsl(120, 100%, 50%)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-ref.png")); } #[test] fn background_color_hsla() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=hsla(120, 100%, 50%, 0.5)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-transparent-ref.png")); } #[test] fn background_color_hwb() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=hwb(120 0% 0%)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-ref.png")); } #[test] fn background_color_hwba() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg("--background-color=hwb(120 0% 0% / 0.5)") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/lime-transparent-ref.png")); } fn test_unsupported_background_color(color: &str) { let color_arg = format!("--background-color={color}"); let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/empty-10x10.svg"); rsvg_convert .command .arg("--width=10") .arg("--height=10") .arg(&color_arg) .assert() .failure() .stderr(contains("Invalid value").and(contains("unsupported color syntax"))); } #[test] fn unsupported_background_color() { let colors = [ "lab(62.2345% -34.9638 47.7721)", "lch(62.2345% 59.2 126.2)", "oklab(66.016% -0.1084 0.1114)", "oklch(0.66016 0.15546 134.231)", "color(display-p3 -0.6112 1.0079 -0.2192)", ]; for c in &colors { test_unsupported_background_color(c); } } #[test] fn stylesheet_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--stylesheet=tests/fixtures/empty.css") .assert() .success(); } #[test] fn stylesheet_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-s") .arg("tests/fixtures/empty.css") .assert() .success(); } #[test] fn stylesheet_option_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--stylesheet=foobar") .assert() .failure() .stderr(starts_with("Error reading stylesheet")); } #[test] fn export_id_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/geometry-element.svg"); rsvg_convert .command .arg("--export-id=foo") .assert() .success() .stdout(file::is_png().with_size(40, 50)); } #[test] fn export_id_with_zero_stroke_width() { // https://gitlab.gnome.org/GNOME/librsvg/-/issues/601 // // This tests a bug that manifested itself easily with the --export-id option, but it // is not a bug with the option itself. An object with stroke_width=0 was causing // an extra point at the origin to be put in the bounding box, so the final image // spanned the origin to the actual visible bounds of the rendered object. // // We can probably test this more cleanly once we have a render tree. let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug601-zero-stroke-width.svg"); rsvg_convert .command .arg("--export-id=foo") .assert() .success() .stdout( file::is_png() .with_contents("tests/fixtures/bug601-zero-stroke-width-render-only-foo.png"), ); } #[test] fn export_id_short_option() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-i") .arg("two") .assert() .success() .stdout(file::is_png().with_size(100, 200)); } #[test] fn export_id_with_hash_prefix() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("-i") .arg("#two") .assert() .success() .stdout(file::is_png().with_size(100, 200)); } #[test] fn export_id_option_error() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/dpi.svg"); rsvg_convert .command .arg("--export-id=foobar") .assert() .failure() .stderr(starts_with("File stdin does not have an object with id \"")); } #[test] fn unlimited_option() { RsvgConvert::accepts_arg("--unlimited"); } #[test] fn unlimited_short_option() { RsvgConvert::accepts_arg("-u"); } #[test] fn keep_aspect_ratio_option() { let input = Path::new("tests/fixtures/dpi.svg"); let mut rsvg_convert = RsvgConvert::new_with_input(input); rsvg_convert .command .arg("--width=500") .arg("--height=1000") .assert() .success() .stdout(file::is_png().with_size(500, 1000)); let mut rsvg_convert = RsvgConvert::new_with_input(input); rsvg_convert .command .arg("--width=500") .arg("--height=1000") .arg("--keep-aspect-ratio") .assert() .success() .stdout(file::is_png().with_size(250, 1000)); } #[test] fn keep_aspect_ratio_short_option() { let input = Path::new("tests/fixtures/dpi.svg"); let mut rsvg_convert = RsvgConvert::new_with_input(input); rsvg_convert .command .arg("--width=1000") .arg("--height=500") .assert() .success() .stdout(file::is_png().with_size(1000, 500)); let mut rsvg_convert = RsvgConvert::new_with_input(input); rsvg_convert .command .arg("--width=1000") .arg("--height=500") .arg("-a") .assert() .success() .stdout(file::is_png().with_size(125, 500)); } #[test] fn overflowing_size_is_detected() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/bug591-vbox-overflow.svg"); rsvg_convert.command.assert().failure().stderr(starts_with( "The resulting image would be larger than 32767 pixels", )); } #[test] fn accept_language_given() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/accept-language.svg"); rsvg_convert .command .arg("--accept-language=es-MX") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/accept-language-es.png")); let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/accept-language.svg"); rsvg_convert .command .arg("--accept-language=de") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/accept-language-de.png")); } #[test] fn accept_language_fallback() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/accept-language.svg"); rsvg_convert .command .arg("--accept-language=fr") .assert() .success() .stdout(file::is_png().with_contents("tests/fixtures/accept-language-fallback.png")); } #[test] fn accept_language_invalid_tag() { // underscores are not valid in BCP47 language tags let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/accept-language.svg"); rsvg_convert .command .arg("--accept-language=foo_bar") .assert() .failure() .stderr(contains("invalid language tag")); } #[test] fn keep_image_data_option() { RsvgConvert::accepts_arg("--keep-image-data"); } #[test] fn no_keep_image_data_option() { RsvgConvert::accepts_arg("--no-keep-image-data"); } fn is_version_output() -> RegexPredicate { predicates::str::is_match(r"rsvg-convert version \d+\.\d+\.\d+").unwrap() } #[test] fn version_option() { RsvgConvert::option_yields_output("--version", is_version_output()) } #[test] fn version_short_option() { RsvgConvert::option_yields_output("-v", is_version_output()) } fn is_usage_output() -> OrPredicate { contains("Usage:").or(contains("USAGE:")) } #[test] fn help_option() { RsvgConvert::option_yields_output("--help", is_usage_output()) } #[test] fn help_short_option() { RsvgConvert::option_yields_output("-?", is_usage_output()) } #[test] fn multiple_stdin_arguments_not_allowed() { let mut rsvg_convert = RsvgConvert::new_with_input("tests/fixtures/accept-language.svg"); rsvg_convert .command .arg("-") .arg("-") .assert() .failure() .stderr(contains("Only one input file can be read from stdin")); }