1use super::prelude::*;
2use crate::{
3 CalcableValue, LengthPercentage, PositionFour, PositionHorizontal, PositionHorizontalKeyword, PositionOne,
4 PositionTwo, PositionVertical, PositionVerticalKeyword,
5};
6
7#[node]
20#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
22#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
23#[derive(csskit_derives::NodeWithMetadata)]
24pub enum BgPosition<'a> {
25 One(PositionOne<'a>),
27 Two(PositionTwo<'a>),
29 ThreeHorizontal(PositionHorizontal<'a>, PositionVerticalKeyword, CalcableValue<'a, LengthPercentage>),
31 ThreeVertical(PositionHorizontalKeyword, CalcableValue<'a, LengthPercentage>, PositionVertical<'a>),
33 Four(PositionFour<'a>),
35}
36
37impl<'a> Peek<'a> for BgPosition<'a> {
38 const PEEK_KINDSET: KindSet = PositionOne::PEEK_KINDSET;
39
40 #[inline(always)]
41 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
42 where
43 I: Iterator<Item = Cursor> + Clone,
44 {
45 PositionOne::peek(p, c)
46 }
47}
48
49impl<'a> Parse<'a> for BgPosition<'a> {
50 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
51 where
52 I: Iterator<Item = Cursor> + Clone,
53 {
54 let first = p.parse::<PositionOne>()?;
55 if !p.peek::<PositionOne>() {
56 return Ok(Self::One(first));
57 }
58
59 let second_c = p.peek_n(1);
60 let second = p.parse::<PositionOne>()?;
61 if !p.peek::<PositionOne>() {
62 return Ok(Self::Two(PositionTwo::from_two(p, first, second, second_c)?));
63 }
64
65 if let Some(h_kw) = first.to_horizontal_keyword() {
66 if let PositionOne::LengthPercentage(lp) = &second {
67 let third_c = p.peek_n(1);
69 let third = p.parse::<PositionOne>()?;
70 if let Some(v) = third.to_vertical() {
71 let fourth = if third.to_vertical_keyword().is_some() {
72 p.parse_if_peek::<CalcableValue<LengthPercentage>>()?
73 } else {
74 None
75 };
76 match (fourth, third.to_vertical_keyword()) {
77 (Some(fourth), Some(v_kw)) => {
79 return Ok(Self::Four(PositionFour::Physical(h_kw, lp.clone(), v_kw, fourth)));
80 }
81 (None, _) => return Ok(Self::ThreeVertical(h_kw, lp.clone(), v)),
83 _ => {}
84 }
85 }
86 Err(Diagnostic::new(third_c, Diagnostic::unexpected))?
87 } else if let Some(v_kw) = second.to_vertical_keyword() {
88 let third = p.parse::<CalcableValue<LengthPercentage>>()?;
90 return Ok(Self::ThreeHorizontal(PositionHorizontal::from_keyword(h_kw), v_kw, third));
91 }
92 Err(Diagnostic::new(second_c, Diagnostic::unexpected))?
93 } else if let PositionOne::Center(t) = first {
94 if let Some(v_kw) = second.to_vertical_keyword() {
95 let third = p.parse::<CalcableValue<LengthPercentage>>()?;
97 return Ok(Self::ThreeHorizontal(PositionHorizontal::Center(t), v_kw, third));
98 } else if let Some(v) = second.to_vertical() {
99 return Ok(Self::Two(PositionTwo::Physical(PositionHorizontal::Center(t), v)));
100 }
101 Err(Diagnostic::new(second_c, Diagnostic::unexpected))?
102 } else {
103 Ok(Self::Four(PositionFour::from_four(p, first, second, second_c)?))
106 }
107 }
108}
109
110impl<'a> PositionHorizontal<'a> {
111 pub(crate) fn from_keyword(kw: PositionHorizontalKeyword) -> Self {
112 match kw {
113 PositionHorizontalKeyword::Left(t) => Self::Left(t),
114 PositionHorizontalKeyword::Right(t) => Self::Right(t),
115 PositionHorizontalKeyword::XStart(t) => Self::XStart(t),
116 PositionHorizontalKeyword::XEnd(t) => Self::XEnd(t),
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::CssAtomSet;
125 use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
126
127 #[test]
128 fn test_writes() {
129 assert_parse!(CssAtomSet::ATOMS, BgPosition, "center");
130 assert_parse!(CssAtomSet::ATOMS, BgPosition, "left");
131 assert_parse!(CssAtomSet::ATOMS, BgPosition, "50%");
132 assert_parse!(CssAtomSet::ATOMS, BgPosition, "center center");
133 assert_parse!(CssAtomSet::ATOMS, BgPosition, "left top");
134 assert_parse!(CssAtomSet::ATOMS, BgPosition, "0 0");
135 assert_parse!(CssAtomSet::ATOMS, BgPosition, "-80px 0");
136 assert_parse!(CssAtomSet::ATOMS, BgPosition, "right 8px bottom 20px");
137 assert_parse!(CssAtomSet::ATOMS, BgPosition, "left 10px top");
138 assert_parse!(CssAtomSet::ATOMS, BgPosition, "left 10px bottom");
139 assert_parse!(CssAtomSet::ATOMS, BgPosition, "right 10px center");
140 assert_parse!(CssAtomSet::ATOMS, BgPosition, "center bottom 10px");
141 assert_parse!(CssAtomSet::ATOMS, BgPosition, "left bottom 10px");
142 }
143
144 #[test]
145 fn test_errors() {
146 assert_peek_false!(CssAtomSet::ATOMS, BgPosition, "");
147 assert_parse_error!(CssAtomSet::ATOMS, BgPosition, "left left");
148 assert_parse_error!(CssAtomSet::ATOMS, BgPosition, "top top");
149 }
150}