Fixes #952 - parsing `@page` as in CSS3 spec.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 22 Jun 2017 08:35:52 +0000 (10:35 +0200)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 29 Jun 2017 06:53:38 +0000 (08:53 +0200)
Why:

* CSS3 spec allows `@page` rules to have custom names and contain
  page-margin boxes, see https://www.w3.org/TR/css3-page/#margin-boxes
* it enables all (?) of Prince page box rules too by whitelisting them.

History.md
lib/tokenizer/tokenize.js
lib/writer/helpers.js
test/integration-test.js
test/tokenizer/tokenize-test.js

index 0cb34ca..5f03f1c 100644 (file)
@@ -10,6 +10,7 @@
 ==================
 
 * Fixed issue [#945](https://github.com/jakubpawlowicz/clean-css/issues/945) - hex RGBA colors in IE filters.
+* Fixed issue [#952](https://github.com/jakubpawlowicz/clean-css/issues/952) - parsing `@page` according to CSS3 spec.
 
 [4.1.4 / 2017-06-14](https://github.com/jakubpawlowicz/clean-css/compare/v4.1.3...v4.1.4)
 ==================
index 4cde136..f2d4ae8 100644 (file)
@@ -30,6 +30,35 @@ var BLOCK_RULES = [
 
 var IGNORE_END_COMMENT_PATTERN = /\/\* clean\-css ignore:end \*\/$/;
 var IGNORE_START_COMMENT_PATTERN = /^\/\* clean\-css ignore:start \*\//;
+
+var PAGE_MARGIN_BOXES = [
+  '@bottom-center',
+  '@bottom-left',
+  '@bottom-left-corner',
+  '@bottom-right',
+  '@bottom-right-corner',
+  '@left-bottom',
+  '@left-middle',
+  '@left-top',
+  '@right-bottom',
+  '@right-middle',
+  '@right-top',
+  '@top-center',
+  '@top-left',
+  '@top-left-corner',
+  '@top-right',
+  '@top-right-corner'
+];
+
+var EXTRA_PAGE_BOXES = [
+  '@footnote',
+  '@footnotes',
+  '@left',
+  '@page-float-bottom',
+  '@page-float-top',
+  '@right'
+];
+
 var REPEAT_PATTERN = /^\[\s*\d+\s*\]$/;
 var RULE_WORD_SEPARATOR_PATTERN = /[\s\(]/;
 var TAIL_BROKEN_VALUE_PATTERN = /[\s|\}]*$/;
@@ -255,6 +284,18 @@ function intoTokens(source, externalContext, internalContext, isNested) {
       levels.push(level);
       level = Level.RULE;
       seekingValue = false;
+    } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.RULE && isPageMarginBox(buffer)) {
+      // open brace opening page-margin box at rule level, e.g. @page{@top-center{<--
+      serializedBuffer = buffer.join('').trim();
+      ruleTokens.push(ruleToken);
+      ruleToken = [Token.AT_RULE_BLOCK, [], []];
+      ruleToken[1].push([Token.AT_RULE_BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
+      newTokens.push(ruleToken);
+      newTokens = ruleToken[2];
+
+      levels.push(level);
+      level = Level.RULE;
+      buffer = [];
     } else if (character == Marker.COLON && level == Level.RULE && !seekingValue) {
       // colon at rule level, e.g. a{color:<--
       serializedBuffer = buffer.join('').trim();
@@ -509,6 +550,12 @@ function tokenScopeFrom(tokenType) {
   }
 }
 
+function isPageMarginBox(buffer) {
+  var serializedBuffer = buffer.join('').trim();
+
+  return PAGE_MARGIN_BOXES.indexOf(serializedBuffer) > -1 || EXTRA_PAGE_BOXES.indexOf(serializedBuffer) > -1;
+}
+
 function isRepeatToken(buffer) {
   return REPEAT_PATTERN.test(buffer.join('') + Marker.CLOSE_SQUARE_BRACKET);
 }
index 7ee55b8..5be184f 100644 (file)
@@ -87,6 +87,12 @@ function property(context, tokens, position, lastPropertyAt) {
       store(context, token);
       store(context, semicolon(context, Breaks.AfterProperty, false));
       break;
+    case Token.AT_RULE_BLOCK:
+      rules(context, token[1]);
+      store(context, openBrace(context, Breaks.AfterRuleBegins, true));
+      body(context, token[2]);
+      store(context, closeBrace(context, Breaks.AfterRuleEnds, false, isLast));
+      break;
     case Token.COMMENT:
       store(context, token);
       break;
index 4cf2a27..1c7d96d 100644 (file)
@@ -2381,6 +2381,14 @@ vows.describe('integration tests')
         '@page{margin:.5em}',
         '@page{margin:.5em}'
       ],
+      '@page named': [
+        '@page :first{margin:.5em}',
+        '@page :first{margin:.5em}'
+      ],
+      '@page named with page-margin box': [
+        '@page :first{margin:10px;@top-center{content:"Page One"}padding:5px}',
+        '@page :first{margin:10px;@top-center{content:"Page One"}padding:5px}'
+      ],
       '@supports': [
         '@supports (display:flexbox){.flex{display:flexbox}}',
         '@supports (display:flexbox){.flex{display:flexbox}}'
index 0002ab9..cd38c60 100644 (file)
@@ -1891,6 +1891,127 @@ vows.describe(tokenize)
           ]
         ]
       ],
+      '@page named rule': [
+        '@page one{margin:10px}',
+        [
+          [
+            'at-rule-block',
+            [
+              [
+                'at-rule-block-scope',
+                '@page one',
+                [
+                  [1, 0, undefined]
+                ]
+              ]
+            ],
+            [
+              [
+                'property',
+                [
+                  'property-name',
+                  'margin',
+                  [
+                    [1, 10, undefined]
+                  ]
+                ],
+                [
+                  'property-value',
+                  '10px',
+                  [
+                    [1, 17, undefined]
+                  ]
+                ]
+              ]
+
+            ]
+          ]
+        ]
+      ],
+      '@page named rule with page-margin box': [
+        '@page :first{margin:10px;@top-center{content:"Page One"}padding:5px}',
+        [
+          [
+            'at-rule-block',
+            [
+              [
+                'at-rule-block-scope',
+                '@page :first',
+                [
+                  [1, 0, undefined]
+                ]
+              ]
+            ],
+            [
+              [
+                'property',
+                [
+                  'property-name',
+                  'margin',
+                  [
+                    [1, 13, undefined]
+                  ]
+                ],
+                [
+                  'property-value',
+                  '10px',
+                  [
+                    [1, 20, undefined]
+                  ]
+                ]
+              ],
+              [
+                'at-rule-block',
+                [
+                  [
+                    'at-rule-block-scope',
+                    '@top-center',
+                    [
+                      [1, 25, undefined]
+                    ]
+                  ]
+                ],
+                [
+                  [
+                    'property',
+                    [
+                      'property-name',
+                      'content',
+                      [
+                        [1, 37, undefined]
+                      ]
+                    ],
+                    [
+                      'property-value',
+                      '"Page One"',
+                      [
+                        [1, 45, undefined]
+                      ]
+                    ]
+                  ]
+                ]
+              ],
+              [
+                'property',
+                [
+                  'property-name',
+                  'padding',
+                  [
+                    [1, 56, undefined]
+                  ]
+                ],
+                [
+                  'property-value',
+                  '5px',
+                  [
+                    [1, 64, undefined]
+                  ]
+                ]
+              ]
+            ]
+          ]
+        ]
+      ],
       'media query': [
         '@media (min-width:980px){}',
         [