lightningcss/rules/
page.rs

1//! The `@page` rule.
2
3use super::Location;
4use crate::declaration::{parse_declaration, DeclarationBlock};
5use crate::error::{ParserError, PrinterError};
6use crate::macros::enum_property;
7use crate::printer::Printer;
8use crate::stylesheet::ParserOptions;
9use crate::traits::{Parse, ToCss};
10use crate::values::string::CowArcStr;
11#[cfg(feature = "visitor")]
12use crate::visitor::Visit;
13use cssparser::*;
14
15/// A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector)
16/// within a `@page` rule.
17///
18/// Either a name or at least one pseudo class is required.
19#[derive(Debug, PartialEq, Clone)]
20#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
21#[cfg_attr(
22  feature = "serde",
23  derive(serde::Serialize, serde::Deserialize),
24  serde(rename_all = "camelCase")
25)]
26#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
27pub struct PageSelector<'i> {
28  /// An optional named page type.
29  #[cfg_attr(feature = "serde", serde(borrow))]
30  pub name: Option<CowArcStr<'i>>,
31  /// A list of page pseudo classes.
32  pub pseudo_classes: Vec<PagePseudoClass>,
33}
34
35enum_property! {
36  /// A page pseudo class within an `@page` selector.
37  ///
38  /// See [PageSelector](PageSelector).
39  pub enum PagePseudoClass {
40    /// The `:left` pseudo class.
41    Left,
42    /// The `:right` pseudo class.
43    Right,
44    /// The `:first` pseudo class.
45    First,
46    /// The `:last` pseudo class.
47    Last,
48    /// The `:blank` pseudo class.
49    Blank,
50  }
51}
52
53impl<'i> Parse<'i> for PageSelector<'i> {
54  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
55    let name = input.try_parse(|input| input.expect_ident().map(|x| x.into())).ok();
56    let mut pseudo_classes = vec![];
57
58    loop {
59      // Whitespace is not allowed between pseudo classes
60      let state = input.state();
61      match input.next_including_whitespace() {
62        Ok(Token::Colon) => {
63          pseudo_classes.push(PagePseudoClass::parse(input)?);
64        }
65        _ => {
66          input.reset(&state);
67          break;
68        }
69      }
70    }
71
72    if name.is_none() && pseudo_classes.is_empty() {
73      return Err(input.new_custom_error(ParserError::InvalidPageSelector));
74    }
75
76    Ok(PageSelector { name, pseudo_classes })
77  }
78}
79
80enum_property! {
81  /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).
82  pub enum PageMarginBox {
83    /// A fixed-size box defined by the intersection of the top and left margins of the page box.
84    TopLeftCorner,
85    /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes.
86    TopLeft,
87    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the
88    /// page top margin between the top-left and top-right page-margin boxes.
89    TopCenter,
90    /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes.
91    TopRight,
92    /// A fixed-size box defined by the intersection of the top and right margins of the page box.
93    TopRightCorner,
94    /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes.
95    LeftTop,
96    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the
97    /// left page margin between the left-top and left-bottom page-margin boxes.
98    LeftMiddle,
99    /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes.
100    LeftBottom,
101    /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes.
102    RightTop,
103    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right
104    /// page margin between the right-top and right-bottom page-margin boxes.
105    RightMiddle,
106    /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes.
107    RightBottom,
108    /// A fixed-size box defined by the intersection of the bottom and left margins of the page box.
109    BottomLeftCorner,
110    /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes.
111    BottomLeft,
112    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom
113    /// page margin between the bottom-left and bottom-right page-margin boxes.
114    BottomCenter,
115    /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes.
116    BottomRight,
117    /// A fixed-size box defined by the intersection of the bottom and right margins of the page box.
118    BottomRightCorner,
119  }
120}
121
122/// A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule.
123#[derive(Debug, PartialEq, Clone)]
124#[cfg_attr(feature = "visitor", derive(Visit))]
125#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
126#[cfg_attr(
127  feature = "serde",
128  derive(serde::Serialize, serde::Deserialize),
129  serde(rename_all = "camelCase")
130)]
131#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
132pub struct PageMarginRule<'i> {
133  /// The margin box identifier for this rule.
134  pub margin_box: PageMarginBox,
135  /// The declarations within the rule.
136  #[cfg_attr(feature = "serde", serde(borrow))]
137  pub declarations: DeclarationBlock<'i>,
138  /// The location of the rule in the source file.
139  #[cfg_attr(feature = "visitor", skip_visit)]
140  pub loc: Location,
141}
142
143impl<'i> ToCss for PageMarginRule<'i> {
144  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
145  where
146    W: std::fmt::Write,
147  {
148    #[cfg(feature = "sourcemap")]
149    dest.add_mapping(self.loc);
150    dest.write_char('@')?;
151    self.margin_box.to_css(dest)?;
152    self.declarations.to_css_block(dest)
153  }
154}
155
156/// A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.
157#[derive(Debug, PartialEq, Clone)]
158#[cfg_attr(feature = "visitor", derive(Visit))]
159#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
162pub struct PageRule<'i> {
163  /// A list of page selectors.
164  #[cfg_attr(feature = "serde", serde(borrow))]
165  #[cfg_attr(feature = "visitor", skip_visit)]
166  pub selectors: Vec<PageSelector<'i>>,
167  /// The declarations within the `@page` rule.
168  pub declarations: DeclarationBlock<'i>,
169  /// The nested margin rules.
170  pub rules: Vec<PageMarginRule<'i>>,
171  /// The location of the rule in the source file.
172  #[cfg_attr(feature = "visitor", skip_visit)]
173  pub loc: Location,
174}
175
176impl<'i> PageRule<'i> {
177  pub(crate) fn parse<'t, 'o>(
178    selectors: Vec<PageSelector<'i>>,
179    input: &mut Parser<'i, 't>,
180    loc: Location,
181    options: &ParserOptions<'o, 'i>,
182  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
183    let mut declarations = DeclarationBlock::new();
184    let mut rules = Vec::new();
185    let mut rule_parser = PageRuleParser {
186      declarations: &mut declarations,
187      rules: &mut rules,
188      options: &options,
189    };
190    let mut parser = RuleBodyParser::new(input, &mut rule_parser);
191
192    while let Some(decl) = parser.next() {
193      if let Err((err, _)) = decl {
194        if parser.parser.options.error_recovery {
195          parser.parser.options.warn(err);
196          continue;
197        }
198        return Err(err);
199      }
200    }
201
202    Ok(PageRule {
203      selectors,
204      declarations,
205      rules,
206      loc,
207    })
208  }
209}
210
211impl<'i> ToCss for PageRule<'i> {
212  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
213  where
214    W: std::fmt::Write,
215  {
216    #[cfg(feature = "sourcemap")]
217    dest.add_mapping(self.loc);
218    dest.write_str("@page")?;
219    if let Some(first) = self.selectors.first() {
220      // Space is only required if the first selector has a name.
221      if !dest.minify || first.name.is_some() {
222        dest.write_char(' ')?;
223      }
224      let mut first = true;
225      for selector in &self.selectors {
226        if first {
227          first = false;
228        } else {
229          dest.delim(',', false)?;
230        }
231        selector.to_css(dest)?;
232      }
233    }
234
235    dest.whitespace()?;
236    dest.write_char('{')?;
237    dest.indent();
238
239    let mut i = 0;
240    let len = self.declarations.len() + self.rules.len();
241
242    macro_rules! write {
243      ($decls: expr, $important: literal) => {
244        for decl in &$decls {
245          dest.newline()?;
246          decl.to_css(dest, $important)?;
247          if i != len - 1 || !dest.minify {
248            dest.write_char(';')?;
249          }
250          i += 1;
251        }
252      };
253    }
254
255    write!(self.declarations.declarations, false);
256    write!(self.declarations.important_declarations, true);
257
258    if !self.rules.is_empty() {
259      if !dest.minify && self.declarations.len() > 0 {
260        dest.write_char('\n')?;
261      }
262      dest.newline()?;
263
264      let mut first = true;
265      for rule in &self.rules {
266        if first {
267          first = false;
268        } else {
269          if !dest.minify {
270            dest.write_char('\n')?;
271          }
272          dest.newline()?;
273        }
274        rule.to_css(dest)?;
275      }
276    }
277
278    dest.dedent();
279    dest.newline()?;
280    dest.write_char('}')
281  }
282}
283
284impl<'i> ToCss for PageSelector<'i> {
285  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
286  where
287    W: std::fmt::Write,
288  {
289    if let Some(name) = &self.name {
290      dest.write_str(&name)?;
291    }
292
293    for pseudo in &self.pseudo_classes {
294      dest.write_char(':')?;
295      pseudo.to_css(dest)?;
296    }
297
298    Ok(())
299  }
300}
301
302struct PageRuleParser<'a, 'o, 'i> {
303  declarations: &'a mut DeclarationBlock<'i>,
304  rules: &'a mut Vec<PageMarginRule<'i>>,
305  options: &'a ParserOptions<'o, 'i>,
306}
307
308impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PageRuleParser<'a, 'o, 'i> {
309  type Declaration = ();
310  type Error = ParserError<'i>;
311
312  fn parse_value<'t>(
313    &mut self,
314    name: CowRcStr<'i>,
315    input: &mut cssparser::Parser<'i, 't>,
316  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
317    parse_declaration(
318      name,
319      input,
320      &mut self.declarations.declarations,
321      &mut self.declarations.important_declarations,
322      &self.options,
323    )
324  }
325}
326
327impl<'a, 'o, 'i> AtRuleParser<'i> for PageRuleParser<'a, 'o, 'i> {
328  type Prelude = PageMarginBox;
329  type AtRule = ();
330  type Error = ParserError<'i>;
331
332  fn parse_prelude<'t>(
333    &mut self,
334    name: CowRcStr<'i>,
335    input: &mut Parser<'i, 't>,
336  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
337    let loc = input.current_source_location();
338    PageMarginBox::parse_string(&name)
339      .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))
340  }
341
342  fn parse_block<'t>(
343    &mut self,
344    prelude: Self::Prelude,
345    start: &ParserState,
346    input: &mut Parser<'i, 't>,
347  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
348    let loc = start.source_location();
349    let declarations = DeclarationBlock::parse(input, self.options)?;
350    self.rules.push(PageMarginRule {
351      margin_box: prelude,
352      declarations,
353      loc: Location {
354        source_index: self.options.source_index,
355        line: loc.line,
356        column: loc.column,
357      },
358    });
359    Ok(())
360  }
361}
362
363impl<'a, 'o, 'i> QualifiedRuleParser<'i> for PageRuleParser<'a, 'o, 'i> {
364  type Prelude = ();
365  type QualifiedRule = ();
366  type Error = ParserError<'i>;
367}
368
369impl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PageRuleParser<'a, 'o, 'i> {
370  fn parse_qualified(&self) -> bool {
371    false
372  }
373
374  fn parse_declarations(&self) -> bool {
375    true
376  }
377}