Line data Source code
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 ... coeff_type;
31 : *
32 : * typedef ... result_type;
33 : *
34 : * // GetUnit(avalue) must return the correct nsCSSUnit for any
35 : * // value that represents a calc tree node (eCSSUnit_Calc*). For
36 : * // other nodes, it may return any non eCSSUnit_Calc* unit.
37 : * static nsCSSUnit GetUnit(const input_type& aValue);
38 : *
39 : * result_type
40 : * MergeAdditive(nsCSSUnit aCalcFunction,
41 : * result_type aValue1, result_type aValue2);
42 : *
43 : * result_type
44 : * MergeMultiplicativeL(nsCSSUnit aCalcFunction,
45 : * coeff_type aValue1, result_type aValue2);
46 : *
47 : * result_type
48 : * MergeMultiplicativeR(nsCSSUnit aCalcFunction,
49 : * result_type aValue1, coeff_type aValue2);
50 : *
51 : * result_type
52 : * ComputeLeafValue(const input_type& aValue);
53 : *
54 : * coeff_type
55 : * ComputeCoefficient(const coeff_type& aValue);
56 : *
57 : * The CalcOps methods might compute the calc() expression down to a
58 : * number, reduce some parts of it to a number but replicate other
59 : * parts, or produce a tree with a different data structure (for
60 : * example, nsCSS* for specified values vs nsStyle* for computed
61 : * values).
62 : *
63 : * For each leaf in the calc() expression, ComputeCalc will call either
64 : * ComputeCoefficient (when the leaf is the left side of a Times_L or the
65 : * right side of a Times_R or Divided) or ComputeLeafValue (otherwise).
66 : * (The CalcOps in the CSS parser that reduces purely numeric
67 : * expressions in turn calls ComputeCalc on numbers; other ops can
68 : * presume that expressions in the coefficient positions have already been
69 : * normalized to a single numeric value and derive from, if their coefficient
70 : * types are floats, FloatCoeffsAlreadyNormalizedCalcOps.)
71 : *
72 : * coeff_type will be float most of the time, but it's templatized so that
73 : * ParseCalc can be used with <integer>s too.
74 : *
75 : * For non-leaves, one of the Merge functions will be called:
76 : * MergeAdditive for Plus and Minus
77 : * MergeMultiplicativeL for Times_L (coeff * value)
78 : * MergeMultiplicativeR for Times_R (value * coeff) and Divided
79 : */
80 : template <class CalcOps>
81 : static typename CalcOps::result_type
82 774 : ComputeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
83 : {
84 774 : switch (CalcOps::GetUnit(aValue)) {
85 : case eCSSUnit_Calc: {
86 127 : typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
87 127 : MOZ_ASSERT(arr->Count() == 1, "unexpected length");
88 127 : return ComputeCalc(arr->Item(0), aOps);
89 : }
90 : case eCSSUnit_Calc_Plus:
91 : case eCSSUnit_Calc_Minus: {
92 93 : typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
93 93 : MOZ_ASSERT(arr->Count() == 2, "unexpected length");
94 93 : typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps),
95 93 : rhs = ComputeCalc(arr->Item(1), aOps);
96 93 : return aOps.MergeAdditive(CalcOps::GetUnit(aValue), lhs, rhs);
97 : }
98 : case eCSSUnit_Calc_Times_L: {
99 88 : typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
100 88 : MOZ_ASSERT(arr->Count() == 2, "unexpected length");
101 88 : typename CalcOps::coeff_type lhs = aOps.ComputeCoefficient(arr->Item(0));
102 88 : typename CalcOps::result_type rhs = ComputeCalc(arr->Item(1), aOps);
103 88 : return aOps.MergeMultiplicativeL(CalcOps::GetUnit(aValue), lhs, rhs);
104 : }
105 : case eCSSUnit_Calc_Times_R:
106 : case eCSSUnit_Calc_Divided: {
107 13 : typename CalcOps::input_array_type *arr = aValue.GetArrayValue();
108 13 : MOZ_ASSERT(arr->Count() == 2, "unexpected length");
109 13 : typename CalcOps::result_type lhs = ComputeCalc(arr->Item(0), aOps);
110 13 : typename CalcOps::coeff_type rhs = aOps.ComputeCoefficient(arr->Item(1));
111 13 : return aOps.MergeMultiplicativeR(CalcOps::GetUnit(aValue), lhs, rhs);
112 : }
113 : default: {
114 453 : return aOps.ComputeLeafValue(aValue);
115 : }
116 : }
117 : }
118 :
119 : /**
120 : * The input unit operation for input_type being nsCSSValue.
121 : */
122 127 : struct CSSValueInputCalcOps
123 : {
124 : typedef nsCSSValue input_type;
125 : typedef nsCSSValue::Array input_array_type;
126 :
127 968 : static nsCSSUnit GetUnit(const nsCSSValue& aValue)
128 : {
129 968 : return aValue.GetUnit();
130 : }
131 :
132 : };
133 :
134 : /**
135 : * Basic*CalcOps provide a partial implementation of the CalcOps
136 : * template parameter to ComputeCalc, for those callers whose merging
137 : * just consists of mathematics (rather than tree construction).
138 : */
139 :
140 0 : struct BasicCoordCalcOps
141 : {
142 : typedef nscoord result_type;
143 : typedef float coeff_type;
144 :
145 : result_type
146 0 : MergeAdditive(nsCSSUnit aCalcFunction,
147 : result_type aValue1, result_type aValue2)
148 : {
149 0 : if (aCalcFunction == eCSSUnit_Calc_Plus) {
150 0 : return NSCoordSaturatingAdd(aValue1, aValue2);
151 : }
152 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
153 : "unexpected unit");
154 0 : return NSCoordSaturatingSubtract(aValue1, aValue2, 0);
155 : }
156 :
157 : result_type
158 0 : MergeMultiplicativeL(nsCSSUnit aCalcFunction,
159 : coeff_type aValue1, result_type aValue2)
160 : {
161 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
162 : "unexpected unit");
163 0 : return NSCoordSaturatingMultiply(aValue2, aValue1);
164 : }
165 :
166 : result_type
167 0 : MergeMultiplicativeR(nsCSSUnit aCalcFunction,
168 : result_type aValue1, coeff_type aValue2)
169 : {
170 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
171 : aCalcFunction == eCSSUnit_Calc_Divided,
172 : "unexpected unit");
173 0 : if (aCalcFunction == eCSSUnit_Calc_Divided) {
174 0 : aValue2 = 1.0f / aValue2;
175 : }
176 0 : return NSCoordSaturatingMultiply(aValue1, aValue2);
177 : }
178 : };
179 :
180 : struct BasicFloatCalcOps
181 : {
182 : typedef float result_type;
183 : typedef float coeff_type;
184 :
185 : result_type
186 0 : MergeAdditive(nsCSSUnit aCalcFunction,
187 : result_type aValue1, result_type aValue2)
188 : {
189 0 : if (aCalcFunction == eCSSUnit_Calc_Plus) {
190 0 : return aValue1 + aValue2;
191 : }
192 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
193 : "unexpected unit");
194 0 : return aValue1 - aValue2;
195 : }
196 :
197 : result_type
198 0 : MergeMultiplicativeL(nsCSSUnit aCalcFunction,
199 : coeff_type aValue1, result_type aValue2)
200 : {
201 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
202 : "unexpected unit");
203 0 : return aValue1 * aValue2;
204 : }
205 :
206 : result_type
207 0 : MergeMultiplicativeR(nsCSSUnit aCalcFunction,
208 : result_type aValue1, coeff_type aValue2)
209 : {
210 0 : if (aCalcFunction == eCSSUnit_Calc_Times_R) {
211 0 : return aValue1 * aValue2;
212 : }
213 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
214 : "unexpected unit");
215 0 : return aValue1 / aValue2;
216 : }
217 : };
218 :
219 : struct BasicIntegerCalcOps
220 : {
221 : typedef int result_type;
222 : typedef int coeff_type;
223 :
224 : result_type
225 0 : MergeAdditive(nsCSSUnit aCalcFunction,
226 : result_type aValue1, result_type aValue2)
227 : {
228 0 : if (aCalcFunction == eCSSUnit_Calc_Plus) {
229 0 : return aValue1 + aValue2;
230 : }
231 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
232 : "unexpected unit");
233 0 : return aValue1 - aValue2;
234 : }
235 :
236 : result_type
237 0 : MergeMultiplicativeL(nsCSSUnit aCalcFunction,
238 : coeff_type aValue1, result_type aValue2)
239 : {
240 0 : MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
241 : "unexpected unit");
242 0 : return aValue1 * aValue2;
243 : }
244 :
245 : result_type
246 0 : MergeMultiplicativeR(nsCSSUnit aCalcFunction,
247 : result_type aValue1, coeff_type aValue2)
248 : {
249 0 : if (aCalcFunction == eCSSUnit_Calc_Times_R) {
250 0 : return aValue1 * aValue2;
251 : }
252 0 : MOZ_ASSERT_UNREACHABLE("We should catch and prevent divisions in integer "
253 : "calc()s in the parser.");
254 : return 1;
255 : }
256 : };
257 :
258 : /**
259 : * A ComputeCoefficient implementation for callers that can assume coefficients
260 : * are floats and are already normalized (i.e., anything past the parser except
261 : * pure-integer calcs, whose coefficients are integers).
262 : */
263 127 : struct FloatCoeffsAlreadyNormalizedOps : public CSSValueInputCalcOps
264 : {
265 : typedef float coeff_type;
266 :
267 101 : coeff_type ComputeCoefficient(const nsCSSValue& aValue)
268 : {
269 101 : MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
270 101 : return aValue.GetFloatValue();
271 : }
272 : };
273 :
274 : /**
275 : * SerializeCalc appends the serialization of aValue to a string.
276 : *
277 : * It is templatized over a CalcOps class that is expected to provide:
278 : *
279 : * // input_type and input_array_type have a bunch of very specific
280 : * // expectations (which happen to be met by two classes (nsCSSValue
281 : * // and nsStyleCoord). There must be methods (roughly):
282 : * // input_array_type* input_type::GetArrayValue();
283 : * // uint32_t input_array_type::Count() const;
284 : * // input_type& input_array_type::Item(uint32_t);
285 : * typedef ... input_type;
286 : * typedef ... input_array_type;
287 : *
288 : * static nsCSSUnit GetUnit(const input_type& aValue);
289 : *
290 : * void Append(const char* aString);
291 : * void AppendLeafValue(const input_type& aValue);
292 : *
293 : * // AppendCoefficient accepts an input_type value, which represents a
294 : * // value in the coefficient position, not a value of coeff_type,
295 : * // because we're serializing the calc() expression itself.
296 : * void AppendCoefficient(const input_type& aValue);
297 : *
298 : * Data structures given may or may not have a toplevel eCSSUnit_Calc
299 : * node representing a calc whose toplevel is not min() or max().
300 : */
301 :
302 : template <class CalcOps>
303 : static void
304 : SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps);
305 :
306 : // Serialize the toplevel value in a calc() tree. See big comment
307 : // above.
308 : template <class CalcOps>
309 : static void
310 0 : SerializeCalc(const typename CalcOps::input_type& aValue, CalcOps &aOps)
311 : {
312 0 : aOps.Append("calc(");
313 0 : nsCSSUnit unit = CalcOps::GetUnit(aValue);
314 0 : if (unit == eCSSUnit_Calc) {
315 0 : const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
316 0 : MOZ_ASSERT(array->Count() == 1, "unexpected length");
317 0 : SerializeCalcInternal(array->Item(0), aOps);
318 : } else {
319 0 : SerializeCalcInternal(aValue, aOps);
320 : }
321 0 : aOps.Append(")");
322 0 : }
323 :
324 : static inline bool
325 0 : IsCalcAdditiveUnit(nsCSSUnit aUnit)
326 : {
327 0 : return aUnit == eCSSUnit_Calc_Plus ||
328 0 : aUnit == eCSSUnit_Calc_Minus;
329 : }
330 :
331 : static inline bool
332 0 : IsCalcMultiplicativeUnit(nsCSSUnit aUnit)
333 : {
334 0 : return aUnit == eCSSUnit_Calc_Times_L ||
335 0 : aUnit == eCSSUnit_Calc_Times_R ||
336 0 : aUnit == eCSSUnit_Calc_Divided;
337 : }
338 :
339 : // Serialize a non-toplevel value in a calc() tree. See big comment
340 : // above.
341 : template <class CalcOps>
342 : /* static */ void
343 0 : SerializeCalcInternal(const typename CalcOps::input_type& aValue, CalcOps &aOps)
344 : {
345 0 : nsCSSUnit unit = CalcOps::GetUnit(aValue);
346 0 : if (IsCalcAdditiveUnit(unit)) {
347 0 : const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
348 0 : MOZ_ASSERT(array->Count() == 2, "unexpected length");
349 :
350 0 : SerializeCalcInternal(array->Item(0), aOps);
351 :
352 0 : if (eCSSUnit_Calc_Plus == unit) {
353 0 : aOps.Append(" + ");
354 : } else {
355 0 : MOZ_ASSERT(eCSSUnit_Calc_Minus == unit, "unexpected unit");
356 0 : aOps.Append(" - ");
357 : }
358 :
359 0 : bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(1)));
360 0 : if (needParens) {
361 0 : aOps.Append("(");
362 : }
363 0 : SerializeCalcInternal(array->Item(1), aOps);
364 0 : if (needParens) {
365 0 : aOps.Append(")");
366 : }
367 0 : } else if (IsCalcMultiplicativeUnit(unit)) {
368 0 : const typename CalcOps::input_array_type *array = aValue.GetArrayValue();
369 0 : MOZ_ASSERT(array->Count() == 2, "unexpected length");
370 :
371 0 : bool needParens = IsCalcAdditiveUnit(CalcOps::GetUnit(array->Item(0)));
372 0 : if (needParens) {
373 0 : aOps.Append("(");
374 : }
375 0 : if (unit == eCSSUnit_Calc_Times_L) {
376 0 : aOps.AppendCoefficient(array->Item(0));
377 : } else {
378 0 : SerializeCalcInternal(array->Item(0), aOps);
379 : }
380 0 : if (needParens) {
381 0 : aOps.Append(")");
382 : }
383 :
384 0 : if (eCSSUnit_Calc_Times_L == unit || eCSSUnit_Calc_Times_R == unit) {
385 0 : aOps.Append(" * ");
386 : } else {
387 0 : MOZ_ASSERT(eCSSUnit_Calc_Divided == unit, "unexpected unit");
388 0 : aOps.Append(" / ");
389 : }
390 :
391 0 : nsCSSUnit subUnit = CalcOps::GetUnit(array->Item(1));
392 0 : needParens = IsCalcAdditiveUnit(subUnit) ||
393 0 : IsCalcMultiplicativeUnit(subUnit);
394 0 : if (needParens) {
395 0 : aOps.Append("(");
396 : }
397 0 : if (unit == eCSSUnit_Calc_Times_L) {
398 0 : SerializeCalcInternal(array->Item(1), aOps);
399 : } else {
400 0 : aOps.AppendCoefficient(array->Item(1));
401 : }
402 0 : if (needParens) {
403 0 : aOps.Append(")");
404 : }
405 : } else {
406 0 : aOps.AppendLeafValue(aValue);
407 : }
408 0 : }
409 :
410 : /**
411 : * ReduceNumberCalcOps is a CalcOps implementation for pure-number calc()
412 : * (sub-)expressions, input as nsCSSValues.
413 : * For example, nsCSSParser::ParseCalcMultiplicativeExpression uses it to
414 : * simplify numeric sub-expressions in order to check for division-by-zero.
415 : */
416 : struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps,
417 : public mozilla::css::CSSValueInputCalcOps
418 : {
419 233 : result_type ComputeLeafValue(const nsCSSValue& aValue)
420 : {
421 233 : MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
422 233 : return aValue.GetFloatValue();
423 : }
424 :
425 0 : coeff_type ComputeCoefficient(const nsCSSValue& aValue)
426 : {
427 0 : return mozilla::css::ComputeCalc(aValue, *this);
428 : }
429 : };
430 :
431 : /**
432 : * ReduceIntegerCalcOps is a CalcOps implementation for pure-integer calc()
433 : * (sub-)expressions, input as nsCSSValues.
434 : */
435 : struct ReduceIntegerCalcOps : public mozilla::css::BasicIntegerCalcOps,
436 : public mozilla::css::CSSValueInputCalcOps
437 : {
438 : result_type
439 0 : ComputeLeafValue(const nsCSSValue& aValue)
440 : {
441 0 : MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Integer, "unexpected unit");
442 0 : return aValue.GetIntValue();
443 : }
444 :
445 : coeff_type
446 0 : ComputeCoefficient(const nsCSSValue& aValue)
447 : {
448 0 : return mozilla::css::ComputeCalc(aValue, *this);
449 : }
450 : };
451 :
452 : } // namespace css
453 :
454 : } // namespace mozilla
455 :
456 : #endif /* !defined(CSSCalc_h_) */
|