Skip to main content

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