Skip to content

Commit 8fc39e3

Browse files
committed
Basic support for @page rules
1 parent 4d80479 commit 8fc39e3

File tree

5 files changed

+128
-23
lines changed

5 files changed

+128
-23
lines changed

src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,4 +1847,16 @@ mod tests {
18471847
minify_test("@font-face {src: url(\"test.woff\") format(woff supports palettes);}", "@font-face{src:url(test.woff)format(woff supports palettes)}");
18481848
minify_test("@font-face {src: url(\"test.woff\") format(woff supports features(opentype) color(sbix));}", "@font-face{src:url(test.woff)format(woff supports features(opentype) color(sbix))}");
18491849
}
1850+
1851+
#[test]
1852+
fn test_page_rule() {
1853+
minify_test("@page {margin: 0.5cm}", "@page{margin:.5cm}");
1854+
minify_test("@page :left {margin: 0.5cm}", "@page:left{margin:.5cm}");
1855+
minify_test("@page :right {margin: 0.5cm}", "@page:right{margin:.5cm}");
1856+
minify_test("@page LandscapeTable {margin: 0.5cm}", "@page LandscapeTable{margin:.5cm}");
1857+
minify_test("@page CompanyLetterHead:first {margin: 0.5cm}", "@page CompanyLetterHead:first{margin:.5cm}");
1858+
minify_test("@page:first {margin: 0.5cm}", "@page:first{margin:.5cm}");
1859+
minify_test("@page :blank:first {margin: 0.5cm}", "@page:blank:first{margin:.5cm}");
1860+
minify_test("@page toc, index {margin: 0.5cm}", "@page toc,index{margin:.5cm}");
1861+
}
18501862
}

src/parser.rs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::fmt::Write;
99
use crate::selector::{Selectors, SelectorParser};
1010
use crate::rules::keyframes::{KeyframeListParser, KeyframesRule};
1111
use crate::rules::font_face::{FontFaceRule, FontFaceDeclarationParser};
12+
use crate::rules::page::{PageSelector, PageRule};
1213
use crate::declaration::{Declaration, DeclarationHandler};
1314

1415
#[derive(Eq, PartialEq, Clone)]
@@ -97,7 +98,7 @@ pub enum AtRulePrelude {
9798
/// A @keyframes rule, with its animation name and vendor prefix if exists.
9899
Keyframes(String),//(KeyframesName, Option<VendorPrefix>),
99100
/// A @page rule prelude.
100-
Page,
101+
Page(Vec<PageSelector>),
101102
/// A @document rule, with its conditional.
102103
Document,//(DocumentCondition),
103104
/// A @import rule prelude.
@@ -381,7 +382,8 @@ pub enum CssRule {
381382
Import(ImportRule),
382383
Style(StyleRule),
383384
Keyframes(KeyframesRule),
384-
FontFace(FontFaceRule)
385+
FontFace(FontFaceRule),
386+
Page(PageRule)
385387
}
386388

387389
impl ToCss for CssRule {
@@ -392,6 +394,7 @@ impl ToCss for CssRule {
392394
CssRule::Style(style) => style.to_css(dest),
393395
CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
394396
CssRule::FontFace(font_face) => font_face.to_css(dest),
397+
CssRule::Page(font_face) => font_face.to_css(dest),
395398
}
396399
}
397400
}
@@ -491,13 +494,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
491494

492495
Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name.into())))
493496
},
494-
// "page" => {
495-
// if cfg!(feature = "gecko") {
496-
// Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Page))
497-
// } else {
498-
// Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
499-
// }
500-
// },
497+
"page" => {
498+
let selectors = input.try_parse(|input| input.parse_comma_separated(PageSelector::parse)).unwrap_or_default();
499+
Ok(AtRuleType::WithBlock(AtRulePrelude::Page(selectors)))
500+
},
501501
// "-moz-document" => {
502502
// if !cfg!(feature = "gecko") {
503503
// return Err(input.new_custom_error(
@@ -616,19 +616,16 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser {
616616
// source_location: start.source_location(),
617617
}))
618618
},
619-
// AtRuleBlockPrelude::Page => {
620-
// let context = ParserContext::new_with_rule_type(
621-
// self.context,
622-
// CssRuleType::Page,
623-
// self.namespaces,
624-
// );
625-
626-
// let declarations = parse_property_declaration_list(&context, input, None);
627-
// Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
628-
// block: Arc::new(self.shared_lock.wrap(declarations)),
629-
// source_location: start.source_location(),
630-
// }))))
631-
// },
619+
AtRulePrelude::Page(selectors) => {
620+
let parser = DeclarationListParser::new(input, PropertyDeclarationParser);
621+
let declarations: Vec<_> = parser.flatten().collect();
622+
Ok(CssRule::Page(PageRule {
623+
selectors,
624+
declarations: DeclarationBlock {
625+
declarations
626+
}
627+
}))
628+
},
632629
// AtRuleBlockPrelude::Document(condition) => {
633630
// if !cfg!(feature = "gecko") {
634631
// unreachable!()

src/properties/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mod custom;
1+
pub mod custom;
22
pub mod margin_padding;
33
pub mod background;
44
pub mod outline;

src/rules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod keyframes;
22
pub mod font_face;
3+
pub mod page;

src/rules/page.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use cssparser::*;
2+
use crate::values::percentage::Percentage;
3+
use crate::traits::{Parse, ToCss};
4+
use crate::parser::{PropertyDeclarationParser, DeclarationBlock};
5+
use crate::printer::Printer;
6+
use crate::macros::enum_property;
7+
use std::fmt::Write;
8+
9+
/// https://www.w3.org/TR/css-page-3/#typedef-page-selector
10+
#[derive(Debug, PartialEq)]
11+
pub struct PageSelector {
12+
name: Option<String>,
13+
pseudo_classes: Vec<PagePseudoClass>
14+
}
15+
16+
enum_property!(PagePseudoClass,
17+
Left,
18+
Right,
19+
First,
20+
Last,
21+
Blank
22+
);
23+
24+
impl Parse for PageSelector {
25+
fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ()>> {
26+
let name = input.try_parse(|input| input.expect_ident_cloned()).ok().map(|s| s.as_ref().to_owned());
27+
let mut pseudo_classes = vec![];
28+
29+
loop {
30+
// Whitespace is not allowed between pseudo classes
31+
let state = input.state();
32+
match input.next_including_whitespace() {
33+
Ok(Token::Colon) => {
34+
pseudo_classes.push(PagePseudoClass::parse(input)?);
35+
}
36+
_ => {
37+
input.reset(&state);
38+
break
39+
}
40+
}
41+
}
42+
43+
if name.is_none() && pseudo_classes.is_empty() {
44+
return Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
45+
}
46+
47+
Ok(PageSelector {
48+
name,
49+
pseudo_classes
50+
})
51+
}
52+
}
53+
54+
#[derive(Debug, PartialEq)]
55+
pub struct PageRule {
56+
pub selectors: Vec<PageSelector>,
57+
pub declarations: DeclarationBlock
58+
}
59+
60+
impl ToCss for PageRule {
61+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
62+
dest.write_str("@page")?;
63+
if let Some(first) = self.selectors.first() {
64+
// Space is only required if the first selector has a name.
65+
if !dest.minify || first.name.is_some() {
66+
dest.write_char(' ')?;
67+
}
68+
let mut first = true;
69+
for selector in &self.selectors {
70+
if first {
71+
first = false;
72+
} else {
73+
dest.delim(',', false)?;
74+
}
75+
selector.to_css(dest)?;
76+
}
77+
}
78+
self.declarations.to_css(dest)
79+
}
80+
}
81+
82+
impl ToCss for PageSelector {
83+
fn to_css<W>(&self, dest: &mut Printer<W>) -> std::fmt::Result where W: std::fmt::Write {
84+
if let Some(name) = &self.name {
85+
dest.write_str(&name)?;
86+
}
87+
88+
for pseudo in &self.pseudo_classes {
89+
dest.write_char(':')?;
90+
pseudo.to_css(dest)?;
91+
}
92+
93+
Ok(())
94+
}
95+
}

0 commit comments

Comments
 (0)