[librsvg: 8/10] SvgPredicate with libxml parser
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 8/10] SvgPredicate with libxml parser
- Date: Tue, 7 Dec 2021 19:37:30 +0000 (UTC)
commit 66fffab3ed20fa253b5bcc11a124ac5164915844
Author: Daniel Petri Rocha <daniel petri tum de>
Date: Mon Nov 22 22:11:07 2021 +0100
SvgPredicate with libxml parser
Cargo.toml | 1 +
src/length.rs | 19 ++++
tests/src/cmdline/rsvg_convert.rs | 68 ++++++++++++-
tests/src/predicates/file.rs | 8 +-
tests/src/predicates/mod.rs | 1 +
tests/src/predicates/svg.rs | 198 ++++++++++++++++++++++++++++++++++++++
6 files changed, 290 insertions(+), 5 deletions(-)
---
diff --git a/Cargo.toml b/Cargo.toml
index 4560ec68..10933ac7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -90,6 +90,7 @@ proptest = "0.10.1"
tempfile = "3"
test-generator = "0.3"
yeslogic-fontconfig-sys = "2.11.1"
+libxml = "0.3.0"
[build-dependencies]
regex = "1.3.9"
diff --git a/src/length.rs b/src/length.rs
index b6a6d658..0d16b472 100644
--- a/src/length.rs
+++ b/src/length.rs
@@ -45,6 +45,7 @@
use cssparser::{match_ignore_ascii_case, Parser, Token, _cssparser_internal_to_lowercase};
use std::f64::consts::*;
use std::marker::PhantomData;
+use std::fmt;
use crate::dpi::Dpi;
use crate::drawing_ctx::ViewParams;
@@ -511,6 +512,24 @@ impl<N: Normalize> Parse for LengthOrAuto<N> {
}
}
+impl fmt::Display for LengthUnit {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let unit = match &self {
+ LengthUnit::Percent => "%",
+ LengthUnit::Px => "px",
+ LengthUnit::Em => "em",
+ LengthUnit::Ex => "ex",
+ LengthUnit::In => "in",
+ LengthUnit::Cm => "cm",
+ LengthUnit::Mm => "mm",
+ LengthUnit::Pt => "pt",
+ LengthUnit::Pc => "pc",
+ };
+
+ write!(f, "{}", unit)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/tests/src/cmdline/rsvg_convert.rs b/tests/src/cmdline/rsvg_convert.rs
index 78c7c625..a1ad58cc 100644
--- a/tests/src/cmdline/rsvg_convert.rs
+++ b/tests/src/cmdline/rsvg_convert.rs
@@ -16,6 +16,7 @@ use predicates::str::*;
use std::path::Path;
use tempfile::Builder;
use url::Url;
+use librsvg::{Length, LengthUnit};
// What should be tested here?
// The goal is to test the code in rsvg-convert, not the entire library.
@@ -152,7 +153,72 @@ fn output_format_svg_short_option() {
.arg("svg")
.assert()
.success()
- .stdout(file::is_svg());
+ .stdout(file::is_svg().with_svg_format());
+}
+
+#[cfg(system_deps_have_cairo_svg)]
+#[test]
+fn user_specified_width_and_height() {
+ RsvgConvert::new_with_input("tests/fixtures/dimensions/521-with-viewbox.svg")
+ .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() {
+ RsvgConvert::new_with_input("tests/fixtures/dimensions/521-with-viewbox.svg")
+ .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() {
+ RsvgConvert::new_with_input("tests/fixtures/dimensions/521-with-viewbox.svg")
+ .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]
diff --git a/tests/src/predicates/file.rs b/tests/src/predicates/file.rs
index 1a026ce1..244835e0 100644
--- a/tests/src/predicates/file.rs
+++ b/tests/src/predicates/file.rs
@@ -1,9 +1,9 @@
-use predicates::boolean::AndPredicate;
use predicates::prelude::*;
-use predicates::str::{ContainsPredicate, StartsWithPredicate};
+use predicates::str::{StartsWithPredicate};
use crate::predicates::pdf::PdfPredicate;
use crate::predicates::png::PngPredicate;
+use crate::predicates::svg::SvgPredicate;
/// Predicates to check that some output ([u8]) is of a certain file type
@@ -23,6 +23,6 @@ pub fn is_pdf() -> PdfPredicate {
PdfPredicate {}
}
-pub fn is_svg() -> AndPredicate<StartsWithPredicate, ContainsPredicate, str> {
- predicate::str::starts_with("<?xml ").and(predicate::str::contains("<svg "))
+pub fn is_svg() -> SvgPredicate {
+ SvgPredicate {}
}
diff --git a/tests/src/predicates/mod.rs b/tests/src/predicates/mod.rs
index 04e8daa5..81716b29 100644
--- a/tests/src/predicates/mod.rs
+++ b/tests/src/predicates/mod.rs
@@ -3,6 +3,7 @@ extern crate predicates;
pub mod file;
mod pdf;
mod png;
+mod svg;
use predicates::str;
diff --git a/tests/src/predicates/svg.rs b/tests/src/predicates/svg.rs
new file mode 100644
index 00000000..f9409126
--- /dev/null
+++ b/tests/src/predicates/svg.rs
@@ -0,0 +1,198 @@
+use librsvg::Length;
+use predicates::boolean::AndPredicate;
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use predicates::str::StartsWithPredicate;
+use predicates::str::*;
+use std::cmp;
+use std::fmt;
+use std::str;
+
+use libxml::parser::Parser;
+use libxml::xpath::Context;
+
+use librsvg::doctest_only::Both;
+use librsvg::rsvg_convert_only::ULength;
+use librsvg::Parse;
+
+/// Checks that the variable of type [u8] can be parsed as a SVG file.
+#[derive(Debug)]
+pub struct SvgPredicate {}
+
+impl SvgPredicate {
+ pub fn with_size(self: Self, width: Length, height: Length) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::Size(Dimensions {
+ w: width,
+ h: height,
+ }),
+ }
+ }
+
+ pub fn with_svg_format(
+ self: Self,
+ ) -> AndPredicate<StartsWithPredicate, ContainsPredicate, str> {
+ predicate::str::starts_with("<?xml ").and(predicate::str::contains("<svg "))
+ }
+}
+
+impl Predicate<[u8]> for SvgPredicate {
+ fn eval(&self, data: &[u8]) -> bool {
+ str::from_utf8(data).is_ok()
+ }
+
+ fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match str::from_utf8(data) {
+ Ok(_) => None,
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for SvgPredicate {}
+
+impl fmt::Display for SvgPredicate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "is an SVG")
+ }
+}
+
+/// Extends a SVG Predicate by a check for its size
+#[derive(Debug)]
+pub struct DetailPredicate<SvgPredicate> {
+ p: SvgPredicate,
+ d: Detail,
+}
+
+#[derive(Debug)]
+enum Detail {
+ Size(Dimensions),
+}
+
+/// SVG's dimensions
+#[derive(Debug)]
+struct Dimensions {
+ w: Length,
+ h: Length,
+}
+
+impl Dimensions {
+ pub fn width(self: &Self) -> f64 {
+ self.w.length
+ }
+
+ pub fn height(self: &Self) -> f64 {
+ self.h.length
+ }
+}
+
+impl fmt::Display for Dimensions {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}{} x {}{}",
+ self.width(),
+ self.w.unit,
+ self.height(),
+ self.h.unit
+ )
+ }
+}
+
+impl cmp::PartialEq for Dimensions {
+ fn eq(&self, other: &Self) -> bool {
+ approx_eq!(f64, self.width(), other.width(), epsilon = 0.000_001)
+ && approx_eq!(f64, self.height(), other.height(), epsilon = 0.000_001)
+ && (self.w.unit == self.h.unit)
+ && (self.h.unit == other.h.unit)
+ && (other.h.unit == other.w.unit)
+ }
+}
+
+impl cmp::Eq for Dimensions {}
+
+trait Details {
+ fn get_size(&self) -> Option<Dimensions>;
+}
+
+impl DetailPredicate<SvgPredicate> {
+ fn eval_doc(&self, doc: &str) -> bool {
+ match &self.d {
+ Detail::Size(d) => doc.get_size() == Some(Dimensions { w: d.w, h: d.h }),
+ }
+ }
+
+ fn find_case_for_doc<'a>(&'a self, expected: bool, doc: &str) -> Option<Case<'a>> {
+ if self.eval_doc(doc) == expected {
+ let product = self.product_for_doc(doc);
+ Some(Case::new(Some(self), false).add_product(product))
+ } else {
+ None
+ }
+ }
+
+ fn product_for_doc(&self, doc: &str) -> Product {
+ match &self.d {
+ Detail::Size(_) => Product::new(
+ "actual size",
+ match doc.get_size() {
+ Some(dim) => format!("{}", dim),
+ None => "None".to_string(),
+ },
+ ),
+ }
+ }
+}
+
+impl Details for &str {
+ fn get_size(self: &Self) -> Option<Dimensions> {
+ let parser = Parser::default();
+ let doc = parser.parse_string(self).unwrap();
+ let context = Context::new(&doc).unwrap();
+
+ let width = context.evaluate("//@width").unwrap().get_nodes_as_vec()[0].get_content();
+ let height = context.evaluate("//@height").unwrap().get_nodes_as_vec()[0].get_content();
+
+ let parsed_w = ULength::<Both>::parse_str(&width).unwrap();
+ let parsed_h = ULength::<Both>::parse_str(&height).unwrap();
+
+ let dim = Dimensions {
+ w: Length::new(parsed_w.length, parsed_w.unit),
+ h: Length::new(parsed_h.length, parsed_h.unit),
+ };
+
+ return Some(dim);
+ }
+}
+
+impl Predicate<[u8]> for DetailPredicate<SvgPredicate> {
+ fn eval(&self, data: &[u8]) -> bool {
+ match str::from_utf8(data) {
+ Ok(doc) => self.eval_doc(&doc),
+ _ => false,
+ }
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match str::from_utf8(data) {
+ Ok(doc) => self.find_case_for_doc(expected, &doc),
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for DetailPredicate<SvgPredicate> {
+ fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
+ let params = vec![Child::new("predicate", &self.p)];
+ Box::new(params.into_iter())
+ }
+}
+
+impl fmt::Display for DetailPredicate<SvgPredicate> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match &self.d {
+ Detail::Size(d) => write!(f, "is an SVG sized {}", d),
+ }
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]