| 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, compactable, 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 = compactable[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 || compactable[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, compactable[property.name].defaultValue]); |
| |
| if (isInheritOnly(restored)) |
| return [restored[0]]; |
| |
| return restored; |
| } |
| |
| function borderRadius(property, compactable) { |
| 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, compactable); |
| var verticalValues = fourValues(vertical, compactable); |
| |
| 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, compactable); |
| } |
| } |
| |
| function font(property, compactable) { |
| 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] != compactable[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] != compactable[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, compactable) { |
| if (!property.multiplex) |
| return restoreWith(property, compactable, 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, compactable, lastInMultiplex); |
| Array.prototype.push.apply(restored, _restored); |
| |
| if (i < multiplexSize) |
| restored.push([Token.PROPERTY_VALUE, Marker.COMMA]); |
| } |
| |
| return restored; |
| }; |
| } |
| |
| function withoutDefaults(property, compactable) { |
| var components = property.components; |
| var restored = []; |
| |
| for (var i = components.length - 1; i >= 0; i--) { |
| var component = components[i]; |
| var descriptor = compactable[component.name]; |
| |
| if (component.value[0][1] != descriptor.defaultValue || ('keepUnlessDefault' in descriptor) && !isDefault(components, compactable, descriptor.keepUnlessDefault)) { |
| restored.unshift(component.value[0]); |
| } |
| } |
| |
| if (restored.length === 0) |
| restored.push([Token.PROPERTY_VALUE, compactable[property.name].defaultValue]); |
| |
| if (isInheritOnly(restored)) |
| return [restored[0]]; |
| |
| return restored; |
| } |
| |
| function isDefault(components, compactable, 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] == compactable[propertyName].defaultValue) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| module.exports = { |
| background: background, |
| borderRadius: borderRadius, |
| font: font, |
| fourValues: fourValues, |
| multiplex: multiplex, |
| withoutDefaults: withoutDefaults |
| }; |