1use crate::{Error, Stream};
2
3/// List of all SVG angle units.
4#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5#[allow(missing_docs)]
6pub enum AngleUnit {
7 Degrees,
8 Gradians,
9 Radians,
10 Turns,
11}
12
13/// Representation of the [`<angle>`] type.
14///
15/// [`<angle>`]: https://www.w3.org/TR/css-values-3/#angles
16#[derive(Clone, Copy, PartialEq, Debug)]
17#[allow(missing_docs)]
18pub struct Angle {
19 pub number: f64,
20 pub unit: AngleUnit,
21}
22
23impl Angle {
24 /// Constructs a new angle.
25 #[inline]
26 pub fn new(number: f64, unit: AngleUnit) -> Angle {
27 Angle { number, unit }
28 }
29
30 /// Converts angle to degrees.
31 #[inline]
32 pub fn to_degrees(&self) -> f64 {
33 match self.unit {
34 AngleUnit::Degrees => self.number,
35 AngleUnit::Gradians => self.number * 180.0 / 200.0,
36 AngleUnit::Radians => self.number.to_degrees(),
37 AngleUnit::Turns => self.number * 360.0,
38 }
39 }
40}
41
42impl std::str::FromStr for Angle {
43 type Err = Error;
44
45 #[inline]
46 fn from_str(text: &str) -> Result<Self, Error> {
47 let mut s: Stream<'_> = Stream::from(text);
48 let l: Angle = s.parse_angle()?;
49
50 if !s.at_end() {
51 return Err(Error::UnexpectedData(s.calc_char_pos()));
52 }
53
54 Ok(Angle::new(l.number, l.unit))
55 }
56}
57
58impl<'a> Stream<'a> {
59 /// Parses angle from the stream.
60 ///
61 /// <https://www.w3.org/TR/SVG2/types.html#InterfaceSVGAngle>
62 ///
63 /// # Notes
64 ///
65 /// - Suffix must be lowercase, otherwise it will be an error.
66 pub fn parse_angle(&mut self) -> Result<Angle, Error> {
67 self.skip_spaces();
68
69 let n = self.parse_number()?;
70
71 if self.at_end() {
72 return Ok(Angle::new(n, AngleUnit::Degrees));
73 }
74
75 let u = if self.starts_with(b"deg") {
76 self.advance(3);
77 AngleUnit::Degrees
78 } else if self.starts_with(b"grad") {
79 self.advance(4);
80 AngleUnit::Gradians
81 } else if self.starts_with(b"rad") {
82 self.advance(3);
83 AngleUnit::Radians
84 } else if self.starts_with(b"turn") {
85 self.advance(4);
86 AngleUnit::Turns
87 } else {
88 AngleUnit::Degrees
89 };
90
91 Ok(Angle::new(n, u))
92 }
93}
94
95#[rustfmt::skip]
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use std::str::FromStr;
100
101 macro_rules! test_p {
102 ($name:ident, $text:expr, $result:expr) => (
103 #[test]
104 fn $name() {
105 assert_eq!(Angle::from_str($text).unwrap(), $result);
106 }
107 )
108 }
109
110 test_p!(parse_1, "1", Angle::new(1.0, AngleUnit::Degrees));
111 test_p!(parse_2, "1deg", Angle::new(1.0, AngleUnit::Degrees));
112 test_p!(parse_3, "1grad", Angle::new(1.0, AngleUnit::Gradians));
113 test_p!(parse_4, "1rad", Angle::new(1.0, AngleUnit::Radians));
114 test_p!(parse_5, "1turn", Angle::new(1.0, AngleUnit::Turns));
115
116 #[test]
117 fn err_1() {
118 let mut s = Stream::from("1q");
119 assert_eq!(s.parse_angle().unwrap(), Angle::new(1.0, AngleUnit::Degrees));
120 assert_eq!(s.parse_angle().unwrap_err().to_string(),
121 "invalid number at position 2");
122 }
123
124 #[test]
125 fn err_2() {
126 assert_eq!(Angle::from_str("1degq").unwrap_err().to_string(),
127 "unexpected data at position 5");
128 }
129}
130