Skip to main content

css_ast/types/
bg_layer.rs

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