1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2012 Apple Inc. All rights
4  * reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
23 
24 #include <cmath>
25 
26 #include "build/build_config.h"
27 #include "third_party/blink/renderer/core/css/css_markup.h"
28 #include "third_party/blink/renderer/core/css/css_math_expression_node.h"
29 #include "third_party/blink/renderer/core/css/css_math_function_value.h"
30 #include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
31 #include "third_party/blink/renderer/core/css/css_resolution_units.h"
32 #include "third_party/blink/renderer/core/css/css_to_length_conversion_data.h"
33 #include "third_party/blink/renderer/core/css/css_value_pool.h"
34 #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
35 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
36 #include "third_party/blink/renderer/platform/wtf/size_assertions.h"
37 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
38 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
39 
40 namespace blink {
41 
42 namespace {
43 
44 // Max/min values for CSS, needs to slightly smaller/larger than the true
45 // max/min values to allow for rounding without overflowing.
46 // Subtract two (rather than one) to allow for values to be converted to float
47 // and back without exceeding the LayoutUnit::Max.
48 const int kMaxValueForCssLength = INT_MAX / kFixedPointDenominator - 2;
49 const int kMinValueForCssLength = INT_MIN / kFixedPointDenominator + 2;
50 
51 }  // namespace
52 
53 struct SameSizeAsCSSPrimitiveValue : CSSValue {
54 };
55 ASSERT_SIZE(CSSPrimitiveValue, SameSizeAsCSSPrimitiveValue);
56 
ClampToCSSLengthRange(double value)57 float CSSPrimitiveValue::ClampToCSSLengthRange(double value) {
58   return clampTo<float>(value, kMinValueForCssLength, kMaxValueForCssLength);
59 }
60 
UnitTypeToUnitCategory(UnitType type)61 CSSPrimitiveValue::UnitCategory CSSPrimitiveValue::UnitTypeToUnitCategory(
62     UnitType type) {
63   switch (type) {
64     case UnitType::kNumber:
65       return CSSPrimitiveValue::kUNumber;
66     case UnitType::kPercentage:
67       return CSSPrimitiveValue::kUPercent;
68     case UnitType::kPixels:
69     case UnitType::kCentimeters:
70     case UnitType::kMillimeters:
71     case UnitType::kQuarterMillimeters:
72     case UnitType::kInches:
73     case UnitType::kPoints:
74     case UnitType::kPicas:
75     case UnitType::kUserUnits:
76       return CSSPrimitiveValue::kULength;
77     case UnitType::kMilliseconds:
78     case UnitType::kSeconds:
79       return CSSPrimitiveValue::kUTime;
80     case UnitType::kDegrees:
81     case UnitType::kRadians:
82     case UnitType::kGradians:
83     case UnitType::kTurns:
84       return CSSPrimitiveValue::kUAngle;
85     case UnitType::kHertz:
86     case UnitType::kKilohertz:
87       return CSSPrimitiveValue::kUFrequency;
88     case UnitType::kDotsPerPixel:
89     case UnitType::kDotsPerInch:
90     case UnitType::kDotsPerCentimeter:
91       return CSSPrimitiveValue::kUResolution;
92     default:
93       return CSSPrimitiveValue::kUOther;
94   }
95 }
96 
IsCalculatedPercentageWithLength() const97 bool CSSPrimitiveValue::IsCalculatedPercentageWithLength() const {
98   // TODO(crbug.com/979895): Move this function to |CSSMathFunctionValue|.
99   return IsCalculated() &&
100          To<CSSMathFunctionValue>(this)->Category() == kCalcPercentLength;
101 }
102 
IsResolution() const103 bool CSSPrimitiveValue::IsResolution() const {
104   // TODO(crbug.com/983613): Either support math functions on resolutions; or
105   // provide a justification for not supporting it, and move this function to
106   // |CSSNumericLiteralValue|.
107   return IsNumericLiteralValue() &&
108          To<CSSNumericLiteralValue>(this)->IsResolution();
109 }
110 
IsFlex() const111 bool CSSPrimitiveValue::IsFlex() const {
112   // TODO(crbug.com/993136): Either support math functions on flexible lengths;
113   // or provide a justification for not supporting it, and move this function to
114   // |CSSNumericLiteralValue|.
115   return IsNumericLiteralValue() && To<CSSNumericLiteralValue>(this)->IsFlex();
116 }
117 
IsAngle() const118 bool CSSPrimitiveValue::IsAngle() const {
119   if (IsNumericLiteralValue())
120     return To<CSSNumericLiteralValue>(this)->IsAngle();
121   return To<CSSMathFunctionValue>(this)->IsAngle();
122 }
123 
IsLength() const124 bool CSSPrimitiveValue::IsLength() const {
125   if (IsNumericLiteralValue())
126     return To<CSSNumericLiteralValue>(this)->IsLength();
127   return To<CSSMathFunctionValue>(this)->IsLength();
128 }
129 
IsPx() const130 bool CSSPrimitiveValue::IsPx() const {
131   if (IsNumericLiteralValue())
132     return To<CSSNumericLiteralValue>(this)->IsPx();
133   return To<CSSMathFunctionValue>(this)->IsPx();
134 }
135 
IsNumber() const136 bool CSSPrimitiveValue::IsNumber() const {
137   if (IsNumericLiteralValue())
138     return To<CSSNumericLiteralValue>(this)->IsNumber();
139   return To<CSSMathFunctionValue>(this)->IsNumber();
140 }
141 
IsInteger() const142 bool CSSPrimitiveValue::IsInteger() const {
143   // TODO(crbug.com/931216): Support integer math functions properly.
144   return IsNumericLiteralValue() &&
145          To<CSSNumericLiteralValue>(this)->IsInteger();
146 }
147 
IsPercentage() const148 bool CSSPrimitiveValue::IsPercentage() const {
149   if (IsNumericLiteralValue())
150     return To<CSSNumericLiteralValue>(this)->IsPercentage();
151   return To<CSSMathFunctionValue>(this)->IsPercentage();
152 }
153 
IsTime() const154 bool CSSPrimitiveValue::IsTime() const {
155   if (IsNumericLiteralValue())
156     return To<CSSNumericLiteralValue>(this)->IsTime();
157   return To<CSSMathFunctionValue>(this)->IsTime();
158 }
159 
IsComputationallyIndependent() const160 bool CSSPrimitiveValue::IsComputationallyIndependent() const {
161   if (IsNumericLiteralValue())
162     return To<CSSNumericLiteralValue>(this)->IsComputationallyIndependent();
163   return To<CSSMathFunctionValue>(this)->IsComputationallyIndependent();
164 }
165 
CSSPrimitiveValue(ClassType class_type)166 CSSPrimitiveValue::CSSPrimitiveValue(ClassType class_type)
167     : CSSValue(class_type) {}
168 
169 // static
CreateFromLength(const Length & length,float zoom)170 CSSPrimitiveValue* CSSPrimitiveValue::CreateFromLength(const Length& length,
171                                                        float zoom) {
172   switch (length.GetType()) {
173     case Length::kPercent:
174       return CSSNumericLiteralValue::Create(length.Percent(),
175                                             UnitType::kPercentage);
176     case Length::kFixed:
177       return CSSNumericLiteralValue::Create(length.Value() / zoom,
178                                             UnitType::kPixels);
179     case Length::kCalculated: {
180       const CalculationValue& calc = length.GetCalculationValue();
181       if (calc.IsExpression() || (calc.Pixels() && calc.Percent()))
182         return CSSMathFunctionValue::Create(length, zoom);
183       if (!calc.Pixels()) {
184         double num = calc.Percent();
185         if (num < 0 && calc.IsNonNegative())
186           num = 0;
187         return CSSNumericLiteralValue::Create(num, UnitType::kPercentage);
188       }
189       double num = calc.Pixels() / zoom;
190       if (num < 0 && calc.IsNonNegative())
191         num = 0;
192       return CSSNumericLiteralValue::Create(num, UnitType::kPixels);
193     }
194     default:
195       break;
196   }
197   NOTREACHED();
198   return nullptr;
199 }
200 
ComputeSeconds() const201 double CSSPrimitiveValue::ComputeSeconds() const {
202   if (IsCalculated())
203     return To<CSSMathFunctionValue>(this)->ComputeSeconds();
204   return To<CSSNumericLiteralValue>(this)->ComputeSeconds();
205 }
206 
ComputeDegrees() const207 double CSSPrimitiveValue::ComputeDegrees() const {
208   if (IsCalculated())
209     return To<CSSMathFunctionValue>(this)->ComputeDegrees();
210   return To<CSSNumericLiteralValue>(this)->ComputeDegrees();
211 }
212 
ComputeDotsPerPixel() const213 double CSSPrimitiveValue::ComputeDotsPerPixel() const {
214   // TODO(crbug.com/983613): Either support math functions on resolutions; or
215   // provide a justification for not supporting it.
216   DCHECK(IsNumericLiteralValue());
217   return To<CSSNumericLiteralValue>(this)->ComputeDotsPerPixel();
218 }
219 
220 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const221 int CSSPrimitiveValue::ComputeLength(
222     const CSSToLengthConversionData& conversion_data) const {
223   return RoundForImpreciseConversion<int>(ComputeLengthDouble(conversion_data));
224 }
225 
226 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const227 unsigned CSSPrimitiveValue::ComputeLength(
228     const CSSToLengthConversionData& conversion_data) const {
229   return RoundForImpreciseConversion<unsigned>(
230       ComputeLengthDouble(conversion_data));
231 }
232 
233 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const234 Length CSSPrimitiveValue::ComputeLength(
235     const CSSToLengthConversionData& conversion_data) const {
236   return Length::Fixed(
237       ClampToCSSLengthRange(ComputeLengthDouble(conversion_data)));
238 }
239 
240 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const241 int16_t CSSPrimitiveValue::ComputeLength(
242     const CSSToLengthConversionData& conversion_data) const {
243   return RoundForImpreciseConversion<int16_t>(
244       ComputeLengthDouble(conversion_data));
245 }
246 
247 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const248 uint16_t CSSPrimitiveValue::ComputeLength(
249     const CSSToLengthConversionData& conversion_data) const {
250   return RoundForImpreciseConversion<uint16_t>(
251       ComputeLengthDouble(conversion_data));
252 }
253 
254 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const255 uint8_t CSSPrimitiveValue::ComputeLength(
256     const CSSToLengthConversionData& conversion_data) const {
257   return RoundForImpreciseConversion<uint8_t>(
258       ComputeLengthDouble(conversion_data));
259 }
260 
261 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const262 float CSSPrimitiveValue::ComputeLength(
263     const CSSToLengthConversionData& conversion_data) const {
264   return clampTo<float>(ComputeLengthDouble(conversion_data));
265 }
266 
267 template <>
ComputeLength(const CSSToLengthConversionData & conversion_data) const268 double CSSPrimitiveValue::ComputeLength(
269     const CSSToLengthConversionData& conversion_data) const {
270   return ComputeLengthDouble(conversion_data);
271 }
272 
ComputeLengthDouble(const CSSToLengthConversionData & conversion_data) const273 double CSSPrimitiveValue::ComputeLengthDouble(
274     const CSSToLengthConversionData& conversion_data) const {
275   if (IsCalculated())
276     return To<CSSMathFunctionValue>(this)->ComputeLengthPx(conversion_data);
277   return To<CSSNumericLiteralValue>(this)->ComputeLengthPx(conversion_data);
278 }
279 
AccumulateLengthArray(CSSLengthArray & length_array,double multiplier) const280 bool CSSPrimitiveValue::AccumulateLengthArray(CSSLengthArray& length_array,
281                                               double multiplier) const {
282   DCHECK_EQ(length_array.values.size(),
283             static_cast<unsigned>(kLengthUnitTypeCount));
284   if (IsCalculated()) {
285     return To<CSSMathFunctionValue>(this)->AccumulateLengthArray(length_array,
286                                                                  multiplier);
287   }
288   return To<CSSNumericLiteralValue>(this)->AccumulateLengthArray(length_array,
289                                                                  multiplier);
290 }
291 
AccumulateLengthUnitTypes(LengthTypeFlags & types) const292 void CSSPrimitiveValue::AccumulateLengthUnitTypes(
293     LengthTypeFlags& types) const {
294   if (IsCalculated())
295     return To<CSSMathFunctionValue>(this)->AccumulateLengthUnitTypes(types);
296   To<CSSNumericLiteralValue>(this)->AccumulateLengthUnitTypes(types);
297 }
298 
ConversionToCanonicalUnitsScaleFactor(UnitType unit_type)299 double CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
300     UnitType unit_type) {
301   double factor = 1.0;
302   // FIXME: the switch can be replaced by an array of scale factors.
303   switch (unit_type) {
304     // These are "canonical" units in their respective categories.
305     case UnitType::kPixels:
306     case UnitType::kUserUnits:
307     case UnitType::kDegrees:
308     case UnitType::kMilliseconds:
309     case UnitType::kHertz:
310       break;
311     case UnitType::kCentimeters:
312       factor = kCssPixelsPerCentimeter;
313       break;
314     case UnitType::kDotsPerCentimeter:
315       factor = 1 / kCssPixelsPerCentimeter;
316       break;
317     case UnitType::kMillimeters:
318       factor = kCssPixelsPerMillimeter;
319       break;
320     case UnitType::kQuarterMillimeters:
321       factor = kCssPixelsPerQuarterMillimeter;
322       break;
323     case UnitType::kInches:
324       factor = kCssPixelsPerInch;
325       break;
326     case UnitType::kDotsPerInch:
327       factor = 1 / kCssPixelsPerInch;
328       break;
329     case UnitType::kPoints:
330       factor = kCssPixelsPerPoint;
331       break;
332     case UnitType::kPicas:
333       factor = kCssPixelsPerPica;
334       break;
335     case UnitType::kRadians:
336       factor = 180 / kPiDouble;
337       break;
338     case UnitType::kGradians:
339       factor = 0.9;
340       break;
341     case UnitType::kTurns:
342       factor = 360;
343       break;
344     case UnitType::kSeconds:
345     case UnitType::kKilohertz:
346       factor = 1000;
347       break;
348     default:
349       break;
350   }
351 
352   return factor;
353 }
354 
ConvertToLength(const CSSToLengthConversionData & conversion_data) const355 Length CSSPrimitiveValue::ConvertToLength(
356     const CSSToLengthConversionData& conversion_data) const {
357   if (IsLength())
358     return ComputeLength<Length>(conversion_data);
359   if (IsPercentage()) {
360     if (IsNumericLiteralValue() ||
361         !To<CSSMathFunctionValue>(this)->AllowsNegativePercentageReference()) {
362       return Length::Percent(GetDoubleValue());
363     }
364   }
365   DCHECK(IsCalculated());
366   return To<CSSMathFunctionValue>(this)->ConvertToLength(conversion_data);
367 }
368 
GetDoubleValue() const369 double CSSPrimitiveValue::GetDoubleValue() const {
370   return IsCalculated() ? To<CSSMathFunctionValue>(this)->DoubleValue()
371                         : To<CSSNumericLiteralValue>(this)->DoubleValue();
372 }
373 
IsZero() const374 bool CSSPrimitiveValue::IsZero() const {
375   return IsCalculated() ? To<CSSMathFunctionValue>(this)->IsZero()
376                         : To<CSSNumericLiteralValue>(this)->IsZero();
377 }
378 
CanonicalUnitTypeForCategory(UnitCategory category)379 CSSPrimitiveValue::UnitType CSSPrimitiveValue::CanonicalUnitTypeForCategory(
380     UnitCategory category) {
381   // The canonical unit type is chosen according to the way
382   // CSSPropertyParser::ValidUnit() chooses the default unit in each category
383   // (based on unitflags).
384   switch (category) {
385     case kUNumber:
386       return UnitType::kNumber;
387     case kULength:
388       return UnitType::kPixels;
389     case kUPercent:
390       return UnitType::kUnknown;  // Cannot convert between numbers and percent.
391     case kUTime:
392       return UnitType::kMilliseconds;
393     case kUAngle:
394       return UnitType::kDegrees;
395     case kUFrequency:
396       return UnitType::kHertz;
397     case kUResolution:
398       return UnitType::kDotsPerPixel;
399     default:
400       return UnitType::kUnknown;
401   }
402 }
403 
UnitTypeToLengthUnitType(UnitType unit_type,LengthUnitType & length_type)404 bool CSSPrimitiveValue::UnitTypeToLengthUnitType(UnitType unit_type,
405                                                  LengthUnitType& length_type) {
406   switch (unit_type) {
407     case CSSPrimitiveValue::UnitType::kPixels:
408     case CSSPrimitiveValue::UnitType::kCentimeters:
409     case CSSPrimitiveValue::UnitType::kMillimeters:
410     case CSSPrimitiveValue::UnitType::kQuarterMillimeters:
411     case CSSPrimitiveValue::UnitType::kInches:
412     case CSSPrimitiveValue::UnitType::kPoints:
413     case CSSPrimitiveValue::UnitType::kPicas:
414     case CSSPrimitiveValue::UnitType::kUserUnits:
415       length_type = kUnitTypePixels;
416       return true;
417     case CSSPrimitiveValue::UnitType::kEms:
418     case CSSPrimitiveValue::UnitType::kQuirkyEms:
419       length_type = kUnitTypeFontSize;
420       return true;
421     case CSSPrimitiveValue::UnitType::kExs:
422       length_type = kUnitTypeFontXSize;
423       return true;
424     case CSSPrimitiveValue::UnitType::kRems:
425       length_type = kUnitTypeRootFontSize;
426       return true;
427     case CSSPrimitiveValue::UnitType::kChs:
428       length_type = kUnitTypeZeroCharacterWidth;
429       return true;
430     case CSSPrimitiveValue::UnitType::kPercentage:
431       length_type = kUnitTypePercentage;
432       return true;
433     case CSSPrimitiveValue::UnitType::kViewportWidth:
434       length_type = kUnitTypeViewportWidth;
435       return true;
436     case CSSPrimitiveValue::UnitType::kViewportHeight:
437       length_type = kUnitTypeViewportHeight;
438       return true;
439     case CSSPrimitiveValue::UnitType::kViewportMin:
440       length_type = kUnitTypeViewportMin;
441       return true;
442     case CSSPrimitiveValue::UnitType::kViewportMax:
443       length_type = kUnitTypeViewportMax;
444       return true;
445     default:
446       return false;
447   }
448 }
449 
LengthUnitTypeToUnitType(LengthUnitType type)450 CSSPrimitiveValue::UnitType CSSPrimitiveValue::LengthUnitTypeToUnitType(
451     LengthUnitType type) {
452   switch (type) {
453     case kUnitTypePixels:
454       return CSSPrimitiveValue::UnitType::kPixels;
455     case kUnitTypeFontSize:
456       return CSSPrimitiveValue::UnitType::kEms;
457     case kUnitTypeFontXSize:
458       return CSSPrimitiveValue::UnitType::kExs;
459     case kUnitTypeRootFontSize:
460       return CSSPrimitiveValue::UnitType::kRems;
461     case kUnitTypeZeroCharacterWidth:
462       return CSSPrimitiveValue::UnitType::kChs;
463     case kUnitTypePercentage:
464       return CSSPrimitiveValue::UnitType::kPercentage;
465     case kUnitTypeViewportWidth:
466       return CSSPrimitiveValue::UnitType::kViewportWidth;
467     case kUnitTypeViewportHeight:
468       return CSSPrimitiveValue::UnitType::kViewportHeight;
469     case kUnitTypeViewportMin:
470       return CSSPrimitiveValue::UnitType::kViewportMin;
471     case kUnitTypeViewportMax:
472       return CSSPrimitiveValue::UnitType::kViewportMax;
473     case kLengthUnitTypeCount:
474       break;
475   }
476   NOTREACHED();
477   return CSSPrimitiveValue::UnitType::kUnknown;
478 }
479 
UnitTypeToString(UnitType type)480 const char* CSSPrimitiveValue::UnitTypeToString(UnitType type) {
481   switch (type) {
482     case UnitType::kNumber:
483     case UnitType::kInteger:
484     case UnitType::kUserUnits:
485       return "";
486     case UnitType::kPercentage:
487       return "%";
488     case UnitType::kEms:
489     case UnitType::kQuirkyEms:
490       return "em";
491     case UnitType::kExs:
492       return "ex";
493     case UnitType::kRems:
494       return "rem";
495     case UnitType::kChs:
496       return "ch";
497     case UnitType::kPixels:
498       return "px";
499     case UnitType::kCentimeters:
500       return "cm";
501     case UnitType::kDotsPerPixel:
502       return "dppx";
503     case UnitType::kDotsPerInch:
504       return "dpi";
505     case UnitType::kDotsPerCentimeter:
506       return "dpcm";
507     case UnitType::kMillimeters:
508       return "mm";
509     case UnitType::kQuarterMillimeters:
510       return "q";
511     case UnitType::kInches:
512       return "in";
513     case UnitType::kPoints:
514       return "pt";
515     case UnitType::kPicas:
516       return "pc";
517     case UnitType::kDegrees:
518       return "deg";
519     case UnitType::kRadians:
520       return "rad";
521     case UnitType::kGradians:
522       return "grad";
523     case UnitType::kMilliseconds:
524       return "ms";
525     case UnitType::kSeconds:
526       return "s";
527     case UnitType::kHertz:
528       return "hz";
529     case UnitType::kKilohertz:
530       return "khz";
531     case UnitType::kTurns:
532       return "turn";
533     case UnitType::kFraction:
534       return "fr";
535     case UnitType::kViewportWidth:
536       return "vw";
537     case UnitType::kViewportHeight:
538       return "vh";
539     case UnitType::kViewportMin:
540       return "vmin";
541     case UnitType::kViewportMax:
542       return "vmax";
543     default:
544       break;
545   }
546   NOTREACHED();
547   return "";
548 }
549 
CustomCSSText() const550 String CSSPrimitiveValue::CustomCSSText() const {
551   if (IsCalculated())
552     return To<CSSMathFunctionValue>(this)->CustomCSSText();
553   return To<CSSNumericLiteralValue>(this)->CustomCSSText();
554 }
555 
TraceAfterDispatch(blink::Visitor * visitor) const556 void CSSPrimitiveValue::TraceAfterDispatch(blink::Visitor* visitor) const {
557   CSSValue::TraceAfterDispatch(visitor);
558 }
559 
560 }  // namespace blink
561