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#[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#[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}