|
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 |
|
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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 |
|
122 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 |
|
140 MergeAdditive(nsCSSUnit aCalcFunction, |
|
141 result_type aValue1, result_type aValue2) |
|
142 { |
|
143 if (aCalcFunction == eCSSUnit_Calc_Plus) { |
|
144 return NSCoordSaturatingAdd(aValue1, aValue2); |
|
145 } |
|
146 NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Minus, |
|
147 "unexpected unit"); |
|
148 return NSCoordSaturatingSubtract(aValue1, aValue2, 0); |
|
149 } |
|
150 |
|
151 result_type |
|
152 MergeMultiplicativeL(nsCSSUnit aCalcFunction, |
|
153 float aValue1, result_type aValue2) |
|
154 { |
|
155 NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_L, |
|
156 "unexpected unit"); |
|
157 return NSCoordSaturatingMultiply(aValue2, aValue1); |
|
158 } |
|
159 |
|
160 result_type |
|
161 MergeMultiplicativeR(nsCSSUnit aCalcFunction, |
|
162 result_type aValue1, float aValue2) |
|
163 { |
|
164 NS_ABORT_IF_FALSE(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 |
|
179 MergeAdditive(nsCSSUnit aCalcFunction, |
|
180 result_type aValue1, result_type aValue2) |
|
181 { |
|
182 if (aCalcFunction == eCSSUnit_Calc_Plus) { |
|
183 return aValue1 + aValue2; |
|
184 } |
|
185 NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Minus, |
|
186 "unexpected unit"); |
|
187 return aValue1 - aValue2; |
|
188 } |
|
189 |
|
190 result_type |
|
191 MergeMultiplicativeL(nsCSSUnit aCalcFunction, |
|
192 float aValue1, result_type aValue2) |
|
193 { |
|
194 NS_ABORT_IF_FALSE(aCalcFunction == eCSSUnit_Calc_Times_L, |
|
195 "unexpected unit"); |
|
196 return aValue1 * aValue2; |
|
197 } |
|
198 |
|
199 result_type |
|
200 MergeMultiplicativeR(nsCSSUnit aCalcFunction, |
|
201 result_type aValue1, float aValue2) |
|
202 { |
|
203 if (aCalcFunction == eCSSUnit_Calc_Times_R) { |
|
204 return aValue1 * aValue2; |
|
205 } |
|
206 NS_ABORT_IF_FALSE(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 { |
|
218 float ComputeNumber(const nsCSSValue& aValue) |
|
219 { |
|
220 NS_ABORT_IF_FALSE(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 |
|
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 NS_ABORT_IF_FALSE(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 |
|
272 IsCalcAdditiveUnit(nsCSSUnit aUnit) |
|
273 { |
|
274 return aUnit == eCSSUnit_Calc_Plus || |
|
275 aUnit == eCSSUnit_Calc_Minus; |
|
276 } |
|
277 |
|
278 static inline bool |
|
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 |
|
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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 NS_ABORT_IF_FALSE(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 } |
|
358 |
|
359 } |
|
360 |
|
361 #endif /* !defined(CSSCalc_h_) */ |