blob: f9c2f0d3377a3dea95c7390eee9db1e8a4a17c57 [file] [log] [blame]
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
};