From 6760b0330c99a0d3120834ee2a4692d94d846bc3 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Wed, 5 Apr 2017 11:35:43 +0200 Subject: [PATCH] See #254 - adds system font support. Why: * System fonts should not be mergeable with other types of fonts as such merging is not supported by browsers. --- lib/optimizer/level-2/break-up.js | 6 ++ .../properties/is-mergeable-shorthand.js | 11 +++ .../level-2/properties/override-properties.js | 14 ++++ lib/optimizer/level-2/restore.js | 5 ++ lib/optimizer/validator.js | 9 ++ lib/tokenizer/marker.js | 1 + test/optimizer/level-2/break-up-test.js | 84 +++++++++++++++++++ .../properties/override-properties-test.js | 47 +++++++++++ test/optimizer/level-2/restore-test.js | 32 +++++++ 9 files changed, 209 insertions(+) create mode 100644 lib/optimizer/level-2/properties/is-mergeable-shorthand.js diff --git a/lib/optimizer/level-2/break-up.js b/lib/optimizer/level-2/break-up.js index e3e495da..ec86e039 100644 --- a/lib/optimizer/level-2/break-up.js +++ b/lib/optimizer/level-2/break-up.js @@ -221,6 +221,12 @@ function font(property, compactable, validator) { throw new InvalidPropertyError('Missing font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.'); } + if (values.length == 1 && (validator.isFontKeyword(values[0][1]) || validator.isPrefixed(values[0][1]))) { + values[0][1] = Marker.INTERNAL + values[0][1]; + style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values; + return components; + } + if (values.length == 1 && values[0][1] == 'inherit') { style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values; return components; diff --git a/lib/optimizer/level-2/properties/is-mergeable-shorthand.js b/lib/optimizer/level-2/properties/is-mergeable-shorthand.js new file mode 100644 index 00000000..ee7191ef --- /dev/null +++ b/lib/optimizer/level-2/properties/is-mergeable-shorthand.js @@ -0,0 +1,11 @@ +var Marker = require('../../../tokenizer/marker'); + +function isMergeableShorthand(shorthand) { + if (shorthand.name != 'font') { + return true; + } + + return shorthand.value[0][1].indexOf(Marker.INTERNAL) == -1; +} + +module.exports = isMergeableShorthand; diff --git a/lib/optimizer/level-2/properties/override-properties.js b/lib/optimizer/level-2/properties/override-properties.js index c3ed3662..035a257d 100644 --- a/lib/optimizer/level-2/properties/override-properties.js +++ b/lib/optimizer/level-2/properties/override-properties.js @@ -2,6 +2,7 @@ var hasInherit = require('./has-inherit'); var everyValuesPair = require('./every-values-pair'); var findComponentIn = require('./find-component-in'); var isComponentOf = require('./is-component-of'); +var isMergeableShorthand = require('./is-mergeable-shorthand'); var overridesNonComponentShorthand = require('./overrides-non-component-shorthand'); var sameVendorPrefixesIn = require('./vendor-prefixes').same; @@ -275,6 +276,11 @@ function overrideProperties(properties, withMerging, compatibility, validator) { if (!anyValue(validator.isFunction, left) && overridingFunction(right, validator)) continue; + if (!isMergeableShorthand(right)) { + left.unused = true; + continue; + } + component = findComponentIn(right, left); mayOverride = compactable[left.name].canOverride; if (everyValuesPair(mayOverride.bind(null, validator), left, component)) { @@ -326,6 +332,9 @@ function overrideProperties(properties, withMerging, compatibility, validator) { if (overridingFunction(left, validator)) continue; + if (!isMergeableShorthand(left)) + continue; + component = findComponentIn(left, right); if (everyValuesPair(mayOverride.bind(null, validator), component, right)) { var disabledBackgroundMerging = @@ -368,6 +377,11 @@ function overrideProperties(properties, withMerging, compatibility, validator) { continue; } + if (!isMergeableShorthand(right)) { + left.unused = true; + continue; + } + for (k = left.components.length - 1; k >= 0; k--) { var leftComponent = left.components[k]; var rightComponent = right.components[k]; diff --git a/lib/optimizer/level-2/restore.js b/lib/optimizer/level-2/restore.js index 2f27ef5d..13f12e49 100644 --- a/lib/optimizer/level-2/restore.js +++ b/lib/optimizer/level-2/restore.js @@ -142,6 +142,11 @@ function font(property, compactable) { var componentIndex = 0; var fontFamilyIndex = 0; + if (property.value[0][1].indexOf(Marker.INTERNAL) === 0) { + property.value[0][1] = property.value[0][1].substring(Marker.INTERNAL.length); + return property.value; + } + // first four components are optional while (componentIndex < 4) { component = components[componentIndex]; diff --git a/lib/optimizer/validator.js b/lib/optimizer/validator.js index 5fc69d08..a4b8b3d3 100644 --- a/lib/optimizer/validator.js +++ b/lib/optimizer/validator.js @@ -140,6 +140,14 @@ var Keywords = { 'left': [ 'auto' ], + 'font': [ + 'caption', + 'icon', + 'menu', + 'message-box', + 'small-caption', + 'status-bar' + ], 'font-size': [ 'large', 'larger', @@ -386,6 +394,7 @@ function validator(compatibility) { isColor: isColor, isColorFunction: isColorFunction, isDynamicUnit: isDynamicUnit, + isFontKeyword: isKeyword('font'), isFontSizeKeyword: isKeyword('font-size'), isFontStretchKeyword: isKeyword('font-stretch'), isFontStyleKeyword: isKeyword('font-style'), diff --git a/lib/tokenizer/marker.js b/lib/tokenizer/marker.js index c3f6805a..767a5f0e 100644 --- a/lib/tokenizer/marker.js +++ b/lib/tokenizer/marker.js @@ -10,6 +10,7 @@ var Marker = { DOUBLE_QUOTE: '"', EXCLAMATION: '!', FORWARD_SLASH: '/', + INTERNAL: '-clean-css-', NEW_LINE_NIX: '\n', NEW_LINE_WIN: '\r', OPEN_CURLY_BRACKET: '{', diff --git a/test/optimizer/level-2/break-up-test.js b/test/optimizer/level-2/break-up-test.js index 34ad49ac..26172033 100644 --- a/test/optimizer/level-2/break-up-test.js +++ b/test/optimizer/level-2/break-up-test.js @@ -1071,6 +1071,90 @@ vows.describe(breakUp) 'has 0 components': function (components) { assert.lengthOf(components, 0); } + }, + 'system font': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', 'icon'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', '-clean-css-icon']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', '-clean-css-icon']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', '-clean-css-icon']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', '-clean-css-icon']]); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', '-clean-css-icon']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', '-clean-css-icon']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', '-clean-css-icon']]); + } + }, + 'system font with vendor prefix': { + 'topic': function () { + return _breakUp([ + [ + 'property', + ['property-name', 'font'], + ['property-value', '-moz-window'] + ] + ]); + }, + 'has 7 components': function (components) { + assert.lengthOf(components, 7); + }, + 'has font-style': function (components) { + assert.equal(components[0].name, 'font-style'); + assert.deepEqual(components[0].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has font-variant': function (components) { + assert.equal(components[1].name, 'font-variant'); + assert.deepEqual(components[1].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has font-weight': function (components) { + assert.equal(components[2].name, 'font-weight'); + assert.deepEqual(components[2].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has font-stretch': function (components) { + assert.equal(components[3].name, 'font-stretch'); + assert.deepEqual(components[3].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has font-size': function (components) { + assert.equal(components[4].name, 'font-size'); + assert.deepEqual(components[4].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has line-height': function (components) { + assert.equal(components[5].name, 'line-height'); + assert.deepEqual(components[5].value, [['property-value', '-clean-css--moz-window']]); + }, + 'has font-family': function (components) { + assert.equal(components[6].name, 'font-family'); + assert.deepEqual(components[6].value, [['property-value', '-clean-css--moz-window']]); + } } }, 'four values': { diff --git a/test/optimizer/level-2/properties/override-properties-test.js b/test/optimizer/level-2/properties/override-properties-test.js index 1d85a85e..6be19107 100644 --- a/test/optimizer/level-2/properties/override-properties-test.js +++ b/test/optimizer/level-2/properties/override-properties-test.js @@ -1716,6 +1716,53 @@ vows.describe(optimizeProperties) ] ]); } + }, + 'system font shorthand before longhand': { + 'topic': function () { + return _optimize('.block{font:icon;font-weight:bold}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 7, undefined]]], + ['property-value', 'icon', [[1, 12, undefined]]] + ], + [ + 'property', + ['property-name', 'font-weight', [[1, 17, undefined]]], + ['property-value', 'bold', [[1, 29, undefined]]] + ] + ]); + } + }, + 'system font shorthand after longhand': { + 'topic': function () { + return _optimize('.block{font-weight:bold;font:icon}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 24, undefined]]], + ['property-value', 'icon', [[1, 29, undefined]]] + ] + ]); + } + }, + 'two system font shorthands': { + 'topic': function () { + return _optimize('.block{font:status-bar;font:icon}'); + }, + 'into': function (properties) { + assert.deepEqual(properties, [ + [ + 'property', + ['property-name', 'font', [[1, 23, undefined]]], + ['property-value', 'icon', [[1, 28, undefined]]] + ] + ]); + } } }) .addBatch({ diff --git a/test/optimizer/level-2/restore-test.js b/test/optimizer/level-2/restore-test.js index e7c8f799..b37c4bcb 100644 --- a/test/optimizer/level-2/restore-test.js +++ b/test/optimizer/level-2/restore-test.js @@ -806,6 +806,38 @@ vows.describe(restore) ]); } }, + 'system font with standard value': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', 'icon'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', 'icon'] + ]); + } + }, + 'system font with vendor-prefixed value': { + 'topic': function () { + return _restore( + _breakUp([ + 'property', + ['property-name', 'font'], + ['property-value', '-moz-status'] + ]) + ); + }, + 'gives right value back': function (restoredValue) { + assert.deepEqual(restoredValue, [ + ['property-value', '-moz-status'] + ]); + } + }, 'list with some values': { 'topic': function () { return _restore( -- 2.34.1