Skip to main content

css_ast/types/
bg_position.rs

1use super::prelude::*;
2use crate::{
3	LengthPercentage, Position, PositionHorizontal, PositionHorizontalKeyword, PositionSingleValue,
4	PositionVerticalKeyword,
5};
6
7/// <https://drafts.csswg.org/css-backgrounds-4/#typedef-bg-position>
8///
9/// ```text,ignore
10/// <bg-position> = <position> | <position-three>
11/// <position-three> = [
12///   [ left | center | right ] && [ [ top | bottom ] <length-percentage> ]
13/// |
14///   [ [ left | right ] <length-percentage> ] && [ top | center | bottom ]
15/// ]
16/// ```
17///
18/// Extends `<position>` with 3-value forms only valid in `background-position`.
19#[derive(ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
21#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
22#[derive(csskit_derives::NodeWithMetadata)]
23pub enum BgPosition {
24	/// Standard 1, 2, or 4-value `<position>`.
25	Standard(Position),
26	/// `[ left | center | right ] [ top | bottom ] <length-percentage>`
27	/// e.g. `center bottom 10px`
28	HorizontalVerticalOffset(PositionSingleValue, PositionVerticalKeyword, LengthPercentage),
29	/// `[ left | right ] <length-percentage> [ top | center | bottom ]`
30	/// e.g. `left 10px center`
31	HorizontalOffsetVertical(PositionHorizontalKeyword, LengthPercentage, PositionSingleValue),
32}
33
34impl<'a> Peek<'a> for BgPosition {
35	const PEEK_KINDSET: KindSet = PositionSingleValue::PEEK_KINDSET;
36
37	#[inline(always)]
38	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
39	where
40		I: Iterator<Item = Cursor> + Clone,
41	{
42		PositionSingleValue::peek(p, c)
43	}
44}
45
46impl<'a> Parse<'a> for BgPosition {
47	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
48	where
49		I: Iterator<Item = Cursor> + Clone,
50	{
51		let first = p.parse::<PositionSingleValue>()?;
52
53		if !p.peek::<PositionSingleValue>() {
54			return Ok(Self::Standard(Position::SingleValue(first)));
55		}
56
57		let second = p.parse::<PositionSingleValue>()?;
58		if !p.peek::<PositionSingleValue>() {
59			if let Some(h) = first.to_horizontal() {
60				if let Some(v) = second.to_vertical() {
61					return Ok(Self::Standard(Position::TwoValue(h, v)));
62				}
63			} else if let Some(h) = second.to_horizontal()
64				&& let Some(v) = first.to_vertical()
65			{
66				return Ok(Self::Standard(Position::TwoValue(h, v)));
67			}
68			Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
69		}
70
71		if let Some(h_kw) = first.to_horizontal_keyword() {
72			if let PositionSingleValue::LengthPercentage(lp) = second {
73				if let Some(v) = p.parse_if_peek::<PositionSingleValue>()? {
74					if let Some(_v_single) = v.to_vertical() {
75						if !p.peek::<LengthPercentage>() {
76							return Ok(Self::HorizontalOffsetVertical(h_kw, lp, v));
77						}
78						let fourth = p.parse::<LengthPercentage>()?;
79						if let Some(v_kw) = v.to_vertical_keyword() {
80							return Ok(Self::Standard(Position::FourValue(h_kw, lp, v_kw, fourth)));
81						}
82						Err(Diagnostic::new(v.into(), Diagnostic::unexpected))?
83					} else {
84						Err(Diagnostic::new(v.into(), Diagnostic::unexpected))?
85					}
86				} else {
87					Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
88				}
89			} else {
90				if let Some(v_kw) = second.to_vertical_keyword() {
91					let third = p.parse::<LengthPercentage>()?;
92					if !p.peek::<PositionSingleValue>() {
93						return Ok(Self::HorizontalVerticalOffset(first, v_kw, third));
94					}
95					Err(Diagnostic::new(third.into(), Diagnostic::unexpected))?
96				} else {
97					Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
98				}
99			}
100		} else if matches!(first, PositionSingleValue::Center(_)) {
101			if let Some(v_kw) = second.to_vertical_keyword() {
102				let third = p.parse::<LengthPercentage>()?;
103				return Ok(Self::HorizontalVerticalOffset(first, v_kw, third));
104			}
105			if let Some(v) = second.to_vertical() {
106				return Ok(Self::Standard(Position::TwoValue(
107					PositionHorizontal::Center(match first {
108						PositionSingleValue::Center(t) => t,
109						_ => unreachable!(),
110					}),
111					v,
112				)));
113			}
114			Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
115		} else {
116			Err(Diagnostic::new(second.into(), Diagnostic::unexpected))?
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};
126
127	#[test]
128	fn size_test() {
129		assert_eq!(std::mem::size_of::<BgPosition>(), 64);
130	}
131
132	#[test]
133	fn test_writes() {
134		assert_parse!(CssAtomSet::ATOMS, BgPosition, "center");
135		assert_parse!(CssAtomSet::ATOMS, BgPosition, "left");
136		assert_parse!(CssAtomSet::ATOMS, BgPosition, "50%");
137		assert_parse!(CssAtomSet::ATOMS, BgPosition, "center center");
138		assert_parse!(CssAtomSet::ATOMS, BgPosition, "left top");
139		assert_parse!(CssAtomSet::ATOMS, BgPosition, "0 0");
140		assert_parse!(CssAtomSet::ATOMS, BgPosition, "-80px 0");
141		assert_parse!(CssAtomSet::ATOMS, BgPosition, "right 8px bottom 20px");
142		assert_parse!(CssAtomSet::ATOMS, BgPosition, "left 10px top");
143		assert_parse!(CssAtomSet::ATOMS, BgPosition, "left 10px bottom");
144		assert_parse!(CssAtomSet::ATOMS, BgPosition, "right 10px center");
145		assert_parse!(CssAtomSet::ATOMS, BgPosition, "center bottom 10px");
146		assert_parse!(CssAtomSet::ATOMS, BgPosition, "left bottom 10px");
147	}
148
149	#[test]
150	fn test_errors() {
151		assert_parse_error!(CssAtomSet::ATOMS, BgPosition, "");
152		assert_parse_error!(CssAtomSet::ATOMS, BgPosition, "left left");
153		assert_parse_error!(CssAtomSet::ATOMS, BgPosition, "top top");
154	}
155}