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