1
use float_cmp::approx_eq;
2
use gio::glib::Bytes;
3
use gio::MemoryInputStream;
4
use predicates::prelude::*;
5
use predicates::reflection::{Case, Child, PredicateReflection, Product};
6
use std::cmp;
7
use std::fmt;
8

            
9
use rsvg::{CairoRenderer, Length, Loader, LoadingError, SvgHandle};
10

            
11
/// Checks that the variable of type [u8] can be parsed as a SVG file.
12
#[derive(Debug)]
13
pub struct SvgPredicate {}
14

            
15
impl SvgPredicate {
16
3
    pub fn with_size(self, width: Length, height: Length) -> DetailPredicate<Self> {
17
3
        DetailPredicate::<Self> {
18
            p: self,
19
3
            d: Detail::Size(Dimensions {
20
                w: width,
21
                h: height,
22
            }),
23
        }
24
3
    }
25
}
26

            
27
4
fn svg_from_bytes(data: &[u8]) -> Result<SvgHandle, LoadingError> {
28
4
    let bytes = Bytes::from(data);
29
4
    let stream = MemoryInputStream::from_bytes(&bytes);
30
4
    Loader::new().read_stream(&stream, None::<&gio::File>, None::<&gio::Cancellable>)
31
4
}
32

            
33
impl Predicate<[u8]> for SvgPredicate {
34
    fn eval(&self, data: &[u8]) -> bool {
35
        svg_from_bytes(data).is_ok()
36
    }
37

            
38
1
    fn find_case<'a>(&'a self, _expected: bool, data: &[u8]) -> Option<Case<'a>> {
39
1
        match svg_from_bytes(data) {
40
1
            Ok(_) => None,
41
            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
42
        }
43
1
    }
44
}
45

            
46
impl PredicateReflection for SvgPredicate {}
47

            
48
impl fmt::Display for SvgPredicate {
49
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50
        write!(f, "is an SVG")
51
    }
52
}
53

            
54
/// Extends a SVG Predicate by a check for its size
55
#[derive(Debug)]
56
pub struct DetailPredicate<SvgPredicate> {
57
    p: SvgPredicate,
58
    d: Detail,
59
}
60

            
61
#[derive(Debug)]
62
enum Detail {
63
    Size(Dimensions),
64
}
65

            
66
/// SVG's dimensions
67
#[derive(Debug)]
68
struct Dimensions {
69
    w: Length,
70
    h: Length,
71
}
72

            
73
impl Dimensions {
74
    pub fn width(&self) -> f64 {
75
        self.w.length
76
    }
77

            
78
    pub fn height(&self) -> f64 {
79
        self.h.length
80
    }
81
}
82

            
83
impl fmt::Display for Dimensions {
84
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85
        write!(
86
            f,
87
            "{}{} x {}{}",
88
            self.width(),
89
            self.w.unit,
90
            self.height(),
91
            self.h.unit
92
        )
93
    }
94
}
95

            
96
impl cmp::PartialEq for Dimensions {
97
    fn eq(&self, other: &Self) -> bool {
98
        approx_eq!(f64, self.width(), other.width(), epsilon = 0.000_001)
99
            && approx_eq!(f64, self.height(), other.height(), epsilon = 0.000_001)
100
            && (self.w.unit == self.h.unit)
101
            && (self.h.unit == other.h.unit)
102
            && (other.h.unit == other.w.unit)
103
    }
104
}
105

            
106
impl cmp::Eq for Dimensions {}
107

            
108
impl DetailPredicate<SvgPredicate> {
109
3
    fn eval_doc(&self, handle: &SvgHandle) -> bool {
110
        match &self.d {
111
3
            Detail::Size(d) => {
112
3
                let renderer = CairoRenderer::new(handle);
113
3
                let dimensions = renderer.intrinsic_dimensions();
114
3
                (dimensions.width, dimensions.height) == (d.w, d.h)
115
3
            }
116
        }
117
3
    }
118

            
119
3
    fn find_case_for_doc<'a>(&'a self, expected: bool, handle: &SvgHandle) -> Option<Case<'a>> {
120
3
        if self.eval_doc(handle) == expected {
121
            let product = self.product_for_doc(handle);
122
            Some(Case::new(Some(self), false).add_product(product))
123
        } else {
124
3
            None
125
        }
126
3
    }
127

            
128
    fn product_for_doc(&self, handle: &SvgHandle) -> Product {
129
        match &self.d {
130
            Detail::Size(_) => {
131
                let renderer = CairoRenderer::new(handle);
132
                let dimensions = renderer.intrinsic_dimensions();
133

            
134
                Product::new(
135
                    "actual size",
136
                    format!(
137
                        "width={:?}, height={:?}",
138
                        dimensions.width, dimensions.height
139
                    ),
140
                )
141
            }
142
        }
143
    }
144
}
145

            
146
impl Predicate<[u8]> for DetailPredicate<SvgPredicate> {
147
    fn eval(&self, data: &[u8]) -> bool {
148
        match svg_from_bytes(data) {
149
            Ok(handle) => self.eval_doc(&handle),
150
            _ => false,
151
        }
152
    }
153

            
154
3
    fn find_case<'a>(&'a self, expected: bool, data: &[u8]) -> Option<Case<'a>> {
155
3
        match svg_from_bytes(data) {
156
3
            Ok(handle) => self.find_case_for_doc(expected, &handle),
157
            Err(e) => Some(Case::new(Some(self), false).add_product(Product::new("Error", e))),
158
        }
159
3
    }
160
}
161

            
162
impl PredicateReflection for DetailPredicate<SvgPredicate> {
163
    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = Child<'a>> + 'a> {
164
        let params = vec![Child::new("predicate", &self.p)];
165
        Box::new(params.into_iter())
166
    }
167
}
168

            
169
impl fmt::Display for DetailPredicate<SvgPredicate> {
170
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171
        match &self.d {
172
            Detail::Size(d) => write!(f, "is an SVG sized {}", d),
173
        }
174
    }
175
}