css_ast/rules/
page.rs

1use super::prelude::*;
2use crate::specificity::{Specificity, ToSpecificity};
3use css_parse::RuleVariants;
4
5// https://drafts.csswg.org/cssom-1/#csspagerule
6// https://drafts.csswg.org/css-page-3/#at-page-rule
7#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
9#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
10#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.page"))]
11#[derive(csskit_derives::NodeWithMetadata)]
12#[metadata(node_kinds = AtRule, used_at_rules = Page)]
13pub struct PageRule<'a> {
14	#[cfg_attr(feature = "visitable", visit(skip))]
15	#[atom(CssAtomSet::Page)]
16	pub name: T![AtKeyword],
17	pub prelude: Option<PageSelectorList<'a>>,
18	#[metadata(delegate)]
19	pub block: PageRuleBlock<'a>,
20}
21
22#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
24#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
25pub struct PageSelectorList<'a>(pub CommaSeparated<'a, PageSelector<'a>>);
26
27#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
29#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
30#[derive(csskit_derives::NodeWithMetadata)]
31pub struct PageSelector<'a> {
32	pub page_type: Option<T![Ident]>,
33	pub pseudos: Vec<'a, PagePseudoClass>,
34}
35
36impl<'a> Peek<'a> for PageSelector<'a> {
37	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident, Kind::Colon]);
38
39	fn peek<I>(_: &Parser<'a, I>, c: Cursor) -> bool
40	where
41		I: Iterator<Item = Cursor> + Clone,
42	{
43		c == Self::PEEK_KINDSET
44	}
45}
46
47impl<'a> Parse<'a> for PageSelector<'a> {
48	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
49	where
50		I: Iterator<Item = Cursor> + Clone,
51	{
52		let mut pseudos = Vec::new_in(p.bump());
53		let page_type = p.parse_if_peek::<T![Ident]>()?;
54		loop {
55			if p.peek::<T![:]>() {
56				pseudos.push(p.parse::<PagePseudoClass>()?);
57			} else {
58				return Ok(Self { page_type, pseudos });
59			}
60		}
61	}
62}
63
64impl<'a> ToSpecificity for PageSelector<'a> {
65	fn specificity(&self) -> Specificity {
66		let specificity = self.pseudos.iter().map(ToSpecificity::specificity).sum();
67		if self.page_type.is_some() { specificity + Specificity(1, 0, 0) } else { specificity }
68	}
69}
70
71#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
73pub enum PagePseudoClass {
74	Left(T![:], T![Ident]),
75	Right(T![:], T![Ident]),
76	First(T![:], T![Ident]),
77	Blank(T![:], T![Ident]),
78}
79
80impl ToSpecificity for PagePseudoClass {
81	fn specificity(&self) -> Specificity {
82		match self {
83			Self::Blank(_, _) => Specificity(0, 1, 0),
84			Self::First(_, _) => Specificity(0, 1, 0),
85			Self::Left(_, _) => Specificity(0, 0, 1),
86			Self::Right(_, _) => Specificity(0, 0, 1),
87		}
88	}
89}
90
91#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
93#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
94#[derive(csskit_derives::NodeWithMetadata)]
95pub struct PageRuleBlock<'a>(#[metadata(delegate)] Block<'a, StyleValue<'a>, MarginRule<'a>, CssMetadata>);
96
97// https://drafts.csswg.org/cssom-1/#cssmarginrule
98#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
100#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
101pub enum MarginRule<'a> {
102	#[atom(CssAtomSet::TopLeftCorner)]
103	TopLeftCorner(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
104	#[atom(CssAtomSet::TopLeft)]
105	TopLeft(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
106	#[atom(CssAtomSet::TopCenter)]
107	TopCenter(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
108	#[atom(CssAtomSet::TopRight)]
109	TopRight(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
110	#[atom(CssAtomSet::TopRightCorner)]
111	TopRightCorner(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
112	#[atom(CssAtomSet::RightTop)]
113	RightTop(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
114	#[atom(CssAtomSet::RightMiddle)]
115	RightMiddle(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
116	#[atom(CssAtomSet::RightBottom)]
117	RightBottom(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
118	#[atom(CssAtomSet::BottomRightCorner)]
119	BottomRightCorner(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
120	#[atom(CssAtomSet::BottomRight)]
121	BottomRight(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
122	#[atom(CssAtomSet::BottomCenter)]
123	BottomCenter(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
124	#[atom(CssAtomSet::BottomLeft)]
125	BottomLeft(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
126	#[atom(CssAtomSet::BottomLeftCorner)]
127	BottomLeftCorner(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
128	#[atom(CssAtomSet::LeftBottom)]
129	LeftBottom(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
130	#[atom(CssAtomSet::LeftMiddle)]
131	LeftMiddle(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
132	#[atom(CssAtomSet::LeftTop)]
133	LeftTop(#[cfg_attr(feature = "visitable", visit(skip))] T![AtKeyword], MarginRuleBlock<'a>),
134}
135
136impl<'a> NodeWithMetadata<CssMetadata> for MarginRule<'a> {
137	fn metadata(&self) -> CssMetadata {
138		self.block().0.metadata()
139	}
140}
141
142impl<'a> MarginRule<'a> {
143	pub fn name(&self) -> &T![AtKeyword] {
144		match self {
145			Self::TopLeftCorner(a, _) => a,
146			Self::TopLeft(a, _) => a,
147			Self::TopCenter(a, _) => a,
148			Self::TopRight(a, _) => a,
149			Self::TopRightCorner(a, _) => a,
150			Self::RightTop(a, _) => a,
151			Self::RightMiddle(a, _) => a,
152			Self::RightBottom(a, _) => a,
153			Self::BottomRightCorner(a, _) => a,
154			Self::BottomRight(a, _) => a,
155			Self::BottomCenter(a, _) => a,
156			Self::BottomLeft(a, _) => a,
157			Self::BottomLeftCorner(a, _) => a,
158			Self::LeftBottom(a, _) => a,
159			Self::LeftMiddle(a, _) => a,
160			Self::LeftTop(a, _) => a,
161		}
162	}
163
164	pub fn block(&self) -> &MarginRuleBlock<'a> {
165		match self {
166			Self::TopLeftCorner(_, b) => b,
167			Self::TopLeft(_, b) => b,
168			Self::TopCenter(_, b) => b,
169			Self::TopRight(_, b) => b,
170			Self::TopRightCorner(_, b) => b,
171			Self::RightTop(_, b) => b,
172			Self::RightMiddle(_, b) => b,
173			Self::RightBottom(_, b) => b,
174			Self::BottomRightCorner(_, b) => b,
175			Self::BottomRight(_, b) => b,
176			Self::BottomCenter(_, b) => b,
177			Self::BottomLeft(_, b) => b,
178			Self::BottomLeftCorner(_, b) => b,
179			Self::LeftBottom(_, b) => b,
180			Self::LeftMiddle(_, b) => b,
181			Self::LeftTop(_, b) => b,
182		}
183	}
184}
185
186impl<'a> RuleVariants<'a> for MarginRule<'a> {
187	type DeclarationValue = StyleValue<'a>;
188	type Metadata = CssMetadata;
189}
190
191#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
192#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
193#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
194pub struct MarginRuleBlock<'a>(DeclarationList<'a, StyleValue<'a>, CssMetadata>);
195
196#[cfg(test)]
197mod tests {
198	use super::*;
199	use crate::CssAtomSet;
200	use css_parse::assert_parse;
201
202	#[test]
203	fn size_test() {
204		assert_eq!(std::mem::size_of::<PageRule>(), 176);
205		assert_eq!(std::mem::size_of::<PageSelectorList>(), 32);
206		assert_eq!(std::mem::size_of::<PageSelector>(), 48);
207		assert_eq!(std::mem::size_of::<PagePseudoClass>(), 28);
208		assert_eq!(std::mem::size_of::<PageRuleBlock>(), 128);
209		assert_eq!(std::mem::size_of::<MarginRule>(), 112);
210		assert_eq!(std::mem::size_of::<MarginRuleBlock>(), 96);
211	}
212
213	#[test]
214	fn test_writes() {
215		assert_parse!(CssAtomSet::ATOMS, PageRule, "@page{margin-top:4in;}");
216		assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide{}");
217		assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide:left{}");
218		assert_parse!(CssAtomSet::ATOMS, MarginRule, "@top-right{}");
219		assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide:left{@top-right{}}");
220	}
221}