michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #ifndef CSSCalc_h_ michael@0: #define CSSCalc_h_ michael@0: michael@0: #include "nsCSSValue.h" michael@0: #include "nsStyleCoord.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: michael@0: namespace css { michael@0: michael@0: /** michael@0: * ComputeCalc computes the result of a calc() expression tree. michael@0: * michael@0: * It is templatized over a CalcOps class that is expected to provide: michael@0: * michael@0: * // input_type and input_array_type have a bunch of very specific michael@0: * // expectations (which happen to be met by two classes (nsCSSValue michael@0: * // and nsStyleCoord). There must be methods (roughly): michael@0: * // input_array_type* input_type::GetArrayValue(); michael@0: * // uint32_t input_array_type::Count() const; michael@0: * // input_type& input_array_type::Item(uint32_t); michael@0: * typedef ... input_type; michael@0: * typedef ... input_array_type; michael@0: * michael@0: * typedef ... result_type; michael@0: * michael@0: * // GetUnit(avalue) must return the correct nsCSSUnit for any michael@0: * // value that represents a calc tree node (eCSSUnit_Calc*). For michael@0: * // other nodes, it may return any non eCSSUnit_Calc* unit. michael@0: * static nsCSSUnit GetUnit(const input_type& aValue); michael@0: * michael@0: * result_type michael@0: * MergeAdditive(nsCSSUnit aCalcFunction, michael@0: * result_type aValue1, result_type aValue2); michael@0: * michael@0: * result_type michael@0: * MergeMultiplicativeL(nsCSSUnit aCalcFunction, michael@0: * float aValue1, result_type aValue2); michael@0: * michael@0: * result_type michael@0: * MergeMultiplicativeR(nsCSSUnit aCalcFunction, michael@0: * result_type aValue1, float aValue2); michael@0: * michael@0: * result_type michael@0: * ComputeLeafValue(const input_type& aValue); michael@0: * michael@0: * float michael@0: * ComputeNumber(const input_type& aValue); michael@0: * michael@0: * The CalcOps methods might compute the calc() expression down to a michael@0: * number, reduce some parts of it to a number but replicate other michael@0: * parts, or produce a tree with a different data structure (for michael@0: * example, nsCSS* for specified values vs nsStyle* for computed michael@0: * values). michael@0: * michael@0: * For each leaf in the calc() expression, ComputeCalc will call either michael@0: * ComputeNumber (when the leaf is the left side of a Times_L or the michael@0: * right side of a Times_R or Divided) or ComputeLeafValue (otherwise). michael@0: * (The CalcOps in the CSS parser that reduces purely numeric michael@0: * expressions in turn calls ComputeCalc on numbers; other ops can michael@0: * presume that expressions in the number positions have already been michael@0: * normalized to a single numeric value and derive from michael@0: * NumbersAlreadyNormalizedCalcOps.) michael@0: * michael@0: * For non-leaves, one of the Merge functions will be called: michael@0: * MergeAdditive for Plus and Minus michael@0: * MergeMultiplicativeL for Times_L (number * value) michael@0: * MergeMultiplicativeR for Times_R (value * number) and Divided michael@0: */ michael@0: template michael@0: static typename CalcOps::result_type michael@0: ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps) michael@0: { michael@0: switch (CalcOps::GetUnit(aValue)) { michael@0: case eCSSUnit_Calc: { michael@0: typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(arr->Count() == 1, "unexpected length"); michael@0: return ComputeCalc(arr->Item(0), aOps); michael@0: } michael@0: case eCSSUnit_Calc_Plus: michael@0: case eCSSUnit_Calc_Minus: { michael@0: typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(arr->Count() == 2, "unexpected length"); michael@0: typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps), michael@0: rhs = ComputeCalc(arr->Item(1), aOps); michael@0: return aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs); michael@0: } michael@0: case eCSSUnit_Calc_Times_L: { michael@0: typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(arr->Count() == 2, "unexpected length"); michael@0: float lhs = aOps.ComputeNumber(arr->Item(0)); michael@0: typename CalcOps::result_type rhs = ComputeCalc(arr->Item(1), aOps); michael@0: return aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs); michael@0: } michael@0: case eCSSUnit_Calc_Times_R: michael@0: case eCSSUnit_Calc_Divided: { michael@0: typename CalcOps::input_array_type *arr = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(arr->Count() == 2, "unexpected length"); michael@0: typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps); michael@0: float rhs = aOps.ComputeNumber(arr->Item(1)); michael@0: return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs); michael@0: } michael@0: default: { michael@0: return aOps.ComputeLeafValue(aValue); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * The input unit operation for input_type being nsCSSValue. michael@0: */ michael@0: struct CSSValueInputCalcOps michael@0: { michael@0: typedef nsCSSValue input_type; michael@0: typedef nsCSSValue::Array input_array_type; michael@0: michael@0: static nsCSSUnit GetUnit(const nsCSSValue& aValue) michael@0: { michael@0: return aValue.GetUnit(); michael@0: } michael@0: michael@0: }; michael@0: michael@0: /** michael@0: * Basic*CalcOps provide a partial implementation of the CalcOps michael@0: * template parameter to ComputeCalc, for those callers whose merging michael@0: * just consists of mathematics (rather than tree construction). michael@0: */ michael@0: michael@0: struct BasicCoordCalcOps michael@0: { michael@0: typedef nscoord result_type; michael@0: michael@0: result_type michael@0: MergeAdditive(nsCSSUnit aCalcFunction, michael@0: result_type aValue1, result_type aValue2) michael@0: { michael@0: if (aCalcFunction == eCSSUnit_Calc_Plus) { michael@0: return NSCoordSaturatingAdd(aValue1, aValue2); michael@0: } michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Minus, michael@0: "unexpected unit"); michael@0: return NSCoordSaturatingSubtract(aValue1, aValue2, 0); michael@0: } michael@0: michael@0: result_type michael@0: MergeMultiplicativeL(nsCSSUnit aCalcFunction, michael@0: float aValue1, result_type aValue2) michael@0: { michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_L, michael@0: "unexpected unit"); michael@0: return NSCoordSaturatingMultiply(aValue2, aValue1); michael@0: } michael@0: michael@0: result_type michael@0: MergeMultiplicativeR(nsCSSUnit aCalcFunction, michael@0: result_type aValue1, float aValue2) michael@0: { michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_R || michael@0: aCalcFunction == eCSSUnit_Calc_Divided, michael@0: "unexpected unit"); michael@0: if (aCalcFunction == eCSSUnit_Calc_Divided) { michael@0: aValue2 = 1.0f / aValue2; michael@0: } michael@0: return NSCoordSaturatingMultiply(aValue1, aValue2); michael@0: } michael@0: }; michael@0: michael@0: struct BasicFloatCalcOps michael@0: { michael@0: typedef float result_type; michael@0: michael@0: result_type michael@0: MergeAdditive(nsCSSUnit aCalcFunction, michael@0: result_type aValue1, result_type aValue2) michael@0: { michael@0: if (aCalcFunction == eCSSUnit_Calc_Plus) { michael@0: return aValue1 + aValue2; michael@0: } michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Minus, michael@0: "unexpected unit"); michael@0: return aValue1 - aValue2; michael@0: } michael@0: michael@0: result_type michael@0: MergeMultiplicativeL(nsCSSUnit aCalcFunction, michael@0: float aValue1, result_type aValue2) michael@0: { michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_L, michael@0: "unexpected unit"); michael@0: return aValue1 * aValue2; michael@0: } michael@0: michael@0: result_type michael@0: MergeMultiplicativeR(nsCSSUnit aCalcFunction, michael@0: result_type aValue1, float aValue2) michael@0: { michael@0: if (aCalcFunction == eCSSUnit_Calc_Times_R) { michael@0: return aValue1 * aValue2; michael@0: } michael@0: NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Divided, michael@0: "unexpected unit"); michael@0: return aValue1 / aValue2; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A ComputeNumber implementation for callers that can assume numbers michael@0: * are already normalized (i.e., anything past the parser). michael@0: */ michael@0: struct NumbersAlreadyNormalizedOps : public CSSValueInputCalcOps michael@0: { michael@0: float ComputeNumber(const nsCSSValue& aValue) michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit"); michael@0: return aValue.GetFloatValue(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * SerializeCalc appends the serialization of aValue to a string. michael@0: * michael@0: * It is templatized over a CalcOps class that is expected to provide: michael@0: * michael@0: * // input_type and input_array_type have a bunch of very specific michael@0: * // expectations (which happen to be met by two classes (nsCSSValue michael@0: * // and nsStyleCoord). There must be methods (roughly): michael@0: * // input_array_type* input_type::GetArrayValue(); michael@0: * // uint32_t input_array_type::Count() const; michael@0: * // input_type& input_array_type::Item(uint32_t); michael@0: * typedef ... input_type; michael@0: * typedef ... input_array_type; michael@0: * michael@0: * static nsCSSUnit GetUnit(const input_type& aValue); michael@0: * michael@0: * void Append(const char* aString); michael@0: * void AppendLeafValue(const input_type& aValue); michael@0: * void AppendNumber(const input_type& aValue); michael@0: * michael@0: * Data structures given may or may not have a toplevel eCSSUnit_Calc michael@0: * node representing a calc whose toplevel is not min() or max(). michael@0: */ michael@0: michael@0: template michael@0: static void michael@0: SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps); michael@0: michael@0: // Serialize the toplevel value in a calc() tree. See big comment michael@0: // above. michael@0: template michael@0: static void michael@0: SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps) michael@0: { michael@0: aOps.Append("calc("); michael@0: nsCSSUnit unit = CalcOps::GetUnit(aValue); michael@0: if (unit == eCSSUnit_Calc) { michael@0: const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(array->Count() == 1, "unexpected length"); michael@0: SerializeCalcInternal(array->Item(0), aOps); michael@0: } else { michael@0: SerializeCalcInternal(aValue, aOps); michael@0: } michael@0: aOps.Append(")"); michael@0: } michael@0: michael@0: static inline bool michael@0: IsCalcAdditiveUnit(nsCSSUnit aUnit) michael@0: { michael@0: return aUnit == eCSSUnit_Calc_Plus || michael@0: aUnit == eCSSUnit_Calc_Minus; michael@0: } michael@0: michael@0: static inline bool michael@0: IsCalcMultiplicativeUnit(nsCSSUnit aUnit) michael@0: { michael@0: return aUnit == eCSSUnit_Calc_Times_L || michael@0: aUnit == eCSSUnit_Calc_Times_R || michael@0: aUnit == eCSSUnit_Calc_Divided; michael@0: } michael@0: michael@0: // Serialize a non-toplevel value in a calc() tree. See big comment michael@0: // above. michael@0: template michael@0: /* static */ void michael@0: SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps) michael@0: { michael@0: nsCSSUnit unit = CalcOps::GetUnit(aValue); michael@0: if (IsCalcAdditiveUnit(unit)) { michael@0: const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(array->Count() == 2, "unexpected length"); michael@0: michael@0: SerializeCalcInternal(array->Item(0), aOps); michael@0: michael@0: if (eCSSUnit_Calc_Plus == unit) { michael@0: aOps.Append(" + "); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(eCSSUnit_Calc_Minus == unit, "unexpected unit"); michael@0: aOps.Append(" - "); michael@0: } michael@0: michael@0: bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(1))); michael@0: if (needParens) { michael@0: aOps.Append("("); michael@0: } michael@0: SerializeCalcInternal(array->Item(1), aOps); michael@0: if (needParens) { michael@0: aOps.Append(")"); michael@0: } michael@0: } else if (IsCalcMultiplicativeUnit(unit)) { michael@0: const typename CalcOps::input_array_type *array = aValue.GetArrayValue(); michael@0: NS_ABORT_IF_FALSE(array->Count() == 2, "unexpected length"); michael@0: michael@0: bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(0))); michael@0: if (needParens) { michael@0: aOps.Append("("); michael@0: } michael@0: if (unit == eCSSUnit_Calc_Times_L) { michael@0: aOps.AppendNumber(array->Item(0)); michael@0: } else { michael@0: SerializeCalcInternal(array->Item(0), aOps); michael@0: } michael@0: if (needParens) { michael@0: aOps.Append(")"); michael@0: } michael@0: michael@0: if (eCSSUnit_Calc_Times_L == unit || eCSSUnit_Calc_Times_R == unit) { michael@0: aOps.Append(" * "); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(eCSSUnit_Calc_Divided == unit, "unexpected unit"); michael@0: aOps.Append(" / "); michael@0: } michael@0: michael@0: nsCSSUnit subUnit = CalcOps::GetUnit(array->Item(1)); michael@0: needParens = IsCalcAdditiveUnit(subUnit) || michael@0: IsCalcMultiplicativeUnit(subUnit); michael@0: if (needParens) { michael@0: aOps.Append("("); michael@0: } michael@0: if (unit == eCSSUnit_Calc_Times_L) { michael@0: SerializeCalcInternal(array->Item(1), aOps); michael@0: } else { michael@0: aOps.AppendNumber(array->Item(1)); michael@0: } michael@0: if (needParens) { michael@0: aOps.Append(")"); michael@0: } michael@0: } else { michael@0: aOps.AppendLeafValue(aValue); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: } michael@0: michael@0: #endif /* !defined(CSSCalc_h_) */