| 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 | |
| };
 |