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