Fixes #304 - merging multiple backgrounds.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 8 Jul 2014 23:28:03 +0000 (00:28 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 10 Jul 2014 22:06:09 +0000 (23:06 +0100)
lib/properties/processable.js
lib/text/comma-splitter.js [new file with mode: 0644]
test/data/issue-304-min.css [new file with mode: 0644]
test/data/issue-304.css [new file with mode: 0644]
test/text/comma-splitter-test.js [new file with mode: 0644]

index 35fa82b..0e961fb 100644 (file)
@@ -1,11 +1,11 @@
 
 // Contains the interpretation of CSS properties, as used by the property optimizer
 
-
 module.exports = (function () {
 
   var tokenModule = require('./token');
   var validator = require('./validator');
+  var CommaSplitter = require('../text/comma-splitter');
 
   // Functions that decide what value can override what.
   // The main purpose is to disallow removing CSS fallbacks.
@@ -177,6 +177,30 @@ module.exports = (function () {
     return result;
   });
   // Breaks up a background property value
+  breakUp.commaSeparatedMulitpleValues = function (splitfunc) {
+    return function (token) {
+      if (token.value.indexOf(',') === -1)
+        return splitfunc(token);
+
+      var values = new CommaSplitter(token.value).split();
+      var components = [];
+
+      for (var i = 0, l = values.length; i < l; i++) {
+        token.value = values[i];
+        components.push(splitfunc(token));
+      }
+
+      for (var j = 0, m = components[0].length; j < m; j++) {
+        for (var k = 0, n = components.length, newValues = []; k < n; k++) {
+          newValues.push(components[k][j].value);
+        }
+
+        components[0][j].value = newValues.join(',');
+      }
+
+      return components[0];
+    };
+  };
   breakUp.background = function (token) {
     // Default values
     var result = Token.makeDefaults(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant);
@@ -462,6 +486,40 @@ module.exports = (function () {
 
       return result;
     },
+    commaSeparatedMulitpleValues: function (assembleFunction) {
+      return function(prop, tokens, isImportant) {
+        var partsCount = new CommaSplitter(tokens[0].value).split();
+        if (partsCount == 1)
+          return assembleFunction(prop, tokens, isImportant);
+
+        var merged = [];
+
+        for (var i = 0; i < partsCount.length; i++) {
+          merged.push([]);
+
+          for (var j = 0; j < tokens.length; j++) {
+            merged[i].push(new CommaSplitter(tokens[j].value).split()[i]);
+          }
+        }
+
+        var mergedValues = [];
+        var firstProcessed;
+        for (i = 0; i < partsCount; i++) {
+          for (var k = 0, n = merged[i].length; k < n; k++) {
+            tokens[k].value = merged[i][k];
+          }
+
+          var processed = assembleFunction(prop, tokens, isImportant);
+          mergedValues.push(processed.value);
+
+          if (!firstProcessed)
+            firstProcessed = processed;
+        }
+
+        firstProcessed.value = mergedValues.join(',');
+        return firstProcessed;
+      };
+    },
     // Handles the cases when some or all the fine-grained properties are set to inherit
     takeCareOfInherit: function (innerFunc) {
       return function (prop, tokens, isImportant) {
@@ -580,8 +638,10 @@ module.exports = (function () {
         'background-position',
         'background-attachment'
       ],
-      breakUp: breakUp.background,
-      putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
+      breakUp: breakUp.commaSeparatedMulitpleValues(breakUp.background),
+      putTogether: putTogether.commaSeparatedMulitpleValues(
+        putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
+      ),
       defaultValue: '0 0',
       shortestValue: '0'
     },
diff --git a/lib/text/comma-splitter.js b/lib/text/comma-splitter.js
new file mode 100644 (file)
index 0000000..eae4f37
--- /dev/null
@@ -0,0 +1,35 @@
+var Splitter = function CommaSplitter (value) {
+  this.value = value;
+};
+
+Splitter.prototype.split = function () {
+  if (this.value.indexOf(',') === -1)
+    return [this.value];
+
+  if (this.value.indexOf('(') === -1)
+    return this.value.split(',');
+
+  var level = 0;
+  var cursor = 0;
+  var lastStart = 0;
+  var len = this.value.length;
+  var tokens = [];
+
+  while (cursor++ < len) {
+    if (this.value[cursor] == '(') {
+      level++;
+    } else if (this.value[cursor] == ')') {
+      level--;
+    } else if (this.value[cursor] == ',' && level === 0) {
+      tokens.push(this.value.substring(lastStart, cursor));
+      lastStart = cursor + 1;
+    }
+  }
+
+  if (lastStart < cursor + 1)
+    tokens.push(this.value.substring(lastStart));
+
+  return tokens;
+};
+
+module.exports = Splitter;
diff --git a/test/data/issue-304-min.css b/test/data/issue-304-min.css
new file mode 100644 (file)
index 0000000..a72a144
--- /dev/null
@@ -0,0 +1 @@
+.test{background:url(top.png) no-repeat left 0 top -12px,url(bottom.png) no-repeat left 0 bottom -12px,url(middle.png) no-repeat left 0 top 0}
diff --git a/test/data/issue-304.css b/test/data/issue-304.css
new file mode 100644 (file)
index 0000000..35de5fd
--- /dev/null
@@ -0,0 +1,4 @@
+.test {
+  background: url(top.png) no-repeat, url(bottom.png) no-repeat, url(middle.png) no-repeat;
+  background-position: left 0px top -12px, left 0px bottom -12px, left 0px top 0px;
+}
diff --git a/test/text/comma-splitter-test.js b/test/text/comma-splitter-test.js
new file mode 100644 (file)
index 0000000..2e7539a
--- /dev/null
@@ -0,0 +1,17 @@
+var vows = require('vows');
+var assert = require('assert');
+var CommaSplitter = require('../../lib/text/comma-splitter');
+
+var split = function (value, expectedValue) {
+  return function () {
+    assert.deepEqual(new CommaSplitter(value).split(), expectedValue);
+  };
+};
+
+vows.describe('comma-splitter').addBatch({
+  'empty': split('', ['']),
+  'simple': split('none', ['none']),
+  'comma separated - level 0': split('#000,#fff,#0f0', ['#000', '#fff', '#0f0']),
+  'comma separated - level 1': split('rgb(0,0,0),#fff', ['rgb(0,0,0)', '#fff']),
+  'comma separated - level 2': split('linear-gradient(0,#fff,rgba(0,0,0)),red', ['linear-gradient(0,#fff,rgba(0,0,0))', 'red'])
+}).export(module);