var shallowClone = require('../clone').shallow; var Token = require('../../tokenizer/token'); var Marker = require('../../tokenizer/marker'); function isInheritOnly(values) { for (var i = 0, l = values.length; i < l; i++) { var value = values[i][1]; if (value != 'inherit' && value != Marker.COMMA && value != Marker.FORWARD_SLASH) return false; } return true; } function background(property, configuration, lastInMultiplex) { var components = property.components; var restored = []; var needsOne, needsBoth; function restoreValue(component) { Array.prototype.unshift.apply(restored, component.value); } function isDefaultValue(component) { var descriptor = configuration[component.name]; if (descriptor.doubleValues && descriptor.defaultValue.length == 1) { return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] == descriptor.defaultValue[0] : true); } else if (descriptor.doubleValues && descriptor.defaultValue.length != 1) { return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] : component.value[0][1]) == descriptor.defaultValue[1]; } else { return component.value[0][1] == descriptor.defaultValue; } } for (var i = components.length - 1; i >= 0; i--) { var component = components[i]; var isDefault = isDefaultValue(component); if (component.name == 'background-clip') { var originComponent = components[i - 1]; var isOriginDefault = isDefaultValue(originComponent); needsOne = component.value[0][1] == originComponent.value[0][1]; needsBoth = !needsOne && ( (isOriginDefault && !isDefault) || (!isOriginDefault && !isDefault) || (!isOriginDefault && isDefault && component.value[0][1] != originComponent.value[0][1])); if (needsOne) { restoreValue(originComponent); } else if (needsBoth) { restoreValue(component); restoreValue(originComponent); } i--; } else if (component.name == 'background-size') { var positionComponent = components[i - 1]; var isPositionDefault = isDefaultValue(positionComponent); needsOne = !isPositionDefault && isDefault; needsBoth = !needsOne && (isPositionDefault && !isDefault || !isPositionDefault && !isDefault); if (needsOne) { restoreValue(positionComponent); } else if (needsBoth) { restoreValue(component); restored.unshift([Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]); restoreValue(positionComponent); } else if (positionComponent.value.length == 1) { restoreValue(positionComponent); } i--; } else { if (isDefault || configuration[component.name].multiplexLastOnly && !lastInMultiplex) continue; restoreValue(component); } } if (restored.length === 0 && property.value.length == 1 && property.value[0][1] == '0') restored.push(property.value[0]); if (restored.length === 0) restored.push([Token.PROPERTY_VALUE, configuration[property.name].defaultValue]); if (isInheritOnly(restored)) return [restored[0]]; return restored; } function borderRadius(property) { if (property.multiplex) { var horizontal = shallowClone(property); var vertical = shallowClone(property); for (var i = 0; i < 4; i++) { var component = property.components[i]; var horizontalComponent = shallowClone(property); horizontalComponent.value = [component.value[0]]; horizontal.components.push(horizontalComponent); var verticalComponent = shallowClone(property); // FIXME: only shorthand compactor (see breakup#borderRadius) knows that border radius // longhands have two values, whereas tokenizer does not care about populating 2nd value // if it's missing, hence this fallback verticalComponent.value = [component.value[1] || component.value[0]]; vertical.components.push(verticalComponent); } var horizontalValues = fourValues(horizontal); var verticalValues = fourValues(vertical); if (horizontalValues.length == verticalValues.length && horizontalValues[0][1] == verticalValues[0][1] && (horizontalValues.length > 1 ? horizontalValues[1][1] == verticalValues[1][1] : true) && (horizontalValues.length > 2 ? horizontalValues[2][1] == verticalValues[2][1] : true) && (horizontalValues.length > 3 ? horizontalValues[3][1] == verticalValues[3][1] : true)) { return horizontalValues; } else { return horizontalValues.concat([[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]).concat(verticalValues); } } else { return fourValues(property); } } function font(property, configuration) { var components = property.components; var restored = []; var component; 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]; if (component.value[0][1] != configuration[component.name].defaultValue) { Array.prototype.push.apply(restored, component.value); } componentIndex++; } // then comes font-size Array.prototype.push.apply(restored, components[componentIndex].value); componentIndex++; // then may come line-height if (components[componentIndex].value[0][1] != configuration[components[componentIndex].name].defaultValue) { Array.prototype.push.apply(restored, [[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]); Array.prototype.push.apply(restored, components[componentIndex].value); } componentIndex++; // then comes font-family while (components[componentIndex].value[fontFamilyIndex]) { restored.push(components[componentIndex].value[fontFamilyIndex]); if (components[componentIndex].value[fontFamilyIndex + 1]) { restored.push([Token.PROPERTY_VALUE, Marker.COMMA]); } fontFamilyIndex++; } if (isInheritOnly(restored)) { return [restored[0]]; } return restored; } function fourValues(property) { var components = property.components; var value1 = components[0].value[0]; var value2 = components[1].value[0]; var value3 = components[2].value[0]; var value4 = components[3].value[0]; if (value1[1] == value2[1] && value1[1] == value3[1] && value1[1] == value4[1]) { return [value1]; } else if (value1[1] == value3[1] && value2[1] == value4[1]) { return [value1, value2]; } else if (value2[1] == value4[1]) { return [value1, value2, value3]; } else { return [value1, value2, value3, value4]; } } function multiplex(restoreWith) { return function (property, configuration) { if (!property.multiplex) return restoreWith(property, configuration, true); var multiplexSize = 0; var restored = []; var componentMultiplexSoFar = {}; var i, l; // At this point we don't know what's the multiplex size, e.g. how many background layers are there for (i = 0, l = property.components[0].value.length; i < l; i++) { if (property.components[0].value[i][1] == Marker.COMMA) multiplexSize++; } for (i = 0; i <= multiplexSize; i++) { var _property = shallowClone(property); // We split multiplex into parts and restore them one by one for (var j = 0, m = property.components.length; j < m; j++) { var componentToClone = property.components[j]; var _component = shallowClone(componentToClone); _property.components.push(_component); // The trick is some properties has more than one value, so we iterate over values looking for // a multiplex separator - a comma for (var k = componentMultiplexSoFar[_component.name] || 0, n = componentToClone.value.length; k < n; k++) { if (componentToClone.value[k][1] == Marker.COMMA) { componentMultiplexSoFar[_component.name] = k + 1; break; } _component.value.push(componentToClone.value[k]); } } // No we can restore shorthand value var lastInMultiplex = i == multiplexSize; var _restored = restoreWith(_property, configuration, lastInMultiplex); Array.prototype.push.apply(restored, _restored); if (i < multiplexSize) restored.push([Token.PROPERTY_VALUE, Marker.COMMA]); } return restored; }; } function withoutDefaults(property, configuration) { var components = property.components; var restored = []; for (var i = components.length - 1; i >= 0; i--) { var component = components[i]; var descriptor = configuration[component.name]; if (component.value[0][1] != descriptor.defaultValue || ('keepUnlessDefault' in descriptor) && !isDefault(components, configuration, descriptor.keepUnlessDefault)) { restored.unshift(component.value[0]); } } if (restored.length === 0) restored.push([Token.PROPERTY_VALUE, configuration[property.name].defaultValue]); if (isInheritOnly(restored)) return [restored[0]]; return restored; } function isDefault(components, configuration, propertyName) { var component; var i, l; for (i = 0, l = components.length; i < l; i++) { component = components[i]; if (component.name == propertyName && component.value[0][1] == configuration[propertyName].defaultValue) { return true; } } return false; } module.exports = { background: background, borderRadius: borderRadius, font: font, fourValues: fourValues, multiplex: multiplex, withoutDefaults: withoutDefaults };