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