[librsvg: 8/10] SvgPredicate with libxml parser




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]