summaryrefslogtreecommitdiff
path: root/rsvg_convert/tests/internal_predicates
diff options
context:
space:
mode:
Diffstat (limited to 'rsvg_convert/tests/internal_predicates')
-rw-r--r--rsvg_convert/tests/internal_predicates/file.rs28
-rw-r--r--rsvg_convert/tests/internal_predicates/mod.rs4
-rw-r--r--rsvg_convert/tests/internal_predicates/pdf.rs358
-rw-r--r--rsvg_convert/tests/internal_predicates/png.rs193
-rw-r--r--rsvg_convert/tests/internal_predicates/svg.rs179
5 files changed, 762 insertions, 0 deletions
diff --git a/rsvg_convert/tests/internal_predicates/file.rs b/rsvg_convert/tests/internal_predicates/file.rs
new file mode 100644
index 00000000..a7af5acf
--- /dev/null
+++ b/rsvg_convert/tests/internal_predicates/file.rs
@@ -0,0 +1,28 @@
+use predicates::prelude::*;
+use predicates::str::StartsWithPredicate;
+
+use super::pdf::PdfPredicate;
+use super::png::PngPredicate;
+use super::svg::SvgPredicate;
+
+/// Predicates to check that some output ([u8]) is of a certain file type
+
+pub fn is_png() -> PngPredicate {
+ PngPredicate {}
+}
+
+pub fn is_ps() -> StartsWithPredicate {
+ predicate::str::starts_with("%!PS-Adobe-3.0\n")
+}
+
+pub fn is_eps() -> StartsWithPredicate {
+ predicate::str::starts_with("%!PS-Adobe-3.0 EPSF-3.0\n")
+}
+
+pub fn is_pdf() -> PdfPredicate {
+ PdfPredicate {}
+}
+
+pub fn is_svg() -> SvgPredicate {
+ SvgPredicate {}
+}
diff --git a/rsvg_convert/tests/internal_predicates/mod.rs b/rsvg_convert/tests/internal_predicates/mod.rs
new file mode 100644
index 00000000..7e15354d
--- /dev/null
+++ b/rsvg_convert/tests/internal_predicates/mod.rs
@@ -0,0 +1,4 @@
+pub mod file;
+mod pdf;
+mod png;
+mod svg;
diff --git a/rsvg_convert/tests/internal_predicates/pdf.rs b/rsvg_convert/tests/internal_predicates/pdf.rs
new file mode 100644
index 00000000..f7872d71
--- /dev/null
+++ b/rsvg_convert/tests/internal_predicates/pdf.rs
@@ -0,0 +1,358 @@
+use chrono::{DateTime, Utc};
+use float_cmp::approx_eq;
+use lopdf::{self, Dictionary, Object};
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use std::cmp;
+use std::fmt;
+
+/// Checks that the variable of type [u8] can be parsed as a PDF file.
+#[derive(Debug)]
+pub struct PdfPredicate {}
+
+impl PdfPredicate {
+ pub fn with_page_count(self: Self, num_pages: usize) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::PageCount(num_pages),
+ }
+ }
+
+ pub fn with_page_size(
+ self: Self,
+ idx: usize,
+ width_in_points: f32,
+ height_in_points: f32,
+ ) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::PageSize(
+ Dimensions {
+ w: width_in_points,
+ h: height_in_points,
+ unit: 1.0,
+ },
+ idx,
+ ),
+ }
+ }
+
+ pub fn with_creation_date(self: Self, when: DateTime<Utc>) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::CreationDate(when),
+ }
+ }
+
+ pub fn with_link(self: Self, link: &str) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::Link(link.to_string()),
+ }
+ }
+
+ pub fn with_text(self: Self, text: &str) -> DetailPredicate<Self> {
+ DetailPredicate::<Self> {
+ p: self,
+ d: Detail::Text(text.to_string()),
+ }
+ }
+}
+
+impl Predicate<[u8]> for PdfPredicate {
+ fn eval(&self, data: &[u8]) -> bool {
+ lopdf::Document::load_mem(data).is_ok()
+ }
+
+ fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match lopdf::Document::load_mem(data) {
+ Ok(_) => None,
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for PdfPredicate {}
+
+impl fmt::Display for PdfPredicate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "is a PDF")
+ }
+}
+
+/// Extends a PdfPredicate by a check for page count, page size or creation date.
+#[derive(Debug)]
+pub struct DetailPredicate<PdfPredicate> {
+ p: PdfPredicate,
+ d: Detail,
+}
+
+#[derive(Debug)]
+enum Detail {
+ PageCount(usize),
+ PageSize(Dimensions, usize),
+ CreationDate(DateTime<Utc>),
+ Link(String),
+ Text(String),
+}
+
+/// A PDF page's dimensions from its `MediaBox`.
+///
+/// Note that `w` and `h` given in `UserUnit`, which is by default 1.0 = 1/72 inch.
+#[derive(Debug)]
+struct Dimensions {
+ w: f32,
+ h: f32,
+ unit: f32, // UserUnit, in points (1/72 of an inch)
+}
+
+impl Dimensions {
+ pub fn from_media_box(obj: &lopdf::Object, unit: Option<f32>) -> lopdf::Result<Dimensions> {
+ let a = obj.as_array()?;
+ Ok(Dimensions {
+ w: a[2].as_float()?,
+ h: a[3].as_float()?,
+ unit: unit.unwrap_or(1.0),
+ })
+ }
+
+ pub fn width_in_pt(self: &Self) -> f32 {
+ self.w * self.unit
+ }
+
+ pub fn height_in_pt(self: &Self) -> f32 {
+ self.h * self.unit
+ }
+}
+
+impl fmt::Display for Dimensions {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} pt x {} pt", self.width_in_pt(), self.height_in_pt())
+ }
+}
+
+impl cmp::PartialEq for Dimensions {
+ fn eq(&self, other: &Self) -> bool {
+ approx_eq!(
+ f32,
+ self.width_in_pt(),
+ other.width_in_pt(),
+ epsilon = 0.0001
+ ) && approx_eq!(
+ f32,
+ self.height_in_pt(),
+ other.height_in_pt(),
+ epsilon = 0.0001
+ )
+ }
+}
+
+impl cmp::Eq for Dimensions {}
+
+trait Details {
+ fn get_page_count(&self) -> usize;
+ fn get_page_size(&self, idx: usize) -> Option<Dimensions>;
+ fn get_creation_date(&self) -> Option<DateTime<Utc>>;
+ fn get_from_trailer<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object>;
+ fn get_from_page<'a>(
+ self: &'a Self,
+ idx: usize,
+ key: &[u8],
+ ) -> lopdf::Result<&'a lopdf::Object>;
+}
+
+impl DetailPredicate<PdfPredicate> {
+ fn eval_doc(&self, doc: &lopdf::Document) -> bool {
+ match &self.d {
+ Detail::PageCount(n) => doc.get_page_count() == *n,
+ Detail::PageSize(d, idx) => doc.get_page_size(*idx).map_or(false, |dim| dim == *d),
+ Detail::CreationDate(d) => doc.get_creation_date().map_or(false, |date| date == *d),
+ Detail::Link(link) => document_has_link(doc, &link),
+ Detail::Text(text) => document_has_text(doc, &text),
+ }
+ }
+
+ fn find_case_for_doc<'a>(&'a self, expected: bool, doc: &lopdf::Document) -> 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: &lopdf::Document) -> Product {
+ match &self.d {
+ Detail::PageCount(_) => Product::new(
+ "actual page count",
+ format!("{} page(s)", doc.get_page_count()),
+ ),
+ Detail::PageSize(_, idx) => Product::new(
+ "actual page size",
+ match doc.get_page_size(*idx) {
+ Some(dim) => format!("{}", dim),
+ None => "None".to_string(),
+ },
+ ),
+ Detail::CreationDate(_) => Product::new(
+ "actual creation date",
+ format!("{:?}", doc.get_creation_date()),
+ ),
+ Detail::Link(_) => Product::new(
+ "actual link contents",
+ "FIXME: who knows, but it's not what we expected".to_string(),
+ ),
+ Detail::Text(_) => {
+ Product::new("actual text contents", doc.extract_text(&[1]).unwrap())
+ }
+ }
+ }
+}
+
+// Extensions to lopdf::Object; can be removed after lopdf 0.26
+trait ObjExt {
+ /// Get the object value as a float.
+ /// Unlike as_f32() this will also cast an Integer to a Real.
+ fn as_float(&self) -> lopdf::Result<f32>;
+}
+
+impl ObjExt for lopdf::Object {
+ fn as_float(&self) -> lopdf::Result<f32> {
+ match *self {
+ lopdf::Object::Integer(ref value) => Ok(*value as f32),
+ lopdf::Object::Real(ref value) => Ok(*value),
+ _ => Err(lopdf::Error::Type),
+ }
+ }
+}
+
+impl Details for lopdf::Document {
+ fn get_page_count(self: &Self) -> usize {
+ self.get_pages().len()
+ }
+
+ fn get_page_size(self: &Self, idx: usize) -> Option<Dimensions> {
+ match self.get_from_page(idx, b"MediaBox") {
+ Ok(obj) => {
+ let unit = self
+ .get_from_page(idx, b"UserUnit")
+ .and_then(ObjExt::as_float)
+ .ok();
+ Dimensions::from_media_box(obj, unit).ok()
+ }
+ Err(_) => None,
+ }
+ }
+
+ fn get_creation_date(self: &Self) -> Option<DateTime<Utc>> {
+ match self.get_from_trailer(b"CreationDate") {
+ Ok(obj) => obj.as_datetime().map(|date| date.with_timezone(&Utc)),
+ Err(_) => None,
+ }
+ }
+
+ fn get_from_trailer<'a>(self: &'a Self, key: &[u8]) -> lopdf::Result<&'a lopdf::Object> {
+ let id = self.trailer.get(b"Info")?.as_reference()?;
+ self.get_object(id)?.as_dict()?.get(key)
+ }
+
+ fn get_from_page<'a>(
+ self: &'a Self,
+ idx: usize,
+ key: &[u8],
+ ) -> lopdf::Result<&'a lopdf::Object> {
+ let mut iter = self.page_iter();
+ for _ in 0..idx {
+ let _ = iter.next();
+ }
+ match iter.next() {
+ Some(id) => self.get_object(id)?.as_dict()?.get(key),
+ None => Err(lopdf::Error::ObjectNotFound),
+ }
+ }
+}
+
+impl Predicate<[u8]> for DetailPredicate<PdfPredicate> {
+ fn eval(&self, data: &[u8]) -> bool {
+ match lopdf::Document::load_mem(data) {
+ Ok(doc) => self.eval_doc(&doc),
+ _ => false,
+ }
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match lopdf::Document::load_mem(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<PdfPredicate> {
+ 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<PdfPredicate> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match &self.d {
+ Detail::PageCount(n) => write!(f, "is a PDF with {} page(s)", n),
+ Detail::PageSize(d, _) => write!(f, "is a PDF sized {}", d),
+ Detail::CreationDate(d) => write!(f, "is a PDF created {:?}", d),
+ Detail::Link(l) => write!(f, "is a PDF with a link to {}", l),
+ Detail::Text(t) => write!(f, "is a PDF with \"{}\" in its text content", t),
+ }
+ }
+}
+
+// This is an extremely trivial test for a string being present in the document's
+// text objects.
+fn document_has_text(document: &lopdf::Document, needle: &str) -> bool {
+ if let Ok(haystack) = text_from_first_page(document) {
+ haystack.contains(needle)
+ } else {
+ false
+ }
+}
+
+// We do a super simple test that a PDF actually contains an Annotation object
+// with a particular link. We don't test that this annotation is actually linked
+// from a page; that would be nicer.
+fn document_has_link(document: &lopdf::Document, link_text: &str) -> bool {
+ document
+ .objects
+ .iter()
+ .map(|(_obj_id, object)| object)
+ .any(|obj| object_is_annotation_with_link(obj, link_text))
+}
+
+fn object_is_annotation_with_link(object: &Object, link_text: &str) -> bool {
+ object
+ .as_dict()
+ .map(|dict| dict_is_annotation(dict) && dict_has_a_with_link(dict, link_text))
+ .unwrap_or(false)
+}
+
+fn dict_is_annotation(dict: &Dictionary) -> bool {
+ dict.get(b"Type")
+ .and_then(|type_val| type_val.as_name_str())
+ .map(|name| name == "Annot")
+ .unwrap_or(false)
+}
+
+fn dict_has_a_with_link(dict: &Dictionary, link_text: &str) -> bool {
+ dict.get(b"A")
+ .and_then(|obj| obj.as_dict())
+ .and_then(|dict| dict.get(b"URI"))
+ .and_then(|obj| obj.as_str())
+ .map(|string| string == link_text.as_bytes())
+ .unwrap_or(false)
+}
+
+fn text_from_first_page(doc: &lopdf::Document) -> lopdf::Result<String> {
+ // This is extremely simplistic; lopdf just concatenates all the text in the page
+ // into a single string.
+ doc.extract_text(&[1])
+}
diff --git a/rsvg_convert/tests/internal_predicates/png.rs b/rsvg_convert/tests/internal_predicates/png.rs
new file mode 100644
index 00000000..f629b510
--- /dev/null
+++ b/rsvg_convert/tests/internal_predicates/png.rs
@@ -0,0 +1,193 @@
+use png;
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use std::fmt;
+use std::io::BufReader;
+use std::path::{Path, PathBuf};
+
+use rsvg::surface_utils::shared_surface::{SharedImageSurface, SurfaceType};
+
+use rsvg::test_utils::compare_surfaces::BufferDiff;
+use rsvg::test_utils::reference_utils::{surface_from_png, Compare, Deviation, Reference};
+
+/// Checks that the variable of type [u8] can be parsed as a PNG file.
+#[derive(Debug)]
+pub struct PngPredicate {}
+
+impl PngPredicate {
+ pub fn with_size(self: Self, w: u32, h: u32) -> SizePredicate<Self> {
+ SizePredicate::<Self> { p: self, w, h }
+ }
+
+ pub fn with_contents<P: AsRef<Path>>(self: Self, reference: P) -> ReferencePredicate<Self> {
+ let mut path = PathBuf::new();
+ path.push(reference);
+ ReferencePredicate::<Self> { p: self, path }
+ }
+}
+
+impl Predicate<[u8]> for PngPredicate {
+ fn eval(&self, data: &[u8]) -> bool {
+ let decoder = png::Decoder::new(data);
+ decoder.read_info().is_ok()
+ }
+
+ fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ let decoder = png::Decoder::new(data);
+ match decoder.read_info() {
+ Ok(_) => None,
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for PngPredicate {}
+
+impl fmt::Display for PngPredicate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "is a PNG")
+ }
+}
+
+/// Extends a PngPredicate by a check for a given size of the PNG file.
+#[derive(Debug)]
+pub struct SizePredicate<PngPredicate> {
+ p: PngPredicate,
+ w: u32,
+ h: u32,
+}
+
+impl SizePredicate<PngPredicate> {
+ fn eval_info(&self, info: &png::Info) -> bool {
+ info.width == self.w && info.height == self.h
+ }
+
+ fn find_case_for_info<'a>(&'a self, expected: bool, info: &png::Info) -> Option<Case<'a>> {
+ if self.eval_info(info) == expected {
+ let product = self.product_for_info(info);
+ Some(Case::new(Some(self), false).add_product(product))
+ } else {
+ None
+ }
+ }
+
+ fn product_for_info(&self, info: &png::Info) -> Product {
+ let actual_size = format!("{} x {}", info.width, info.height);
+ Product::new("actual size", actual_size)
+ }
+}
+
+impl Predicate<[u8]> for SizePredicate<PngPredicate> {
+ fn eval(&self, data: &[u8]) -> bool {
+ let decoder = png::Decoder::new(data);
+ match decoder.read_info() {
+ Ok(reader) => self.eval_info(&reader.info()),
+ _ => false,
+ }
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ let decoder = png::Decoder::new(data);
+ match decoder.read_info() {
+ Ok(reader) => self.find_case_for_info(expected, reader.info()),
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for SizePredicate<PngPredicate> {
+ 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 SizePredicate<PngPredicate> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "is a PNG with size {} x {}", self.w, self.h)
+ }
+}
+
+/// Extends a PngPredicate by a comparison to the contents of a reference file
+#[derive(Debug)]
+pub struct ReferencePredicate<PngPredicate> {
+ p: PngPredicate,
+ path: PathBuf,
+}
+
+impl ReferencePredicate<PngPredicate> {
+ fn diff_acceptable(diff: &BufferDiff) -> bool {
+ match diff {
+ BufferDiff::DifferentSizes => false,
+ BufferDiff::Diff(diff) => !diff.inacceptable(),
+ }
+ }
+
+ fn diff_surface(&self, surface: &SharedImageSurface) -> Option<BufferDiff> {
+ let reference = Reference::from_png(&self.path)
+ .unwrap_or_else(|_| panic!("could not open {:?}", self.path));
+ if let Ok(diff) = reference.compare(&surface) {
+ if !Self::diff_acceptable(&diff) {
+ return Some(diff);
+ }
+ }
+ None
+ }
+
+ fn find_case_for_surface<'a>(
+ &'a self,
+ expected: bool,
+ surface: &SharedImageSurface,
+ ) -> Option<Case<'a>> {
+ let diff = self.diff_surface(&surface);
+ if diff.is_some() != expected {
+ let product = self.product_for_diff(&diff.unwrap());
+ Some(Case::new(Some(self), false).add_product(product))
+ } else {
+ None
+ }
+ }
+
+ fn product_for_diff(&self, diff: &BufferDiff) -> Product {
+ let difference = format!("{}", diff);
+ Product::new("images differ", difference)
+ }
+}
+
+impl Predicate<[u8]> for ReferencePredicate<PngPredicate> {
+ fn eval(&self, data: &[u8]) -> bool {
+ if let Ok(surface) = surface_from_png(&mut BufReader::new(data)) {
+ let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();
+ self.diff_surface(&surface).is_some()
+ } else {
+ false
+ }
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match surface_from_png(&mut BufReader::new(data)) {
+ Ok(surface) => {
+ let surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb).unwrap();
+ self.find_case_for_surface(expected, &surface)
+ }
+ Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
+ }
+ }
+}
+
+impl PredicateReflection for ReferencePredicate<PngPredicate> {
+ 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 ReferencePredicate<PngPredicate> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "is a PNG that matches the reference {}",
+ self.path.display()
+ )
+ }
+}
diff --git a/rsvg_convert/tests/internal_predicates/svg.rs b/rsvg_convert/tests/internal_predicates/svg.rs
new file mode 100644
index 00000000..70473812
--- /dev/null
+++ b/rsvg_convert/tests/internal_predicates/svg.rs
@@ -0,0 +1,179 @@
+use float_cmp::approx_eq;
+use gio::glib::Bytes;
+use gio::MemoryInputStream;
+use predicates::prelude::*;
+use predicates::reflection::{Case, Child, PredicateReflection, Product};
+use std::cmp;
+use std::fmt;
+
+use rsvg::{CairoRenderer, Length, Loader, LoadingError, SvgHandle};
+
+/// 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,
+ }),
+ }
+ }
+}
+
+fn svg_from_bytes(data: &[u8]) -> Result<SvgHandle, LoadingError> {
+ let bytes = Bytes::from(data);
+ let stream = MemoryInputStream::from_bytes(&bytes);
+ Loader::new().read_stream(&stream, None::<&gio::File>, None::<&gio::Cancellable>)
+}
+
+impl Predicate<[u8]> for SvgPredicate {
+ fn eval(&self, data: &[u8]) -> bool {
+ svg_from_bytes(data).is_ok()
+ }
+
+ fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match svg_from_bytes(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, handle: &SvgHandle) -> bool {
+ match &self.d {
+ Detail::Size(d) => {
+ let renderer = CairoRenderer::new(handle);
+ let dimensions = renderer.intrinsic_dimensions();
+ (dimensions.width, dimensions.height) == (d.w, d.h)
+ }
+ }
+ }
+
+ fn find_case_for_doc<'a>(&'a self, expected: bool, handle: &SvgHandle) -> Option<Case<'a>> {
+ if self.eval_doc(handle) == expected {
+ let product = self.product_for_doc(handle);
+ Some(Case::new(Some(self), false).add_product(product))
+ } else {
+ None
+ }
+ }
+
+ fn product_for_doc(&self, handle: &SvgHandle) -> Product {
+ match &self.d {
+ Detail::Size(_) => {
+ let renderer = CairoRenderer::new(handle);
+ let dimensions = renderer.intrinsic_dimensions();
+
+ Product::new(
+ "actual size",
+ format!(
+ "width={:?}, height={:?}",
+ dimensions.width, dimensions.height
+ ),
+ )
+ }
+ }
+ }
+}
+
+impl Predicate<[u8]> for DetailPredicate<SvgPredicate> {
+ fn eval(&self, data: &[u8]) -> bool {
+ match svg_from_bytes(data) {
+ Ok(handle) => self.eval_doc(&handle),
+ _ => false,
+ }
+ }
+
+ fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
+ match svg_from_bytes(data) {
+ Ok(handle) => self.find_case_for_doc(expected, &handle),
+ 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),
+ }
+ }
+}