1use crate::{Error, Stream};
2
3/// List of all SVG length units.
4#[derive(Clone, Copy, PartialEq, Eq, Debug)]
5#[allow(missing_docs)]
6pub enum LengthUnit {
7 None,
8 Em,
9 Ex,
10 Px,
11 In,
12 Cm,
13 Mm,
14 Pt,
15 Pc,
16 Percent,
17}
18
19/// Representation of the [`<length>`] type.
20///
21/// [`<length>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLength
22#[derive(Clone, Copy, PartialEq, Debug)]
23#[allow(missing_docs)]
24pub struct Length {
25 pub number: f64,
26 pub unit: LengthUnit,
27}
28
29impl Length {
30 /// Constructs a new length.
31 #[inline]
32 pub fn new(number: f64, unit: LengthUnit) -> Length {
33 Length { number, unit }
34 }
35
36 /// Constructs a new length with `LengthUnit::None`.
37 #[inline]
38 pub fn new_number(number: f64) -> Length {
39 Length {
40 number,
41 unit: LengthUnit::None,
42 }
43 }
44
45 /// Constructs a new length with a zero number.
46 ///
47 /// Shorthand for: `Length::new(0.0, Unit::None)`.
48 #[inline]
49 pub fn zero() -> Length {
50 Length {
51 number: 0.0,
52 unit: LengthUnit::None,
53 }
54 }
55}
56
57impl Default for Length {
58 #[inline]
59 fn default() -> Self {
60 Length::zero()
61 }
62}
63
64impl std::str::FromStr for Length {
65 type Err = Error;
66
67 #[inline]
68 fn from_str(text: &str) -> Result<Self, Error> {
69 let mut s: Stream<'_> = Stream::from(text);
70 let l: Length = s.parse_length()?;
71
72 if !s.at_end() {
73 return Err(Error::UnexpectedData(s.calc_char_pos()));
74 }
75
76 Ok(Length::new(l.number, l.unit))
77 }
78}
79
80impl<'a> Stream<'a> {
81 /// Parses length from the stream.
82 ///
83 /// <https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLength>
84 ///
85 /// # Notes
86 ///
87 /// - Suffix must be lowercase, otherwise it will be an error.
88 pub fn parse_length(&mut self) -> Result<Length, Error> {
89 self.skip_spaces();
90
91 let n = self.parse_number()?;
92
93 if self.at_end() {
94 return Ok(Length::new(n, LengthUnit::None));
95 }
96
97 let u = if self.starts_with(b"%") {
98 LengthUnit::Percent
99 } else if self.starts_with(b"em") {
100 LengthUnit::Em
101 } else if self.starts_with(b"ex") {
102 LengthUnit::Ex
103 } else if self.starts_with(b"px") {
104 LengthUnit::Px
105 } else if self.starts_with(b"in") {
106 LengthUnit::In
107 } else if self.starts_with(b"cm") {
108 LengthUnit::Cm
109 } else if self.starts_with(b"mm") {
110 LengthUnit::Mm
111 } else if self.starts_with(b"pt") {
112 LengthUnit::Pt
113 } else if self.starts_with(b"pc") {
114 LengthUnit::Pc
115 } else {
116 LengthUnit::None
117 };
118
119 match u {
120 LengthUnit::Percent => self.advance(1),
121 LengthUnit::None => {}
122 _ => self.advance(2),
123 }
124
125 Ok(Length::new(n, u))
126 }
127
128 /// Parses length from a list of lengths.
129 pub fn parse_list_length(&mut self) -> Result<Length, Error> {
130 if self.at_end() {
131 return Err(Error::UnexpectedEndOfStream);
132 }
133
134 let l = self.parse_length()?;
135 self.skip_spaces();
136 self.parse_list_separator();
137 Ok(l)
138 }
139}
140
141/// A pull-based [`<list-of-length>`] parser.
142///
143/// # Examples
144///
145/// ```
146/// use svgtypes::{Length, LengthUnit, LengthListParser};
147///
148/// let mut p = LengthListParser::from("10px 20% 50mm");
149/// assert_eq!(p.next().unwrap().unwrap(), Length::new(10.0, LengthUnit::Px));
150/// assert_eq!(p.next().unwrap().unwrap(), Length::new(20.0, LengthUnit::Percent));
151/// assert_eq!(p.next().unwrap().unwrap(), Length::new(50.0, LengthUnit::Mm));
152/// assert_eq!(p.next().is_none(), true);
153/// ```
154///
155/// [`<list-of-length>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLengthList
156#[derive(Clone, Copy, PartialEq, Eq, Debug)]
157pub struct LengthListParser<'a>(Stream<'a>);
158
159impl<'a> From<&'a str> for LengthListParser<'a> {
160 #[inline]
161 fn from(v: &'a str) -> Self {
162 LengthListParser(Stream::from(v))
163 }
164}
165
166impl<'a> Iterator for LengthListParser<'a> {
167 type Item = Result<Length, Error>;
168
169 fn next(&mut self) -> Option<Self::Item> {
170 if self.0.at_end() {
171 None
172 } else {
173 let v: Result = self.0.parse_list_length();
174 if v.is_err() {
175 self.0.jump_to_end();
176 }
177
178 Some(v)
179 }
180 }
181}
182
183#[rustfmt::skip]
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use std::str::FromStr;
188
189 macro_rules! test_p {
190 ($name:ident, $text:expr, $result:expr) => (
191 #[test]
192 fn $name() {
193 assert_eq!(Length::from_str($text).unwrap(), $result);
194 }
195 )
196 }
197
198 test_p!(parse_1, "1", Length::new(1.0, LengthUnit::None));
199 test_p!(parse_2, "1em", Length::new(1.0, LengthUnit::Em));
200 test_p!(parse_3, "1ex", Length::new(1.0, LengthUnit::Ex));
201 test_p!(parse_4, "1px", Length::new(1.0, LengthUnit::Px));
202 test_p!(parse_5, "1in", Length::new(1.0, LengthUnit::In));
203 test_p!(parse_6, "1cm", Length::new(1.0, LengthUnit::Cm));
204 test_p!(parse_7, "1mm", Length::new(1.0, LengthUnit::Mm));
205 test_p!(parse_8, "1pt", Length::new(1.0, LengthUnit::Pt));
206 test_p!(parse_9, "1pc", Length::new(1.0, LengthUnit::Pc));
207 test_p!(parse_10, "1%", Length::new(1.0, LengthUnit::Percent));
208 test_p!(parse_11, "1e0", Length::new(1.0, LengthUnit::None));
209 test_p!(parse_12, "1.0e0", Length::new(1.0, LengthUnit::None));
210 test_p!(parse_13, "1.0e0em", Length::new(1.0, LengthUnit::Em));
211
212 #[test]
213 fn parse_14() {
214 let mut s = Stream::from("1,");
215 assert_eq!(s.parse_length().unwrap(), Length::new(1.0, LengthUnit::None));
216 }
217
218 #[test]
219 fn parse_15() {
220 let mut s = Stream::from("1 ,");
221 assert_eq!(s.parse_length().unwrap(), Length::new(1.0, LengthUnit::None));
222 }
223
224 #[test]
225 fn parse_16() {
226 let mut s = Stream::from("1 1");
227 assert_eq!(s.parse_length().unwrap(), Length::new(1.0, LengthUnit::None));
228 }
229
230 #[test]
231 fn err_1() {
232 let mut s = Stream::from("1q");
233 assert_eq!(s.parse_length().unwrap(), Length::new(1.0, LengthUnit::None));
234 assert_eq!(s.parse_length().unwrap_err().to_string(),
235 "invalid number at position 2");
236 }
237
238 #[test]
239 fn err_2() {
240 assert_eq!(Length::from_str("1mmx").unwrap_err().to_string(),
241 "unexpected data at position 4");
242 }
243}
244