css_ast/rules/
page.rs

1use crate::{
2	StyleValue,
3	specificity::{Specificity, ToSpecificity},
4};
5use bumpalo::collections::Vec;
6use css_parse::{
7	AtRule, Block, Build, Cursor, DeclarationList, Kind, KindSet, NoPreludeAllowed, Parse, Parser, Peek,
8	Result as ParserResult, T, atkeyword_set, keyword_set, syntax::CommaSeparated,
9};
10use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
11
12atkeyword_set!(pub struct AtPageKeyword "page");
13
14// https://drafts.csswg.org/cssom-1/#csspagerule
15// https://drafts.csswg.org/css-page-3/#at-page-rule
16#[derive(Peek, Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.page"))]
19#[visit]
20pub struct PageRule<'a>(pub AtRule<AtPageKeyword, Option<PageSelectorList<'a>>, PageRuleBlock<'a>>);
21
22#[derive(Peek, Parse, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
24pub struct PageSelectorList<'a>(pub CommaSeparated<'a, PageSelector<'a>>);
25
26#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
28#[visit(self)]
29pub struct PageSelector<'a> {
30	pub page_type: Option<T![Ident]>,
31	pub pseudos: Vec<'a, PagePseudoClass>,
32}
33
34impl<'a> Peek<'a> for PageSelector<'a> {
35	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident, Kind::Colon]);
36
37	fn peek(_: &Parser<'a>, c: Cursor) -> bool {
38		c == Self::PEEK_KINDSET
39	}
40}
41
42impl<'a> Parse<'a> for PageSelector<'a> {
43	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
44		let mut pseudos = Vec::new_in(p.bump());
45		let page_type = p.parse_if_peek::<T![Ident]>()?;
46		loop {
47			if p.peek::<T![:]>() {
48				pseudos.push(p.parse::<PagePseudoClass>()?);
49			} else {
50				return Ok(Self { page_type, pseudos });
51			}
52		}
53	}
54}
55
56impl<'a> ToSpecificity for PageSelector<'a> {
57	fn specificity(&self) -> Specificity {
58		let specificity = self.pseudos.iter().map(ToSpecificity::specificity).sum();
59		if self.page_type.is_some() { specificity + Specificity(1, 0, 0) } else { specificity }
60	}
61}
62
63#[derive(Peek, ToCursors, ToSpan, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "kebab-case"))]
65pub enum PagePseudoClass {
66	Left(T![:], T![Ident]),
67	Right(T![:], T![Ident]),
68	First(T![:], T![Ident]),
69	Blank(T![:], T![Ident]),
70}
71
72keyword_set!(pub enum PagePseudoClassKeyword { Left: "left", Right: "right", First: "first", Blank: "blank" });
73
74impl<'a> Parse<'a> for PagePseudoClass {
75	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
76		let colon = p.parse::<T![:]>()?;
77		let skip = p.set_skip(KindSet::NONE);
78		let keyword = p.parse::<PagePseudoClassKeyword>();
79		p.set_skip(skip);
80		let keyword = keyword?;
81		let c: Cursor = keyword.into();
82		let ident = <T![Ident]>::build(p, c);
83		match keyword {
84			PagePseudoClassKeyword::Left(_) => Ok(Self::Left(colon, ident)),
85			PagePseudoClassKeyword::Right(_) => Ok(Self::Right(colon, ident)),
86			PagePseudoClassKeyword::First(_) => Ok(Self::First(colon, ident)),
87			PagePseudoClassKeyword::Blank(_) => Ok(Self::Blank(colon, ident)),
88		}
89	}
90}
91
92impl ToSpecificity for PagePseudoClass {
93	fn specificity(&self) -> Specificity {
94		match self {
95			Self::Blank(_, _) => Specificity(0, 1, 0),
96			Self::First(_, _) => Specificity(0, 1, 0),
97			Self::Left(_, _) => Specificity(0, 0, 1),
98			Self::Right(_, _) => Specificity(0, 0, 1),
99		}
100	}
101}
102
103#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
105#[visit(children)]
106pub struct PageRuleBlock<'a>(Block<'a, StyleValue<'a>, MarginRule<'a>>);
107
108atkeyword_set!(
109	pub enum AtMarginRuleKeywords {
110		TopLeftCorner: "top-left-corner",
111		TopLeft: "top-left",
112		TopCenter: "top-center",
113		TopRight: "top-right",
114		TopRightCorner: "top-right-corner",
115		RightTop: "right-top",
116		RightMiddle: "right-middle",
117		RightBottom: "right-bottom",
118		BottomRightCorner: "bottom-right-corner",
119		BottomRight: "bottom-right",
120		BottomCenter: "bottom-center",
121		BottomLeft: "bottom-left",
122		BottomLeftCorner: "bottom-left-corner",
123		LeftBottom: "left-bottom",
124		LeftMiddle: "left-middle",
125		LeftTop: "left-top"
126	}
127);
128
129// https://drafts.csswg.org/cssom-1/#cssmarginrule
130#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
132#[visit]
133pub struct MarginRule<'a>(pub AtRule<AtMarginRuleKeywords, NoPreludeAllowed, MarginRuleBlock<'a>>);
134
135#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
137#[visit(children)]
138pub struct MarginRuleBlock<'a>(DeclarationList<'a, StyleValue<'a>>);
139
140#[cfg(test)]
141mod tests {
142	use super::*;
143	use css_parse::assert_parse;
144
145	#[test]
146	fn size_test() {
147		assert_eq!(std::mem::size_of::<PageRule>(), 160);
148		assert_eq!(std::mem::size_of::<PageSelectorList>(), 32);
149		assert_eq!(std::mem::size_of::<PageSelector>(), 48);
150		assert_eq!(std::mem::size_of::<PagePseudoClass>(), 28);
151		assert_eq!(std::mem::size_of::<PageRuleBlock>(), 96);
152		assert_eq!(std::mem::size_of::<MarginRule>(), 96);
153		assert_eq!(std::mem::size_of::<MarginRuleBlock>(), 64);
154	}
155
156	#[test]
157	fn test_writes() {
158		assert_parse!(PageRule, "@page{margin-top:4in;}");
159		assert_parse!(PageRule, "@page wide{}");
160		assert_parse!(PageRule, "@page wide:left{}");
161		assert_parse!(MarginRule, "@top-right{}");
162		assert_parse!(PageRule, "@page wide:left{@top-right{}}");
163	}
164}