svgtypes/
length.rs

1// Copyright 2018 the SVG Types Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::{Error, Stream};
5
6/// List of all SVG length units.
7#[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/// Representation of the [`<length>`] type.
23///
24/// [`<length>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLength
25#[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    /// Constructs a new length.
34    #[inline]
35    pub fn new(number: f64, unit: LengthUnit) -> Length {
36        Length { number, unit }
37    }
38
39    /// Constructs a new length with `LengthUnit::None`.
40    #[inline]
41    pub fn new_number(number: f64) -> Length {
42        Length {
43            number,
44            unit: LengthUnit::None,
45        }
46    }
47
48    /// Constructs a new length with a zero number.
49    ///
50    /// Shorthand for: `Length::new(0.0, Unit::None)`.
51    #[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    /// Parses length from the stream.
85    ///
86    /// <https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLength>
87    ///
88    /// # Notes
89    ///
90    /// - Suffix must be lowercase, otherwise it will be an error.
91    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    /// Parses length from a list of lengths.
132    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/// A pull-based [`<list-of-length>`] parser.
145///
146/// # Examples
147///
148/// ```
149/// use svgtypes::{Length, LengthUnit, LengthListParser};
150///
151/// let mut p = LengthListParser::from("10px 20% 50mm");
152/// assert_eq!(p.next().unwrap().unwrap(), Length::new(10.0, LengthUnit::Px));
153/// assert_eq!(p.next().unwrap().unwrap(), Length::new(20.0, LengthUnit::Percent));
154/// assert_eq!(p.next().unwrap().unwrap(), Length::new(50.0, LengthUnit::Mm));
155/// assert_eq!(p.next().is_none(), true);
156/// ```
157///
158/// [`<list-of-length>`]: https://www.w3.org/TR/SVG2/types.html#InterfaceSVGLengthList
159#[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}