1use crate::{Error, Stream};
2
3/// Representation of the `align` value of the [`preserveAspectRatio`] attribute.
4///
5/// [`preserveAspectRatio`]: https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
6#[allow(missing_docs)]
7#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8pub enum Align {
9 None,
10 XMinYMin,
11 XMidYMin,
12 XMaxYMin,
13 XMinYMid,
14 XMidYMid,
15 XMaxYMid,
16 XMinYMax,
17 XMidYMax,
18 XMaxYMax,
19}
20
21/// Representation of the [`preserveAspectRatio`] attribute.
22///
23/// SVG 2 removed the `defer` keyword, but we still support it.
24///
25/// [`preserveAspectRatio`]: https://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
26#[derive(Clone, Copy, PartialEq, Eq, Debug)]
27pub struct AspectRatio {
28 /// `<defer>` value.
29 ///
30 /// Set to `true` when `defer` value is present.
31 pub defer: bool,
32 /// `<align>` value.
33 pub align: Align,
34 /// `<meetOrSlice>` value.
35 ///
36 /// - Set to `true` when `slice` value is present.
37 /// - Set to `false` when `meet` value is present or value is not set at all.
38 pub slice: bool,
39}
40
41impl std::str::FromStr for AspectRatio {
42 type Err = Error;
43
44 fn from_str(text: &str) -> Result<Self, Error> {
45 let mut s = Stream::from(text);
46
47 s.skip_spaces();
48
49 let defer = s.starts_with(b"defer");
50 if defer {
51 s.advance(5);
52 s.consume_byte(b' ')?;
53 s.skip_spaces();
54 }
55
56 let start = s.pos();
57 let align = s.consume_ascii_ident();
58 let align = match align {
59 "none" => Align::None,
60 "xMinYMin" => Align::XMinYMin,
61 "xMidYMin" => Align::XMidYMin,
62 "xMaxYMin" => Align::XMaxYMin,
63 "xMinYMid" => Align::XMinYMid,
64 "xMidYMid" => Align::XMidYMid,
65 "xMaxYMid" => Align::XMaxYMid,
66 "xMinYMax" => Align::XMinYMax,
67 "xMidYMax" => Align::XMidYMax,
68 "xMaxYMax" => Align::XMaxYMax,
69 _ => return Err(Error::UnexpectedData(s.calc_char_pos_at(start))),
70 };
71
72 s.skip_spaces();
73
74 let mut slice = false;
75 if !s.at_end() {
76 let start = s.pos();
77 let v = s.consume_ascii_ident();
78 match v {
79 "meet" => {}
80 "slice" => slice = true,
81 "" => {}
82 _ => return Err(Error::UnexpectedData(s.calc_char_pos_at(start))),
83 };
84 }
85
86 Ok(AspectRatio {
87 defer,
88 align,
89 slice,
90 })
91 }
92}
93
94impl Default for AspectRatio {
95 #[inline]
96 fn default() -> Self {
97 AspectRatio {
98 defer: false,
99 align: Align::XMidYMid,
100 slice: false,
101 }
102 }
103}
104
105#[rustfmt::skip]
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use std::str::FromStr;
110
111 macro_rules! test {
112 ($name:ident, $text:expr, $result:expr) => (
113 #[test]
114 fn $name() {
115 let v = AspectRatio::from_str($text).unwrap();
116 assert_eq!(v, $result);
117 }
118 )
119 }
120
121 test!(parse_1, "none", AspectRatio {
122 defer: false,
123 align: Align::None,
124 slice: false,
125 });
126
127 test!(parse_2, "defer none", AspectRatio {
128 defer: true,
129 align: Align::None,
130 slice: false,
131 });
132
133 test!(parse_3, "xMinYMid", AspectRatio {
134 defer: false,
135 align: Align::XMinYMid,
136 slice: false,
137 });
138
139 test!(parse_4, "xMinYMid slice", AspectRatio {
140 defer: false,
141 align: Align::XMinYMid,
142 slice: true,
143 });
144
145 test!(parse_5, "xMinYMid meet", AspectRatio {
146 defer: false,
147 align: Align::XMinYMid,
148 slice: false,
149 });
150}
151