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