1
//! CSS angle values.
2

            
3
use std::f64::consts::*;
4

            
5
use cssparser::{Parser, Token};
6
use float_cmp::approx_eq;
7

            
8
use crate::error::*;
9
use crate::parsers::{finite_f32, Parse};
10

            
11
70
#[derive(Debug, Copy, Clone, PartialEq)]
12
29
pub struct Angle(f64);
13

            
14
impl Angle {
15
127
    pub fn new(rad: f64) -> Angle {
16
127
        Angle(Angle::normalize(rad))
17
127
    }
18

            
19
227
    pub fn from_degrees(deg: f64) -> Angle {
20
227
        Angle(Angle::normalize(deg.to_radians()))
21
227
    }
22

            
23
386
    pub fn from_vector(vx: f64, vy: f64) -> Angle {
24
386
        let rad = vy.atan2(vx);
25

            
26
386
        if rad.is_nan() {
27
            Angle(0.0)
28
        } else {
29
386
            Angle(Angle::normalize(rad))
30
        }
31
386
    }
32

            
33
393
    pub fn radians(self) -> f64 {
34
        self.0
35
393
    }
36

            
37
132
    pub fn bisect(self, other: Angle) -> Angle {
38
132
        let half_delta = (other.0 - self.0) * 0.5;
39

            
40
132
        if FRAC_PI_2 < half_delta.abs() {
41
37
            Angle(Angle::normalize(self.0 + half_delta - PI))
42
        } else {
43
95
            Angle(Angle::normalize(self.0 + half_delta))
44
        }
45
132
    }
46

            
47
    //Flips an angle to be 180deg or PI radians rotated
48
1
    pub fn flip(self) -> Angle {
49
1
        Angle::new(self.radians() + PI)
50
1
    }
51

            
52
    // Normalizes an angle to [0.0, 2*PI)
53
872
    fn normalize(rad: f64) -> f64 {
54
872
        let res = rad % (PI * 2.0);
55
872
        if approx_eq!(f64, res, 0.0) {
56
278
            0.0
57
594
        } else if res < 0.0 {
58
170
            res + PI * 2.0
59
        } else {
60
424
            res
61
        }
62
872
    }
63
}
64

            
65
// angle:
66
// https://www.w3.org/TR/SVG/types.html#DataTypeAngle
67
//
68
// angle ::= number ("deg" | "grad" | "rad")?
69
//
70
impl Parse for Angle {
71
62
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Angle, ParseError<'i>> {
72
        let angle = {
73
62
            let loc = parser.current_source_location();
74

            
75
62
            let token = parser.next()?;
76

            
77
58
            match *token {
78
28
                Token::Number { value, .. } => {
79
28
                    let degrees = finite_f32(value).map_err(|e| loc.new_custom_error(e))?;
80
28
                    Angle::from_degrees(f64::from(degrees))
81
                }
82

            
83
                Token::Dimension {
84
26
                    value, ref unit, ..
85
                } => {
86
26
                    let value = f64::from(finite_f32(value).map_err(|e| loc.new_custom_error(e))?);
87

            
88
26
                    match unit.as_ref() {
89
26
                        "deg" => Angle::from_degrees(value),
90
13
                        "grad" => Angle::from_degrees(value * 360.0 / 400.0),
91
11
                        "rad" => Angle::new(value),
92
5
                        "turn" => Angle::from_degrees(value * 360.0),
93
                        _ => {
94
4
                            return Err(loc.new_unexpected_token_error(token.clone()));
95
                        }
96
                    }
97
                }
98

            
99
4
                _ => return Err(loc.new_unexpected_token_error(token.clone())),
100
            }
101
        };
102

            
103
50
        Ok(angle)
104
62
    }
105
}
106

            
107
#[cfg(test)]
108
mod tests {
109
    use super::*;
110

            
111
    #[test]
112
2
    fn parses_angle() {
113
1
        assert_eq!(Angle::parse_str("0").unwrap(), Angle::new(0.0));
114
1
        assert_eq!(Angle::parse_str("15").unwrap(), Angle::from_degrees(15.0));
115
1
        assert_eq!(
116
1
            Angle::parse_str("180.5deg").unwrap(),
117
1
            Angle::from_degrees(180.5)
118
        );
119
1
        assert_eq!(Angle::parse_str("1rad").unwrap(), Angle::new(1.0));
120
1
        assert_eq!(
121
1
            Angle::parse_str("-400grad").unwrap(),
122
1
            Angle::from_degrees(-360.0)
123
        );
124
1
        assert_eq!(
125
1
            Angle::parse_str("0.25turn").unwrap(),
126
1
            Angle::from_degrees(90.0)
127
        );
128

            
129
1
        assert!(Angle::parse_str("").is_err());
130
1
        assert!(Angle::parse_str("foo").is_err());
131
1
        assert!(Angle::parse_str("300foo").is_err());
132
2
    }
133

            
134
12
    fn test_bisection_angle(
135
        expected: f64,
136
        incoming_vx: f64,
137
        incoming_vy: f64,
138
        outgoing_vx: f64,
139
        outgoing_vy: f64,
140
    ) {
141
12
        let i = Angle::from_vector(incoming_vx, incoming_vy);
142
12
        let o = Angle::from_vector(outgoing_vx, outgoing_vy);
143
12
        let bisected = i.bisect(o);
144
12
        assert!(approx_eq!(f64, expected, bisected.radians()));
145
12
    }
146

            
147
    #[test]
148
2
    fn bisection_angle_is_correct_from_incoming_counterclockwise_to_outgoing() {
149
        // 1st quadrant
150
1
        test_bisection_angle(FRAC_PI_4, 1.0, 0.0, 0.0, 1.0);
151

            
152
        // 2nd quadrant
153
1
        test_bisection_angle(FRAC_PI_2 + FRAC_PI_4, 0.0, 1.0, -1.0, 0.0);
154

            
155
        // 3rd quadrant
156
1
        test_bisection_angle(PI + FRAC_PI_4, -1.0, 0.0, 0.0, -1.0);
157

            
158
        // 4th quadrant
159
1
        test_bisection_angle(PI + FRAC_PI_2 + FRAC_PI_4, 0.0, -1.0, 1.0, 0.0);
160
2
    }
161

            
162
    #[test]
163
2
    fn bisection_angle_is_correct_from_incoming_clockwise_to_outgoing() {
164
        // 1st quadrant
165
1
        test_bisection_angle(FRAC_PI_4, 0.0, 1.0, 1.0, 0.0);
166

            
167
        // 2nd quadrant
168
1
        test_bisection_angle(FRAC_PI_2 + FRAC_PI_4, -1.0, 0.0, 0.0, 1.0);
169

            
170
        // 3rd quadrant
171
1
        test_bisection_angle(PI + FRAC_PI_4, 0.0, -1.0, -1.0, 0.0);
172

            
173
        // 4th quadrant
174
1
        test_bisection_angle(PI + FRAC_PI_2 + FRAC_PI_4, 1.0, 0.0, 0.0, -1.0);
175
2
    }
176

            
177
    #[test]
178
2
    fn bisection_angle_is_correct_for_more_than_quarter_turn_angle() {
179
1
        test_bisection_angle(0.0, 0.1, -1.0, 0.1, 1.0);
180

            
181
1
        test_bisection_angle(FRAC_PI_2, 1.0, 0.1, -1.0, 0.1);
182

            
183
1
        test_bisection_angle(PI, -0.1, 1.0, -0.1, -1.0);
184

            
185
1
        test_bisection_angle(PI + FRAC_PI_2, -1.0, -0.1, 1.0, -0.1);
186
2
    }
187
}