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