1 | use 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)] |
8 | pub 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)] |
27 | pub 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 | |
41 | impl 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 | |
94 | impl 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)] |
107 | mod 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 | |