1use super::prelude::*;
2use crate::specificity::{Specificity, ToSpecificity};
3use css_parse::RuleVariants;
4
5#[node]
9#[derive(Peek, Parse, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
11#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
12#[cfg_attr(feature = "css_feature_data", derive(::csskit_derives::ToCSSFeature), css_feature("css.at-rules.page"))]
13#[derive(csskit_derives::NodeWithMetadata)]
14#[metadata(node_kinds = AtRule, used_at_rules = Page)]
15pub struct PageRule<'a> {
16 #[cfg_attr(feature = "visitable", visit(skip))]
17 #[atom(CssAtomSet::Page)]
18 pub name: T![AtKeyword],
19 #[metadata(prelude)]
20 pub prelude: Option<PageSelectorList<'a>>,
21 #[metadata(block)]
22 pub block: PageRuleBlock<'a>,
23}
24
25#[node]
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())]
29#[derive(csskit_derives::NodeWithMetadata)]
30pub struct PageSelectorList<'a>(pub CommaSeparated<'a, PageSelector<'a>>);
31
32#[node]
33#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
35#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
36#[derive(csskit_derives::NodeWithMetadata)]
37pub struct PageSelector<'a> {
38 pub page_type: Option<T![Ident]>,
39 pub pseudos: Vec<'a, PagePseudoClass>,
40}
41
42impl<'a> Peek<'a> for PageSelector<'a> {
43 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Ident, Kind::Colon]);
44}
45
46impl<'a> Parse<'a> for PageSelector<'a> {
47 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
48 where
49 I: Iterator<Item = Cursor> + Clone,
50 {
51 let mut pseudos = Vec::new_in(p.alloc());
52 let page_type = p.parse_if_peek::<T![Ident]>()?;
53 loop {
54 if p.peek::<T![:]>() {
55 pseudos.push(p.parse::<PagePseudoClass>()?);
56 } else {
57 return Ok(Self { page_type, pseudos });
58 }
59 }
60 }
61}
62
63impl<'a> ToSpecificity for PageSelector<'a> {
64 fn specificity(&self) -> Specificity {
65 let specificity = self.pseudos.iter().map(ToSpecificity::specificity).sum();
66 if self.page_type.is_some() { specificity + Specificity(1, 0, 0) } else { specificity }
67 }
68}
69
70#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
72#[derive(csskit_derives::NodeWithMetadata)]
73pub enum PagePseudoClass {
74 Left(#[semantic_eq(skip)] T![:], T![Ident]),
75 Right(#[semantic_eq(skip)] T![:], T![Ident]),
76 First(#[semantic_eq(skip)] T![:], T![Ident]),
77 Blank(#[semantic_eq(skip)] 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#[node]
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>(Block<'a, StyleValue<'a>, MarginRule<'a>, CssMetadata>);
97
98#[node]
100#[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
188impl<'a> RuleVariants<'a> for MarginRule<'a> {
189 type DeclarationValue = StyleValue<'a>;
190 type Metadata = CssMetadata;
191}
192
193#[node]
194#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
196#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
197pub struct MarginRuleBlock<'a>(DeclarationList<'a, StyleValue<'a>, CssMetadata>);
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::CssAtomSet;
203 use css_parse::assert_parse;
204
205 #[test]
206 fn test_writes() {
207 assert_parse!(CssAtomSet::ATOMS, PageRule, "@page{margin-top:4in;}");
208 assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide{}");
209 assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide:left{}");
210 assert_parse!(CssAtomSet::ATOMS, MarginRule, "@top-right{}");
211 assert_parse!(CssAtomSet::ATOMS, PageRule, "@page wide:left{@top-right{}}");
212 }
213}