1use crate::stream::Stream;
2
3/// [`paint-order`] property variants.
4///
5/// [`paint-order`]: https://www.w3.org/TR/SVG2/painting.html#PaintOrder
6#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7#[allow(missing_docs)]
8pub enum PaintOrderKind {
9 Fill,
10 Stroke,
11 Markers,
12}
13
14/// Representation of the [`paint-order`] property.
15///
16/// [`paint-order`]: https://www.w3.org/TR/SVG2/painting.html#PaintOrder
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18pub struct PaintOrder {
19 /// The order.
20 ///
21 /// Guarantee to not have duplicates.
22 pub order: [PaintOrderKind; 3],
23}
24
25impl Default for PaintOrder {
26 #[inline]
27 fn default() -> Self {
28 Self {
29 order: [
30 PaintOrderKind::Fill,
31 PaintOrderKind::Stroke,
32 PaintOrderKind::Markers,
33 ],
34 }
35 }
36}
37
38impl From<[PaintOrderKind; 3]> for PaintOrder {
39 #[inline]
40 fn from(order: [PaintOrderKind; 3]) -> Self {
41 Self { order }
42 }
43}
44
45impl std::str::FromStr for PaintOrder {
46 type Err = ();
47
48 /// Parses `PaintOrder` from a string.
49 ///
50 /// Never returns an error and fallbacks to the default value instead.
51 fn from_str(text: &str) -> Result<Self, Self::Err> {
52 let mut order = Vec::new();
53
54 let mut left = vec![
55 PaintOrderKind::Fill,
56 PaintOrderKind::Stroke,
57 PaintOrderKind::Markers,
58 ];
59
60 let mut s = Stream::from(text);
61 while !s.at_end() && order.len() < 3 {
62 s.skip_spaces();
63 let name = s.consume_ascii_ident();
64 s.skip_spaces();
65 let name = match name {
66 // `normal` is the special value that should short-circuit.
67 "normal" => return Ok(PaintOrder::default()),
68 "fill" => PaintOrderKind::Fill,
69 "stroke" => PaintOrderKind::Stroke,
70 "markers" => PaintOrderKind::Markers,
71 _ => return Ok(PaintOrder::default()),
72 };
73
74 if let Some(index) = left.iter().position(|v| *v == name) {
75 left.remove(index);
76 }
77
78 order.push(name);
79 }
80
81 s.skip_spaces();
82 if !s.at_end() {
83 // Any trailing data is an error.
84 return Ok(PaintOrder::default());
85 }
86
87 if order.is_empty() {
88 return Ok(PaintOrder::default());
89 }
90
91 // Any missing values should be added in the original order.
92 while order.len() < 3 && !left.is_empty() {
93 order.push(left.remove(0));
94 }
95
96 // Any duplicates is an error.
97 if order[0] == order[1] || order[0] == order[2] || order[1] == order[2] {
98 // Any trailing data is an error.
99 return Ok(PaintOrder::default());
100 }
101
102 Ok(PaintOrder {
103 order: [order[0], order[1], order[2]],
104 })
105 }
106}
107
108#[rustfmt::skip]
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use std::str::FromStr;
113
114 #[test]
115 fn parse_1() {
116 assert_eq!(PaintOrder::from_str("normal").unwrap(), PaintOrder::default());
117 }
118
119 #[test]
120 fn parse_2() {
121 assert_eq!(PaintOrder::from_str("qwe").unwrap(), PaintOrder::default());
122 }
123
124 #[test]
125 fn parse_3() {
126 assert_eq!(PaintOrder::from_str("").unwrap(), PaintOrder::default());
127 }
128
129 #[test]
130 fn parse_4() {
131 assert_eq!(PaintOrder::from_str("stroke qwe").unwrap(), PaintOrder::default());
132 }
133
134 #[test]
135 fn parse_5() {
136 assert_eq!(PaintOrder::from_str("stroke stroke").unwrap(), PaintOrder::default());
137 }
138
139 #[test]
140 fn parse_6() {
141 assert_eq!(PaintOrder::from_str("stroke").unwrap(), PaintOrder::from([
142 PaintOrderKind::Stroke, PaintOrderKind::Fill, PaintOrderKind::Markers
143 ]));
144 }
145
146 #[test]
147 fn parse_7() {
148 assert_eq!(PaintOrder::from_str("stroke markers").unwrap(), PaintOrder::from([
149 PaintOrderKind::Stroke, PaintOrderKind::Markers, PaintOrderKind::Fill
150 ]));
151 }
152
153 #[test]
154 fn parse_8() {
155 assert_eq!(PaintOrder::from_str("stroke markers fill").unwrap(), PaintOrder::from([
156 PaintOrderKind::Stroke, PaintOrderKind::Markers, PaintOrderKind::Fill
157 ]));
158 }
159
160 #[test]
161 fn parse_9() {
162 assert_eq!(PaintOrder::from_str("markers").unwrap(), PaintOrder::from([
163 PaintOrderKind::Markers, PaintOrderKind::Fill, PaintOrderKind::Stroke
164 ]));
165 }
166
167 #[test]
168 fn parse_10() {
169 assert_eq!(PaintOrder::from_str(" stroke\n").unwrap(), PaintOrder::from([
170 PaintOrderKind::Stroke, PaintOrderKind::Fill, PaintOrderKind::Markers
171 ]));
172 }
173
174 #[test]
175 fn parse_11() {
176 assert_eq!(PaintOrder::from_str("stroke stroke stroke stroke").unwrap(), PaintOrder::default());
177 }
178}
179