Skip to content

Commit 80a982a

Browse files
committed
Parse page margin at rules
Fixes parcel-bundler#179
1 parent 7e867ee commit 80a982a

File tree

4 files changed

+314
-8
lines changed

4 files changed

+314
-8
lines changed

src/declaration.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ impl<'i> DeclarationBlock<'i> {
103103
important_declarations: vec![],
104104
}
105105
}
106+
107+
/// Returns the total number of declarations in the block.
108+
pub fn len(&self) -> usize {
109+
self.declarations.len() + self.important_declarations.len()
110+
}
106111
}
107112

108113
impl<'i> ToCss for DeclarationBlock<'i> {
@@ -143,7 +148,7 @@ impl<'i> DeclarationBlock<'i> {
143148
dest.indent();
144149

145150
let mut i = 0;
146-
let len = self.declarations.len() + self.important_declarations.len();
151+
let len = self.len();
147152

148153
macro_rules! write {
149154
($decls: expr, $important: literal) => {

src/lib.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10048,6 +10048,93 @@ mod tests {
1004810048
minify_test("@page:first {margin: 0.5cm}", "@page:first{margin:.5cm}");
1004910049
minify_test("@page :blank:first {margin: 0.5cm}", "@page:blank:first{margin:.5cm}");
1005010050
minify_test("@page toc, index {margin: 0.5cm}", "@page toc,index{margin:.5cm}");
10051+
minify_test(
10052+
r#"
10053+
@page :right {
10054+
@bottom-left {
10055+
margin: 10pt;
10056+
}
10057+
}
10058+
"#,
10059+
"@page:right{@bottom-left{margin:10pt}}",
10060+
);
10061+
minify_test(
10062+
r#"
10063+
@page :right {
10064+
margin: 1in;
10065+
10066+
@bottom-left {
10067+
margin: 10pt;
10068+
}
10069+
}
10070+
"#,
10071+
"@page:right{margin:1in;@bottom-left{margin:10pt}}",
10072+
);
10073+
10074+
test(
10075+
r#"
10076+
@page :right {
10077+
@bottom-left {
10078+
margin: 10pt;
10079+
}
10080+
}
10081+
"#,
10082+
indoc! {r#"
10083+
@page :right {
10084+
@bottom-left {
10085+
margin: 10pt;
10086+
}
10087+
}
10088+
"#},
10089+
);
10090+
10091+
test(
10092+
r#"
10093+
@page :right {
10094+
margin: 1in;
10095+
10096+
@bottom-left-corner { content: "Foo"; }
10097+
@bottom-right-corner { content: "Bar"; }
10098+
}
10099+
"#,
10100+
indoc! {r#"
10101+
@page :right {
10102+
margin: 1in;
10103+
10104+
@bottom-left-corner {
10105+
content: "Foo";
10106+
}
10107+
10108+
@bottom-right-corner {
10109+
content: "Bar";
10110+
}
10111+
}
10112+
"#},
10113+
);
10114+
10115+
error_test(
10116+
r#"
10117+
@page {
10118+
@foo {
10119+
margin: 1in;
10120+
}
10121+
}
10122+
"#,
10123+
ParserError::AtRuleInvalid("foo".into()),
10124+
);
10125+
10126+
error_test(
10127+
r#"
10128+
@page {
10129+
@top-left-corner {
10130+
@bottom-left {
10131+
margin: 1in;
10132+
}
10133+
}
10134+
}
10135+
"#,
10136+
ParserError::AtRuleInvalid("bottom-left".into()),
10137+
);
1005110138
}
1005210139

1005310140
#[test]

src/parser.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,10 @@ impl<'a, 'o, 'b, 'i, T: AtRuleParser<'i>> AtRuleParser<'i> for NestedRuleParser<
616616
loc,
617617
}))
618618
}
619-
AtRulePrelude::Page(selectors) => Ok(CssRule::Page(PageRule {
620-
selectors,
621-
declarations: DeclarationBlock::parse(input, self.options)?,
622-
loc,
623-
})),
619+
AtRulePrelude::Page(selectors) => {
620+
let rule = PageRule::parse(selectors, input, loc, self.options)?;
621+
Ok(CssRule::Page(rule))
622+
}
624623
AtRulePrelude::MozDocument => Ok(CssRule::MozDocument(MozDocumentRule {
625624
rules: self.parse_nested_rules(input)?,
626625
loc,

src/rules/page.rs

Lines changed: 217 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! The `@page` rule.
22
33
use super::Location;
4-
use crate::declaration::DeclarationBlock;
4+
use crate::declaration::{parse_declaration, DeclarationBlock};
55
use crate::error::{ParserError, PrinterError};
66
use crate::macros::enum_property;
77
use crate::printer::Printer;
8+
use crate::stylesheet::ParserOptions;
89
use crate::traits::{Parse, ToCss};
910
use crate::values::string::CowArcStr;
1011
use crate::visitor::Visit;
@@ -69,6 +70,74 @@ impl<'i> Parse<'i> for PageSelector<'i> {
6970
}
7071
}
7172

73+
enum_property! {
74+
/// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).
75+
pub enum PageMarginBox {
76+
/// A fixed-size box defined by the intersection of the top and left margins of the page box.
77+
"top-left-corner": TopLeftCorner,
78+
/// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes.
79+
"top-left": TopLeft,
80+
/// A variable-width box centered horizontally between the page’s left and right border edges and filling the
81+
/// page top margin between the top-left and top-right page-margin boxes.
82+
"top-center": TopCenter,
83+
/// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes.
84+
"top-right": TopRight,
85+
/// A fixed-size box defined by the intersection of the top and right margins of the page box.
86+
"top-right-corner": TopRightCorner,
87+
/// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes.
88+
"left-top": LeftTop,
89+
/// A variable-height box centered vertically between the page’s top and bottom border edges and filling the
90+
/// left page margin between the left-top and left-bottom page-margin boxes.
91+
"left-middle": LeftMiddle,
92+
/// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes.
93+
"left-bottom": LeftBottom,
94+
/// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes.
95+
"right-top": RightTop,
96+
/// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right
97+
/// page margin between the right-top and right-bottom page-margin boxes.
98+
"right-middle": RightMiddle,
99+
/// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes.
100+
"right-bottom": RightBottom,
101+
/// A fixed-size box defined by the intersection of the bottom and left margins of the page box.
102+
"bottom-left-corner": BottomLeftCorner,
103+
/// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes.
104+
"bottom-left": BottomLeft,
105+
/// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom
106+
/// page margin between the bottom-left and bottom-right page-margin boxes.
107+
"bottom-center": BottomCenter,
108+
/// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes.
109+
"bottom-right": BottomRight,
110+
/// A fixed-size box defined by the intersection of the bottom and right margins of the page box.
111+
"bottom-right-corner": BottomRightCorner,
112+
}
113+
}
114+
115+
/// A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule.
116+
#[derive(Debug, PartialEq, Clone, Visit)]
117+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118+
pub struct PageMarginRule<'i> {
119+
/// The margin box identifier for this rule.
120+
pub margin_box: PageMarginBox,
121+
/// The declarations within the rule.
122+
#[cfg_attr(feature = "serde", serde(borrow))]
123+
pub declarations: DeclarationBlock<'i>,
124+
/// The location of the rule in the source file.
125+
#[skip_visit]
126+
pub loc: Location,
127+
}
128+
129+
impl<'i> ToCss for PageMarginRule<'i> {
130+
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
131+
where
132+
W: std::fmt::Write,
133+
{
134+
dest.add_mapping(self.loc);
135+
dest.write_char('@')?;
136+
self.margin_box.to_css(dest)?;
137+
self.declarations.to_css_block(dest)
138+
}
139+
}
140+
72141
/// A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.
73142
#[derive(Debug, PartialEq, Clone, Visit)]
74143
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -79,11 +148,50 @@ pub struct PageRule<'i> {
79148
pub selectors: Vec<PageSelector<'i>>,
80149
/// The declarations within the `@page` rule.
81150
pub declarations: DeclarationBlock<'i>,
151+
/// The nested margin rules.
152+
pub rules: Vec<PageMarginRule<'i>>,
82153
/// The location of the rule in the source file.
83154
#[skip_visit]
84155
pub loc: Location,
85156
}
86157

158+
impl<'i> PageRule<'i> {
159+
pub(crate) fn parse<'t, 'o, T>(
160+
selectors: Vec<PageSelector<'i>>,
161+
input: &mut Parser<'i, 't>,
162+
loc: Location,
163+
options: &ParserOptions<'o, 'i, T>,
164+
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
165+
let mut declarations = DeclarationBlock::new();
166+
let mut rules = Vec::new();
167+
let mut parser = DeclarationListParser::new(
168+
input,
169+
PageRuleParser {
170+
declarations: &mut declarations,
171+
rules: &mut rules,
172+
options: &options,
173+
},
174+
);
175+
176+
while let Some(decl) = parser.next() {
177+
if let Err((err, _)) = decl {
178+
if parser.parser.options.error_recovery {
179+
parser.parser.options.warn(err);
180+
continue;
181+
}
182+
return Err(err);
183+
}
184+
}
185+
186+
Ok(PageRule {
187+
selectors,
188+
declarations,
189+
rules,
190+
loc,
191+
})
192+
}
193+
}
194+
87195
impl<'i> ToCss for PageRule<'i> {
88196
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
89197
where
@@ -106,7 +214,53 @@ impl<'i> ToCss for PageRule<'i> {
106214
selector.to_css(dest)?;
107215
}
108216
}
109-
self.declarations.to_css_block(dest)
217+
218+
dest.whitespace()?;
219+
dest.write_char('{')?;
220+
dest.indent();
221+
222+
let mut i = 0;
223+
let len = self.declarations.len() + self.rules.len();
224+
225+
macro_rules! write {
226+
($decls: expr, $important: literal) => {
227+
for decl in &$decls {
228+
dest.newline()?;
229+
decl.to_css(dest, $important)?;
230+
if i != len - 1 || !dest.minify {
231+
dest.write_char(';')?;
232+
}
233+
i += 1;
234+
}
235+
};
236+
}
237+
238+
write!(self.declarations.declarations, false);
239+
write!(self.declarations.important_declarations, true);
240+
241+
if !self.rules.is_empty() {
242+
if !dest.minify && self.declarations.len() > 0 {
243+
dest.write_char('\n')?;
244+
}
245+
dest.newline()?;
246+
247+
let mut first = true;
248+
for rule in &self.rules {
249+
if first {
250+
first = false;
251+
} else {
252+
if !dest.minify {
253+
dest.write_char('\n')?;
254+
}
255+
dest.newline()?;
256+
}
257+
rule.to_css(dest)?;
258+
}
259+
}
260+
261+
dest.dedent();
262+
dest.newline()?;
263+
dest.write_char('}')
110264
}
111265
}
112266

@@ -127,3 +281,64 @@ impl<'i> ToCss for PageSelector<'i> {
127281
Ok(())
128282
}
129283
}
284+
285+
struct PageRuleParser<'a, 'o, 'i, T> {
286+
declarations: &'a mut DeclarationBlock<'i>,
287+
rules: &'a mut Vec<PageMarginRule<'i>>,
288+
options: &'a ParserOptions<'o, 'i, T>,
289+
}
290+
291+
impl<'a, 'o, 'i, T> cssparser::DeclarationParser<'i> for PageRuleParser<'a, 'o, 'i, T> {
292+
type Declaration = ();
293+
type Error = ParserError<'i>;
294+
295+
fn parse_value<'t>(
296+
&mut self,
297+
name: CowRcStr<'i>,
298+
input: &mut cssparser::Parser<'i, 't>,
299+
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
300+
parse_declaration(
301+
name,
302+
input,
303+
&mut self.declarations.declarations,
304+
&mut self.declarations.important_declarations,
305+
&self.options,
306+
)
307+
}
308+
}
309+
310+
impl<'a, 'o, 'i, T> AtRuleParser<'i> for PageRuleParser<'a, 'o, 'i, T> {
311+
type Prelude = PageMarginBox;
312+
type AtRule = ();
313+
type Error = ParserError<'i>;
314+
315+
fn parse_prelude<'t>(
316+
&mut self,
317+
name: CowRcStr<'i>,
318+
input: &mut Parser<'i, 't>,
319+
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
320+
let loc = input.current_source_location();
321+
PageMarginBox::parse_string(&name)
322+
.map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))
323+
}
324+
325+
fn parse_block<'t>(
326+
&mut self,
327+
prelude: Self::Prelude,
328+
start: &ParserState,
329+
input: &mut Parser<'i, 't>,
330+
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
331+
let loc = start.source_location();
332+
let declarations = DeclarationBlock::parse(input, self.options)?;
333+
self.rules.push(PageMarginRule {
334+
margin_box: prelude,
335+
declarations,
336+
loc: Location {
337+
source_index: self.options.source_index,
338+
line: loc.line,
339+
column: loc.column,
340+
},
341+
});
342+
Ok(())
343+
}
344+
}

0 commit comments

Comments
 (0)