Skip to content

Commit 4bb17f0

Browse files
committed
add more at rules support
1 parent fe510b9 commit 4bb17f0

7 files changed

Lines changed: 181 additions & 19 deletions

File tree

src/parse/index.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
type CssMediaAST,
2222
type CssNamespaceAST,
2323
type CssPageAST,
24+
type CssPageMarginBoxAST,
2425
type CssPositionTryAST,
2526
type CssPropertyAST,
2627
type CssRuleAST,
@@ -678,6 +679,60 @@ export const parse = (
678679
});
679680
}
680681

682+
/**
683+
* Parse @page margin box at-rules (@top-left, @bottom-right, @left-middle, etc.).
684+
*/
685+
const pageMarginBoxNames = [
686+
'top-left-corner',
687+
'top-left',
688+
'top-center',
689+
'top-right',
690+
'top-right-corner',
691+
'bottom-left-corner',
692+
'bottom-left',
693+
'bottom-center',
694+
'bottom-right',
695+
'bottom-right-corner',
696+
'left-top',
697+
'left-middle',
698+
'left-bottom',
699+
'right-top',
700+
'right-middle',
701+
'right-bottom',
702+
];
703+
const pageMarginBoxRegex = new RegExp(
704+
'^@(' + pageMarginBoxNames.join('|') + ')(?![\\w-])\\s*',
705+
);
706+
707+
function atPageMarginBox(): CssPageMarginBoxAST | undefined {
708+
const pos = position();
709+
const m = pageMarginBoxRegex.exec(css);
710+
if (!m) {
711+
return;
712+
}
713+
const name = processMatch(m)[1];
714+
715+
if (!open()) {
716+
return error(`@${name} missing '{'`);
717+
}
718+
let decls = comments<CssDeclarationAST>();
719+
let decl: CssDeclarationAST | undefined = declaration();
720+
while (decl) {
721+
decls.push(decl);
722+
decls = decls.concat(comments());
723+
decl = declaration();
724+
}
725+
if (!close()) {
726+
return error(`@${name} missing '}'`);
727+
}
728+
729+
return pos<CssPageMarginBoxAST>({
730+
type: CssTypes.pageMarginBox,
731+
name: name,
732+
declarations: decls,
733+
});
734+
}
735+
681736
/**
682737
* Parse paged media.
683738
*/
@@ -1123,6 +1178,7 @@ export const parse = (
11231178
atScope() ||
11241179
atViewTransition() ||
11251180
atPositionTry() ||
1181+
atPageMarginBox() ||
11261182
atGeneric()
11271183
);
11281184
}

src/stringify/compiler.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
type CssMediaAST,
2020
type CssNamespaceAST,
2121
type CssPageAST,
22+
type CssPageMarginBoxAST,
2223
type CssPositionTryAST,
2324
type CssPropertyAST,
2425
type CssRuleAST,
@@ -109,6 +110,8 @@ class Compiler {
109110
return this.namespace(node);
110111
case CssTypes.page:
111112
return this.page(node);
113+
case CssTypes.pageMarginBox:
114+
return this.pageMarginBox(node);
112115
case CssTypes.positionTry:
113116
return this.positionTry(node);
114117
case CssTypes.property:
@@ -383,6 +386,28 @@ class Compiler {
383386
);
384387
}
385388

389+
/**
390+
* Visit @page margin box node (@top-left, @bottom-right, etc.).
391+
*/
392+
pageMarginBox(node: CssPageMarginBoxAST) {
393+
if (this.compress) {
394+
return (
395+
this.emit(`@${node.name}`, node.position) +
396+
this.emit('{') +
397+
this.mapVisit(node.declarations) +
398+
this.emit('}')
399+
);
400+
}
401+
return (
402+
this.emit(`${this.indent()}@${node.name} `, node.position) +
403+
this.emit('{\n') +
404+
this.emit(this.indent(1)) +
405+
this.mapVisit(node.declarations, '\n') +
406+
this.emit(this.indent(-1)) +
407+
this.emit(`\n${this.indent()}}`)
408+
);
409+
}
410+
386411
/**
387412
* Visit font-face node.
388413
*/

src/type.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export enum CssTypes {
2222
media = 'media',
2323
namespace = 'namespace',
2424
page = 'page',
25+
pageMarginBox = 'page-margin-box',
2526
positionTry = 'position-try',
2627
property = 'property',
2728
scope = 'scope',
@@ -167,6 +168,11 @@ export type CssViewTransitionAST = CssCommonPositionAST & {
167168
type: CssTypes.viewTransition;
168169
declarations: Array<CssDeclarationAST | CssCommentAST>;
169170
};
171+
export type CssPageMarginBoxAST = CssCommonPositionAST & {
172+
type: CssTypes.pageMarginBox;
173+
name: string;
174+
declarations: Array<CssDeclarationAST | CssCommentAST>;
175+
};
170176
export type CssGenericAtRuleAST = CssCommonPositionAST & {
171177
type: CssTypes.atRule;
172178
name: string;
@@ -191,6 +197,7 @@ export type CssAtRuleAST =
191197
| CssMediaAST
192198
| CssNamespaceAST
193199
| CssPageAST
200+
| CssPageMarginBoxAST
194201
| CssPositionTryAST
195202
| CssPropertyAST
196203
| CssScopeAST
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"type":"stylesheet","stylesheet":{"source":"input.css","rules":[{"type":"page","selectors":[],"declarations":[{"type":"declaration","property":"margin","value":"2cm","position":{"start":{"line":2,"column":3},"end":{"line":2,"column":14},"source":"input.css"}},{"type":"at-rule","name":"top-center","prelude":"","rules":[{"type":"declaration","property":"content","value":"\"Page Title\"","position":{"start":{"line":5,"column":5},"end":{"line":5,"column":26},"source":"input.css"}}],"position":{"start":{"line":4,"column":3},"end":{"line":6,"column":4},"source":"input.css"}},{"type":"at-rule","name":"bottom-right","prelude":"","rules":[{"type":"declaration","property":"content","value":"counter(page)","position":{"start":{"line":9,"column":5},"end":{"line":9,"column":27},"source":"input.css"}}],"position":{"start":{"line":8,"column":3},"end":{"line":10,"column":4},"source":"input.css"}},{"type":"at-rule","name":"left-middle","prelude":"","rules":[{"type":"declaration","property":"content","value":"\"Side\"","position":{"start":{"line":13,"column":5},"end":{"line":13,"column":20},"source":"input.css"}},{"type":"declaration","property":"font-size","value":"10pt","position":{"start":{"line":14,"column":5},"end":{"line":14,"column":20},"source":"input.css"}}],"position":{"start":{"line":12,"column":3},"end":{"line":15,"column":4},"source":"input.css"}}],"position":{"start":{"line":1,"column":1},"end":{"line":16,"column":2},"source":"input.css"}},{"type":"page","selectors":[":first"],"declarations":[{"type":"declaration","property":"margin-top","value":"4cm","position":{"start":{"line":19,"column":3},"end":{"line":19,"column":18},"source":"input.css"}},{"type":"at-rule","name":"top-left","prelude":"","rules":[{"type":"declaration","property":"content","value":"none","position":{"start":{"line":22,"column":5},"end":{"line":22,"column":18},"source":"input.css"}}],"position":{"start":{"line":21,"column":3},"end":{"line":23,"column":4},"source":"input.css"}}],"position":{"start":{"line":18,"column":1},"end":{"line":24,"column":2},"source":"input.css"}}],"parsingErrors":[]}}
1+
{"type":"stylesheet","stylesheet":{"source":"input.css","rules":[{"type":"page","selectors":[],"declarations":[{"type":"declaration","property":"margin","value":"2cm","position":{"start":{"line":2,"column":3},"end":{"line":2,"column":14},"source":"input.css"}},{"type":"page-margin-box","name":"top-left-corner","declarations":[{"type":"declaration","property":"content","value":"\"\"","position":{"start":{"line":5,"column":5},"end":{"line":5,"column":16},"source":"input.css"}}],"position":{"start":{"line":4,"column":3},"end":{"line":6,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"top-left","declarations":[{"type":"declaration","property":"content","value":"\"Header Left\"","position":{"start":{"line":9,"column":5},"end":{"line":9,"column":27},"source":"input.css"}}],"position":{"start":{"line":8,"column":3},"end":{"line":10,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"top-center","declarations":[{"type":"declaration","property":"content","value":"\"Page Title\"","position":{"start":{"line":13,"column":5},"end":{"line":13,"column":26},"source":"input.css"}}],"position":{"start":{"line":12,"column":3},"end":{"line":14,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"top-right","declarations":[{"type":"declaration","property":"content","value":"\"Header Right\"","position":{"start":{"line":17,"column":5},"end":{"line":17,"column":28},"source":"input.css"}}],"position":{"start":{"line":16,"column":3},"end":{"line":18,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"top-right-corner","declarations":[{"type":"declaration","property":"content","value":"\"\"","position":{"start":{"line":21,"column":5},"end":{"line":21,"column":16},"source":"input.css"}}],"position":{"start":{"line":20,"column":3},"end":{"line":22,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"bottom-left-corner","declarations":[{"type":"declaration","property":"content","value":"\"\"","position":{"start":{"line":25,"column":5},"end":{"line":25,"column":16},"source":"input.css"}}],"position":{"start":{"line":24,"column":3},"end":{"line":26,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"bottom-left","declarations":[{"type":"declaration","property":"content","value":"\"Footer Left\"","position":{"start":{"line":29,"column":5},"end":{"line":29,"column":27},"source":"input.css"}}],"position":{"start":{"line":28,"column":3},"end":{"line":30,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"bottom-center","declarations":[{"type":"declaration","property":"content","value":"counter(page)","position":{"start":{"line":33,"column":5},"end":{"line":33,"column":27},"source":"input.css"}}],"position":{"start":{"line":32,"column":3},"end":{"line":34,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"bottom-right","declarations":[{"type":"declaration","property":"content","value":"\"Footer Right\"","position":{"start":{"line":37,"column":5},"end":{"line":37,"column":28},"source":"input.css"}}],"position":{"start":{"line":36,"column":3},"end":{"line":38,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"bottom-right-corner","declarations":[{"type":"declaration","property":"content","value":"\"\"","position":{"start":{"line":41,"column":5},"end":{"line":41,"column":16},"source":"input.css"}}],"position":{"start":{"line":40,"column":3},"end":{"line":42,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"left-top","declarations":[{"type":"declaration","property":"content","value":"\"LT\"","position":{"start":{"line":45,"column":5},"end":{"line":45,"column":18},"source":"input.css"}}],"position":{"start":{"line":44,"column":3},"end":{"line":46,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"left-middle","declarations":[{"type":"declaration","property":"content","value":"\"LM\"","position":{"start":{"line":49,"column":5},"end":{"line":49,"column":18},"source":"input.css"}}],"position":{"start":{"line":48,"column":3},"end":{"line":50,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"left-bottom","declarations":[{"type":"declaration","property":"content","value":"\"LB\"","position":{"start":{"line":53,"column":5},"end":{"line":53,"column":18},"source":"input.css"}}],"position":{"start":{"line":52,"column":3},"end":{"line":54,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"right-top","declarations":[{"type":"declaration","property":"content","value":"\"RT\"","position":{"start":{"line":57,"column":5},"end":{"line":57,"column":18},"source":"input.css"}}],"position":{"start":{"line":56,"column":3},"end":{"line":58,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"right-middle","declarations":[{"type":"declaration","property":"content","value":"\"RM\"","position":{"start":{"line":61,"column":5},"end":{"line":61,"column":18},"source":"input.css"}}],"position":{"start":{"line":60,"column":3},"end":{"line":62,"column":4},"source":"input.css"}},{"type":"page-margin-box","name":"right-bottom","declarations":[{"type":"declaration","property":"content","value":"\"RB\"","position":{"start":{"line":65,"column":5},"end":{"line":65,"column":18},"source":"input.css"}}],"position":{"start":{"line":64,"column":3},"end":{"line":66,"column":4},"source":"input.css"}}],"position":{"start":{"line":1,"column":1},"end":{"line":67,"column":2},"source":"input.css"}}],"parsingErrors":[]}}

test/cases/page-margin-boxes/compressed.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,67 @@
11
@page {
22
margin: 2cm;
33

4+
@top-left-corner {
5+
content: "";
6+
}
7+
8+
@top-left {
9+
content: "Header Left";
10+
}
11+
412
@top-center {
513
content: "Page Title";
614
}
715

8-
@bottom-right {
16+
@top-right {
17+
content: "Header Right";
18+
}
19+
20+
@top-right-corner {
21+
content: "";
22+
}
23+
24+
@bottom-left-corner {
25+
content: "";
26+
}
27+
28+
@bottom-left {
29+
content: "Footer Left";
30+
}
31+
32+
@bottom-center {
933
content: counter(page);
1034
}
1135

36+
@bottom-right {
37+
content: "Footer Right";
38+
}
39+
40+
@bottom-right-corner {
41+
content: "";
42+
}
43+
44+
@left-top {
45+
content: "LT";
46+
}
47+
1248
@left-middle {
13-
content: "Side";
14-
font-size: 10pt;
49+
content: "LM";
1550
}
16-
}
1751

18-
@page :first {
19-
margin-top: 4cm;
52+
@left-bottom {
53+
content: "LB";
54+
}
2055

21-
@top-left {
22-
content: none;
56+
@right-top {
57+
content: "RT";
58+
}
59+
60+
@right-middle {
61+
content: "RM";
62+
}
63+
64+
@right-bottom {
65+
content: "RB";
2366
}
2467
}
Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,51 @@
11
@page {
22
margin: 2cm;
3+
@top-left-corner {
4+
content: "";
5+
}
6+
@top-left {
7+
content: "Header Left";
8+
}
39
@top-center {
410
content: "Page Title";
511
}
6-
@bottom-right {
12+
@top-right {
13+
content: "Header Right";
14+
}
15+
@top-right-corner {
16+
content: "";
17+
}
18+
@bottom-left-corner {
19+
content: "";
20+
}
21+
@bottom-left {
22+
content: "Footer Left";
23+
}
24+
@bottom-center {
725
content: counter(page);
826
}
27+
@bottom-right {
28+
content: "Footer Right";
29+
}
30+
@bottom-right-corner {
31+
content: "";
32+
}
33+
@left-top {
34+
content: "LT";
35+
}
936
@left-middle {
10-
content: "Side";
11-
font-size: 10pt;
37+
content: "LM";
1238
}
13-
}
14-
15-
@page :first {
16-
margin-top: 4cm;
17-
@top-left {
18-
content: none;
39+
@left-bottom {
40+
content: "LB";
41+
}
42+
@right-top {
43+
content: "RT";
44+
}
45+
@right-middle {
46+
content: "RM";
47+
}
48+
@right-bottom {
49+
content: "RB";
1950
}
2051
}

0 commit comments

Comments
 (0)