//! The `@page` rule. use super::Location; use crate::declaration::{parse_declaration, DeclarationBlock}; use crate::error::{ParserError, PrinterError}; use crate::macros::enum_property; use crate::printer::Printer; use crate::stylesheet::ParserOptions; use crate::traits::{Parse, ToCss}; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; /// A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector) /// within a `@page` rule. /// /// Either a name or at least one pseudo class is required. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct PageSelector<'i> { /// An optional named page type. #[cfg_attr(feature = "serde", serde(borrow))] pub name: Option>, /// A list of page pseudo classes. pub pseudo_classes: Vec, } enum_property! { /// A page pseudo class within an `@page` selector. /// /// See [PageSelector](PageSelector). pub enum PagePseudoClass { /// The `:left` pseudo class. Left, /// The `:right` pseudo class. Right, /// The `:first` pseudo class. First, /// The `:last` pseudo class. Last, /// The `:blank` pseudo class. Blank, } } impl<'i> Parse<'i> for PageSelector<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let name = input.try_parse(|input| input.expect_ident().map(|x| x.into())).ok(); let mut pseudo_classes = vec![]; loop { // Whitespace is not allowed between pseudo classes let state = input.state(); match input.next_including_whitespace() { Ok(Token::Colon) => { pseudo_classes.push(PagePseudoClass::parse(input)?); } _ => { input.reset(&state); break; } } } if name.is_none() && pseudo_classes.is_empty() { return Err(input.new_custom_error(ParserError::InvalidPageSelector)); } Ok(PageSelector { name, pseudo_classes }) } } enum_property! { /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes). pub enum PageMarginBox { /// A fixed-size box defined by the intersection of the top and left margins of the page box. TopLeftCorner, /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes. TopLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the /// page top margin between the top-left and top-right page-margin boxes. TopCenter, /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes. TopRight, /// A fixed-size box defined by the intersection of the top and right margins of the page box. TopRightCorner, /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes. LeftTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the /// left page margin between the left-top and left-bottom page-margin boxes. LeftMiddle, /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes. LeftBottom, /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes. RightTop, /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right /// page margin between the right-top and right-bottom page-margin boxes. RightMiddle, /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes. RightBottom, /// A fixed-size box defined by the intersection of the bottom and left margins of the page box. BottomLeftCorner, /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes. BottomLeft, /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom /// page margin between the bottom-left and bottom-right page-margin boxes. BottomCenter, /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes. BottomRight, /// A fixed-size box defined by the intersection of the bottom and right margins of the page box. BottomRightCorner, } } /// A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct PageMarginRule<'i> { /// The margin box identifier for this rule. pub margin_box: PageMarginBox, /// The declarations within the rule. #[cfg_attr(feature = "serde", serde(borrow))] pub declarations: DeclarationBlock<'i>, /// The location of the rule in the source file. #[cfg_attr(feature = "visitor", skip_visit)] pub loc: Location, } impl<'i> ToCss for PageMarginRule<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { #[cfg(feature = "sourcemap")] dest.add_mapping(self.loc); dest.write_char('@')?; self.margin_box.to_css(dest)?; self.declarations.to_css_block(dest) } } /// A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct PageRule<'i> { /// A list of page selectors. #[cfg_attr(feature = "serde", serde(borrow))] #[cfg_attr(feature = "visitor", skip_visit)] pub selectors: Vec>, /// The declarations within the `@page` rule. pub declarations: DeclarationBlock<'i>, /// The nested margin rules. pub rules: Vec>, /// The location of the rule in the source file. #[cfg_attr(feature = "visitor", skip_visit)] pub loc: Location, } impl<'i> PageRule<'i> { pub(crate) fn parse<'t, 'o>( selectors: Vec>, input: &mut Parser<'i, 't>, loc: Location, options: &ParserOptions<'o, 'i>, ) -> Result>> { let mut declarations = DeclarationBlock::new(); let mut rules = Vec::new(); let mut rule_parser = PageRuleParser { declarations: &mut declarations, rules: &mut rules, options: &options, }; let mut parser = RuleBodyParser::new(input, &mut rule_parser); while let Some(decl) = parser.next() { if let Err((err, _)) = decl { if parser.parser.options.error_recovery { parser.parser.options.warn(err); continue; } return Err(err); } } Ok(PageRule { selectors, declarations, rules, loc, }) } } impl<'i> ToCss for PageRule<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { #[cfg(feature = "sourcemap")] dest.add_mapping(self.loc); dest.write_str("@page")?; if let Some(first) = self.selectors.first() { // Space is only required if the first selector has a name. if !dest.minify || first.name.is_some() { dest.write_char(' ')?; } let mut first = true; for selector in &self.selectors { if first { first = false; } else { dest.delim(',', false)?; } selector.to_css(dest)?; } } dest.whitespace()?; dest.write_char('{')?; dest.indent(); let mut i = 0; let len = self.declarations.len() + self.rules.len(); macro_rules! write { ($decls: expr, $important: literal) => { for decl in &$decls { dest.newline()?; decl.to_css(dest, $important)?; if i != len - 1 || !dest.minify { dest.write_char(';')?; } i += 1; } }; } write!(self.declarations.declarations, false); write!(self.declarations.important_declarations, true); if !self.rules.is_empty() { if !dest.minify && self.declarations.len() > 0 { dest.write_char('\n')?; } dest.newline()?; let mut first = true; for rule in &self.rules { if first { first = false; } else { if !dest.minify { dest.write_char('\n')?; } dest.newline()?; } rule.to_css(dest)?; } } dest.dedent(); dest.newline()?; dest.write_char('}') } } impl<'i> ToCss for PageSelector<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { if let Some(name) = &self.name { dest.write_str(&name)?; } for pseudo in &self.pseudo_classes { dest.write_char(':')?; pseudo.to_css(dest)?; } Ok(()) } } struct PageRuleParser<'a, 'o, 'i> { declarations: &'a mut DeclarationBlock<'i>, rules: &'a mut Vec>, options: &'a ParserOptions<'o, 'i>, } impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PageRuleParser<'a, 'o, 'i> { type Declaration = (); type Error = ParserError<'i>; fn parse_value<'t>( &mut self, name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, ) -> Result> { parse_declaration( name, input, &mut self.declarations.declarations, &mut self.declarations.important_declarations, &self.options, ) } } impl<'a, 'o, 'i> AtRuleParser<'i> for PageRuleParser<'a, 'o, 'i> { type Prelude = PageMarginBox; type AtRule = (); type Error = ParserError<'i>; fn parse_prelude<'t>( &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { let loc = input.current_source_location(); PageMarginBox::parse_string(&name) .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into()))) } fn parse_block<'t>( &mut self, prelude: Self::Prelude, start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { let loc = start.source_location(); let declarations = DeclarationBlock::parse(input, self.options)?; self.rules.push(PageMarginRule { margin_box: prelude, declarations, loc: Location { source_index: self.options.source_index, line: loc.line, column: loc.column, }, }); Ok(()) } } impl<'a, 'o, 'i> QualifiedRuleParser<'i> for PageRuleParser<'a, 'o, 'i> { type Prelude = (); type QualifiedRule = (); type Error = ParserError<'i>; } impl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PageRuleParser<'a, 'o, 'i> { fn parse_qualified(&self) -> bool { false } fn parse_declarations(&self) -> bool { true } }