Skip to main content

css_ast/types/
bg_layer.rs

1use super::prelude::*;
2use crate::{Attachment, BgClip, BgImage, BgPositionAndSize, Color, RepeatStyle, VisualBox};
3
4/// Represents `<bg-layer>` and `<final-bg-layer>` from css-backgrounds-3.
5///
6/// ```text,ignore
7/// <bg-layer> =
8///   <bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style>
9///   || <attachment> || <visual-box> || <bg-clip>
10///
11/// <final-bg-layer> = <bg-layer> || <color>?
12/// ```
13///
14/// The `color` field is `None` for non-final layers.
15///
16/// <https://drafts.csswg.org/css-backgrounds-3/#typedef-bg-layer>
17#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
19#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
20#[derive(csskit_derives::NodeWithMetadata)]
21pub struct BgLayer<'a> {
22	pub image: Option<BgImage<'a>>,
23	pub position: Option<BgPositionAndSize>,
24	pub repeat: Option<RepeatStyle>,
25	pub attachment: Option<Attachment>,
26	pub origin: Option<VisualBox>,
27	pub clip: Option<BgClip>,
28	pub color: Option<Color<'a>>,
29}
30
31impl<'a> Peek<'a> for BgLayer<'a> {
32	const PEEK_KINDSET: KindSet = BgImage::PEEK_KINDSET
33		.combine(BgPositionAndSize::PEEK_KINDSET)
34		.combine(RepeatStyle::PEEK_KINDSET)
35		.combine(Attachment::PEEK_KINDSET)
36		.combine(VisualBox::PEEK_KINDSET)
37		.combine(BgClip::PEEK_KINDSET)
38		.combine(Color::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		BgImage::peek(p, c)
46			|| BgPositionAndSize::peek(p, c)
47			|| RepeatStyle::peek(p, c)
48			|| Attachment::peek(p, c)
49			|| VisualBox::peek(p, c)
50			|| BgClip::peek(p, c)
51			|| Color::peek(p, c)
52	}
53}
54
55impl<'a> Parse<'a> for BgLayer<'a> {
56	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
57	where
58		I: Iterator<Item = Cursor> + Clone,
59	{
60		let mut image: Option<BgImage<'a>> = None;
61		let mut position: Option<BgPositionAndSize> = None;
62		let mut repeat: Option<RepeatStyle> = None;
63		let mut attachment: Option<Attachment> = None;
64		let mut origin: Option<VisualBox> = None;
65		let mut clip: Option<BgClip> = None;
66		let mut color: Option<Color<'a>> = None;
67
68		let mut any = false;
69		loop {
70			if image.is_none()
71				&& let Some(v) = p.parse_if_peek::<BgImage<'a>>()?
72			{
73				image = Some(v);
74				any = true;
75				continue;
76			}
77			if position.is_none()
78				&& let Some(v) = p.parse_if_peek::<BgPositionAndSize>()?
79			{
80				position = Some(v);
81				any = true;
82				continue;
83			}
84			if repeat.is_none()
85				&& let Some(v) = p.parse_if_peek::<RepeatStyle>()?
86			{
87				repeat = Some(v);
88				any = true;
89				continue;
90			}
91			if attachment.is_none()
92				&& let Some(v) = p.parse_if_peek::<Attachment>()?
93			{
94				attachment = Some(v);
95				any = true;
96				continue;
97			}
98			// <visual-box> (origin) before <bg-clip> (superset).
99			if origin.is_none()
100				&& clip.is_none()
101				&& let Some(v) = p.parse_if_peek::<VisualBox>()?
102			{
103				origin = Some(v);
104				any = true;
105				continue;
106			}
107			// <bg-clip> — border-area or text (not covered by VisualBox)
108			if clip.is_none()
109				&& let Some(v) = p.parse_if_peek::<BgClip>()?
110			{
111				clip = Some(v);
112				any = true;
113				continue;
114			}
115			// <color> — only valid in final-bg-layer but we parse permissively.
116			if color.is_none()
117				&& let Some(v) = p.parse_if_peek::<Color<'a>>()?
118			{
119				color = Some(v);
120				any = true;
121				continue;
122			}
123			break;
124		}
125
126		if !any {
127			Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
128		}
129
130		Ok(Self { image, position, repeat, attachment, origin, clip, color })
131	}
132}
133
134#[cfg(test)]
135mod tests {
136	use super::*;
137	use crate::CssAtomSet;
138	use css_parse::{assert_parse, assert_parse_error};
139
140	#[test]
141	fn size_test() {
142		assert_eq!(std::mem::size_of::<BgLayer>(), 256);
143	}
144
145	#[test]
146	fn test_writes() {
147		assert_parse!(CssAtomSet::ATOMS, BgLayer, "none");
148		assert_parse!(CssAtomSet::ATOMS, BgLayer, "url(foo.png)");
149		assert_parse!(CssAtomSet::ATOMS, BgLayer, "red");
150		assert_parse!(CssAtomSet::ATOMS, BgLayer, "#fff");
151		assert_parse!(CssAtomSet::ATOMS, BgLayer, "center");
152		assert_parse!(CssAtomSet::ATOMS, BgLayer, "0 0");
153		assert_parse!(CssAtomSet::ATOMS, BgLayer, "center / cover");
154		assert_parse!(CssAtomSet::ATOMS, BgLayer, "repeat-x");
155		assert_parse!(CssAtomSet::ATOMS, BgLayer, "no-repeat");
156		assert_parse!(CssAtomSet::ATOMS, BgLayer, "fixed");
157		assert_parse!(CssAtomSet::ATOMS, BgLayer, "url(bg.png) center no-repeat");
158		assert_parse!(CssAtomSet::ATOMS, BgLayer, "url(bg.png) 0 0 / cover no-repeat fixed");
159		assert_parse!(CssAtomSet::ATOMS, BgLayer, "url(bg.png) 0 0 / cover no-repeat fixed red");
160	}
161
162	#[test]
163	fn test_errors() {
164		assert_parse_error!(CssAtomSet::ATOMS, BgLayer, "");
165	}
166}