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