1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef CSSCalc_h_
6 #define CSSCalc_h_
7 
8 #include "nsCSSValue.h"
9 #include "nsStyleCoord.h"
10 #include <math.h>
11 
12 namespace mozilla {
13 
14 namespace css {
15 
16 /**
17  * ComputeCalc computes the result of a calc() expression tree.
18  *
19  * It is templatized over a CalcOps class that is expected to provide:
20  *
21  *   // input_type and input_array_type have a bunch of very specific
22  *   // expectations (which happen to be met by two classes (nsCSSValue
23  *   // and nsStyleCoord).  There must be methods (roughly):
24  *   //   input_array_type* input_type::GetArrayValue();
25  *   //   uint32_t input_array_type::Count() const;
26  *   //   input_type& input_array_type::Item(uint32_t);
27  *   typedef ... input_type;
28  *   typedef ... input_array_type;
29  *
30  *   typedef ... result_type;
31  *
32  *   // GetUnit(avalue) must return the correct nsCSSUnit for any
33  *   // value that represents a calc tree node (eCSSUnit_Calc*).  For
34  *   // other nodes, it may return any non eCSSUnit_Calc* unit.
35  *   static nsCSSUnit GetUnit(const input_type& aValue);
36  *
37  *   result_type
38  *   MergeAdditive(nsCSSUnit aCalcFunction,
39  *                 result_type aValue1, result_type aValue2);
40  *
41  *   result_type
42  *   MergeMultiplicativeL(nsCSSUnit aCalcFunction,
43  *                        float aValue1, result_type aValue2);
44  *
45  *   result_type
46  *   MergeMultiplicativeR(nsCSSUnit aCalcFunction,
47  *                        result_type aValue1, float aValue2);
48  *
49  *   result_type
50  *   ComputeLeafValue(const input_type& aValue);
51  *
52  *   float
53  *   ComputeNumber(const input_type& aValue);
54  *
55  * The CalcOps methods might compute the calc() expression down to a
56  * number, reduce some parts of it to a number but replicate other
57  * parts, or produce a tree with a different data structure (for
58  * example, nsCSS* for specified values vs nsStyle* for computed
59  * values).
60  *
61  * For each leaf in the calc() expression, ComputeCalc will call either
62  * ComputeNumber (when the leaf is the left side of a Times_L or the
63  * right side of a Times_R or Divided) or ComputeLeafValue (otherwise).
64  * (The CalcOps in the CSS parser that reduces purely numeric
65  * expressions in turn calls ComputeCalc on numbers; other ops can
66  * presume that expressions in the number positions have already been
67  * normalized to a single numeric value and derive from
68  * NumbersAlreadyNormalizedCalcOps.)
69  *
70  * For non-leaves, one of the Merge functions will be called:
71  *   MergeAdditive for Plus and Minus
72  *   MergeMultiplicativeL for Times_L (number * value)
73  *   MergeMultiplicativeR for Times_R (value * number) and Divided
74  */
75 template <class CalcOps>
76 static typename CalcOps::result_type
ComputeCalc(const typename CalcOps::input_type & aValue,CalcOps & aOps)77 ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
78 {
79   switch (CalcOps::GetUnit(aValue)) {
80     case eCSSUnit_Calc: {
81       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
82       MOZ_ASSERT(arr->Count() == 1, "unexpected length");
83       return ComputeCalc(arr->Item(0), aOps);
84     }
85     case eCSSUnit_Calc_Plus:
86     case eCSSUnit_Calc_Minus: {
87       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
88       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
89       typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps),
90                                     rhs = ComputeCalc(arr->Item(1), aOps);
91       return aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs);
92     }
93     case eCSSUnit_Calc_Times_L: {
94       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
95       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
96       float lhs = aOps.ComputeNumber(arr->Item(0));
97       typename CalcOps::result_type rhs = ComputeCalc(arr->Item(1), aOps);
98       return aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs);
99     }
100     case eCSSUnit_Calc_Times_R:
101     case eCSSUnit_Calc_Divided: {
102       typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
103       MOZ_ASSERT(arr->Count() == 2, "unexpected length");
104       typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps);
105       float rhs = aOps.ComputeNumber(arr->Item(1));
106       return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
107     }
108     default: {
109       return aOps.ComputeLeafValue(aValue);
110     }
111   }
112 }
113 
114 /**
115  * The input unit operation for input_type being nsCSSValue.
116  */
117 struct CSSValueInputCalcOps
118 {
119   typedef nsCSSValue input_type;
120   typedef nsCSSValue::Array input_array_type;
121 
GetUnitCSSValueInputCalcOps122   static nsCSSUnit GetUnit(const nsCSSValue& aValue)
123   {
124     return aValue.GetUnit();
125   }
126 
127 };
128 
129 /**
130  * Basic*CalcOps provide a partial implementation of the CalcOps
131  * template parameter to ComputeCalc, for those callers whose merging
132  * just consists of mathematics (rather than tree construction).
133  */
134 
135 struct BasicCoordCalcOps
136 {
137   typedef nscoord result_type;
138 
139   result_type
MergeAdditiveBasicCoordCalcOps140   MergeAdditive(nsCSSUnit aCalcFunction,
141                 result_type aValue1, result_type aValue2)
142   {
143     if (aCalcFunction == eCSSUnit_Calc_Plus) {
144       return NSCoordSaturatingAdd(aValue1, aValue2);
145     }
146     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
147                "unexpected unit");
148     return NSCoordSaturatingSubtract(aValue1, aValue2, 0);
149   }
150 
151   result_type
MergeMultiplicativeLBasicCoordCalcOps152   MergeMultiplicativeL(nsCSSUnit aCalcFunction,
153                        float aValue1, result_type aValue2)
154   {
155     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
156                "unexpected unit");
157     return NSCoordSaturatingMultiply(aValue2, aValue1);
158   }
159 
160   result_type
MergeMultiplicativeRBasicCoordCalcOps161   MergeMultiplicativeR(nsCSSUnit aCalcFunction,
162                        result_type aValue1, float aValue2)
163   {
164     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
165                aCalcFunction == eCSSUnit_Calc_Divided,
166                "unexpected unit");
167     if (aCalcFunction == eCSSUnit_Calc_Divided) {
168       aValue2 = 1.0f / aValue2;
169     }
170     return NSCoordSaturatingMultiply(aValue1, aValue2);
171   }
172 };
173 
174 struct BasicFloatCalcOps
175 {
176   typedef float result_type;
177 
178   result_type
MergeAdditiveBasicFloatCalcOps179   MergeAdditive(nsCSSUnit aCalcFunction,
180                 result_type aValue1, result_type aValue2)
181   {
182     if (aCalcFunction == eCSSUnit_Calc_Plus) {
183       return aValue1 + aValue2;
184     }
185     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
186                "unexpected unit");
187     return aValue1 - aValue2;
188   }
189 
190   result_type
MergeMultiplicativeLBasicFloatCalcOps191   MergeMultiplicativeL(nsCSSUnit aCalcFunction,
192                        float aValue1, result_type aValue2)
193   {
194     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
195                "unexpected unit");
196     return aValue1 * aValue2;
197   }
198 
199   result_type
MergeMultiplicativeRBasicFloatCalcOps200   MergeMultiplicativeR(nsCSSUnit aCalcFunction,
201                        result_type aValue1, float aValue2)
202   {
203     if (aCalcFunction == eCSSUnit_Calc_Times_R) {
204       return aValue1 * aValue2;
205     }
206     MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
207                "unexpected unit");
208     return aValue1 / aValue2;
209   }
210 };
211 
212 /**
213  * A ComputeNumber implementation for callers that can assume numbers
214  * are already normalized (i.e., anything past the parser).
215  */
216 struct NumbersAlreadyNormalizedOps : public CSSValueInputCalcOps
217 {
ComputeNumberNumbersAlreadyNormalizedOps218   float ComputeNumber(const nsCSSValue& aValue)
219   {
220     MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
221     return aValue.GetFloatValue();
222   }
223 };
224 
225 /**
226  * SerializeCalc appends the serialization of aValue to a string.
227  *
228  * It is templatized over a CalcOps class that is expected to provide:
229  *
230  *   // input_type and input_array_type have a bunch of very specific
231  *   // expectations (which happen to be met by two classes (nsCSSValue
232  *   // and nsStyleCoord).  There must be methods (roughly):
233  *   //   input_array_type* input_type::GetArrayValue();
234  *   //   uint32_t input_array_type::Count() const;
235  *   //   input_type& input_array_type::Item(uint32_t);
236  *   typedef ... input_type;
237  *   typedef ... input_array_type;
238  *
239  *   static nsCSSUnit GetUnit(const input_type& aValue);
240  *
241  *   void Append(const char* aString);
242  *   void AppendLeafValue(const input_type& aValue);
243  *   void AppendNumber(const input_type& aValue);
244  *
245  * Data structures given may or may not have a toplevel eCSSUnit_Calc
246  * node representing a calc whose toplevel is not min() or max().
247  */
248 
249 template <class CalcOps>
250 static void
251 SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps);
252 
253 // Serialize the toplevel value in a calc() tree.  See big comment
254 // above.
255 template <class CalcOps>
256 static void
SerializeCalc(const typename CalcOps::input_type & aValue,CalcOps & aOps)257 SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
258 {
259   aOps.Append("calc(");
260   nsCSSUnit unit = CalcOps::GetUnit(aValue);
261   if (unit == eCSSUnit_Calc) {
262     const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
263     MOZ_ASSERT(array->Count() == 1, "unexpected length");
264     SerializeCalcInternal(array->Item(0), aOps);
265   } else {
266     SerializeCalcInternal(aValue, aOps);
267   }
268   aOps.Append(")");
269 }
270 
271 static inline bool
IsCalcAdditiveUnit(nsCSSUnit aUnit)272 IsCalcAdditiveUnit(nsCSSUnit aUnit)
273 {
274   return aUnit == eCSSUnit_Calc_Plus ||
275          aUnit == eCSSUnit_Calc_Minus;
276 }
277 
278 static inline bool
IsCalcMultiplicativeUnit(nsCSSUnit aUnit)279 IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
280 {
281   return aUnit == eCSSUnit_Calc_Times_L ||
282          aUnit == eCSSUnit_Calc_Times_R ||
283          aUnit == eCSSUnit_Calc_Divided;
284 }
285 
286 // Serialize a non-toplevel value in a calc() tree.  See big comment
287 // above.
288 template <class CalcOps>
289 /* static */ void
SerializeCalcInternal(const typename CalcOps::input_type & aValue,CalcOps & aOps)290 SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
291 {
292   nsCSSUnit unit = CalcOps::GetUnit(aValue);
293   if (IsCalcAdditiveUnit(unit)) {
294     const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
295     MOZ_ASSERT(array->Count() == 2, "unexpected length");
296 
297     SerializeCalcInternal(array->Item(0), aOps);
298 
299     if (eCSSUnit_Calc_Plus == unit) {
300       aOps.Append(" + ");
301     } else {
302       MOZ_ASSERT(eCSSUnit_Calc_Minus == unit, "unexpected unit");
303       aOps.Append(" - ");
304     }
305 
306     bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(1)));
307     if (needParens) {
308       aOps.Append("(");
309     }
310     SerializeCalcInternal(array->Item(1), aOps);
311     if (needParens) {
312       aOps.Append(")");
313     }
314   } else if (IsCalcMultiplicativeUnit(unit)) {
315     const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
316     MOZ_ASSERT(array->Count() == 2, "unexpected length");
317 
318     bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(0)));
319     if (needParens) {
320       aOps.Append("(");
321     }
322     if (unit == eCSSUnit_Calc_Times_L) {
323       aOps.AppendNumber(array->Item(0));
324     } else {
325       SerializeCalcInternal(array->Item(0), aOps);
326     }
327     if (needParens) {
328       aOps.Append(")");
329     }
330 
331     if (eCSSUnit_Calc_Times_L == unit || eCSSUnit_Calc_Times_R == unit) {
332       aOps.Append(" * ");
333     } else {
334       MOZ_ASSERT(eCSSUnit_Calc_Divided == unit, "unexpected unit");
335       aOps.Append(" / ");
336     }
337 
338     nsCSSUnit subUnit = CalcOps::GetUnit(array->Item(1));
339     needParens = IsCalcAdditiveUnit(subUnit) ||
340                  IsCalcMultiplicativeUnit(subUnit);
341     if (needParens) {
342       aOps.Append("(");
343     }
344     if (unit == eCSSUnit_Calc_Times_L) {
345       SerializeCalcInternal(array->Item(1), aOps);
346     } else {
347       aOps.AppendNumber(array->Item(1));
348     }
349     if (needParens) {
350       aOps.Append(")");
351     }
352   } else {
353     aOps.AppendLeafValue(aValue);
354   }
355 }
356 
357 } // namespace css
358 
359 } // namespace mozilla
360 
361 #endif /* !defined(CSSCalc_h_) */
362