WebCore:
[WebKit-https.git] / WebCore / css / CSSPrimitiveValue.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include "config.h"
22 #include "CSSPrimitiveValue.h"
23
24 #include "CSSHelper.h"
25 #include "CSSValueKeywords.h"
26 #include "Color.h"
27 #include "Counter.h"
28 #include "DashboardRegion.h"
29 #include "ExceptionCode.h"
30 #include "Pair.h"
31 #include "RenderStyle.h"
32 #include <wtf/ASCIICType.h>
33
34 using namespace WTF;
35
36 namespace WebCore {
37
38 // "ident" from the CSS tokenizer, minus backslash-escape sequences
39 static bool isCSSTokenizerIdentifier(const String& string)
40 {
41     const UChar* p = string.characters();
42     const UChar* end = p + string.length();
43
44     // -?
45     if (p != end && p[0] == '-')
46         ++p;
47
48     // {nmstart}
49     if (p == end || !(p[0] == '_' || p[0] >= 128 || isASCIIAlpha(p[0])))
50         return false;
51     ++p;
52
53     // {nmchar}*
54     for (; p != end; ++p) {
55         if (!(p[0] == '_' || p[0] == '-' || p[0] >= 128 || isASCIIAlphanumeric(p[0])))
56             return false;
57     }
58
59     return true;
60 }
61
62 // "url" from the CSS tokenizer, minus backslash-escape sequences
63 static bool isCSSTokenizerURL(const String& string)
64 {
65     const UChar* p = string.characters();
66     const UChar* end = p + string.length();
67
68     for (; p != end; ++p) {
69         UChar c = p[0];
70         switch (c) {
71             case '!':
72             case '#':
73             case '$':
74             case '%':
75             case '&':
76                 break;
77             default:
78                 if (c < '*')
79                     return false;
80                 if (c <= '~')
81                     break;
82                 if (c < 128)
83                     return false;
84         }
85     }
86
87     return true;
88 }
89
90 // We use single quotes for now because markup.cpp uses double quotes.
91 static String quoteString(const String& string)
92 {
93     // FIXME: Also need to escape characters like '\n'.
94     String s = string;
95     s.replace('\\', "\\\\");
96     s.replace('\'', "\\'");
97     return "'" + s + "'";
98 }
99
100 static String quoteStringIfNeeded(const String& string)
101 {
102     return isCSSTokenizerIdentifier(string) ? string : quoteString(string);
103 }
104
105 static String quoteURLIfNeeded(const String& string)
106 {
107     return isCSSTokenizerURL(string) ? string : quoteString(string);
108 }
109
110 CSSPrimitiveValue::CSSPrimitiveValue()
111     : m_type(0)
112 {
113 }
114
115 CSSPrimitiveValue::CSSPrimitiveValue(int ident)
116     : m_type(CSS_IDENT)
117 {
118     m_value.ident = ident;
119 }
120
121 CSSPrimitiveValue::CSSPrimitiveValue(double num, UnitTypes type)
122     : m_type(type)
123 {
124     m_value.num = num;
125 }
126
127 CSSPrimitiveValue::CSSPrimitiveValue(const String& str, UnitTypes type)
128     : m_type(type)
129 {
130     if ((m_value.string = str.impl()))
131         m_value.string->ref();
132 }
133
134 CSSPrimitiveValue::CSSPrimitiveValue(RGBA32 color)
135     : m_type(CSS_RGBCOLOR)
136 {
137     m_value.rgbcolor = color;
138 }
139
140 CSSPrimitiveValue::CSSPrimitiveValue(const Length& length)
141 {
142     switch (length.type()) {
143         case Auto:
144             m_type = CSS_IDENT;
145             m_value.ident = CSS_VAL_AUTO;
146             break;
147         case WebCore::Fixed:
148             m_type = CSS_PX;
149             m_value.num = length.value();
150             break;
151         case Intrinsic:
152             m_type = CSS_IDENT;
153             m_value.ident = CSS_VAL_INTRINSIC;
154             break;
155         case MinIntrinsic:
156             m_type = CSS_IDENT;
157             m_value.ident = CSS_VAL_MIN_INTRINSIC;
158             break;
159         case Percent:
160             m_type = CSS_PERCENTAGE;
161             m_value.num = length.percent();
162             break;
163         case Relative:
164         case Static:
165             ASSERT_NOT_REACHED();
166             break;
167     }
168 }
169
170 void CSSPrimitiveValue::init(PassRefPtr<Counter> c)
171 {
172     m_type = CSS_COUNTER;
173     m_value.counter = c.releaseRef();
174 }
175
176 void CSSPrimitiveValue::init(PassRefPtr<Rect> r)
177 {
178     m_type = CSS_RECT;
179     m_value.rect = r.releaseRef();
180 }
181
182 void CSSPrimitiveValue::init(PassRefPtr<DashboardRegion> r)
183 {
184     m_type = CSS_DASHBOARD_REGION;
185     m_value.region = r.releaseRef();
186 }
187
188 void CSSPrimitiveValue::init(PassRefPtr<Pair> p)
189 {
190     m_type = CSS_PAIR;
191     m_value.pair = p.releaseRef();
192 }
193
194 CSSPrimitiveValue::~CSSPrimitiveValue()
195 {
196     cleanup();
197 }
198
199 void CSSPrimitiveValue::cleanup()
200 {
201     switch (m_type) {
202         case CSS_STRING:
203         case CSS_URI:
204         case CSS_ATTR:
205             if (m_value.string)
206                 m_value.string->deref();
207             break;
208         case CSS_COUNTER:
209             m_value.counter->deref();
210             break;
211         case CSS_RECT:
212             m_value.rect->deref();
213             break;
214         case CSS_PAIR:
215             m_value.pair->deref();
216             break;
217         case CSS_DASHBOARD_REGION:
218             if (m_value.region)
219                 m_value.region->deref();
220             break;
221         default:
222             break;
223     }
224
225     m_type = 0;
226 }
227
228 int CSSPrimitiveValue::computeLengthInt(RenderStyle* style)
229 {
230     double result = computeLengthDouble(style);
231
232     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
233     // need to go ahead and round if we're really close to the next integer value.
234     result += result < 0 ? -0.01 : +0.01;
235
236     if (result > INT_MAX || result < INT_MIN)
237         return 0;
238     return static_cast<int>(result);
239 }
240
241 int CSSPrimitiveValue::computeLengthInt(RenderStyle* style, double multiplier)
242 {
243     double result = multiplier * computeLengthDouble(style);
244
245     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
246     // need to go ahead and round if we're really close to the next integer value.
247     result += result < 0 ? -0.01 : +0.01;
248
249     if (result > INT_MAX || result < INT_MIN)
250         return 0;
251     return static_cast<int>(result);
252 }
253
254 const int intMaxForLength = 0x7ffffff; // max value for a 28-bit int
255 const int intMinForLength = (-0x7ffffff - 1); // min value for a 28-bit int
256
257 // Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
258 int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle* style)
259 {
260     double result = computeLengthDouble(style);
261
262     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
263     // need to go ahead and round if we're really close to the next integer value.
264     result += result < 0 ? -0.01 : +0.01;
265
266     if (result > intMaxForLength || result < intMinForLength)
267         return 0;
268     return static_cast<int>(result);
269 }
270
271 // Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
272 int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle* style, double multiplier)
273 {
274     double result = multiplier * computeLengthDouble(style);
275
276     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
277     // need to go ahead and round if we're really close to the next integer value.
278     result += result < 0 ? -0.01 : +0.01;
279
280     if (result > intMaxForLength || result < intMinForLength)
281         return 0;
282     return static_cast<int>(result);
283 }
284
285 short CSSPrimitiveValue::computeLengthShort(RenderStyle* style)
286 {
287     double result = computeLengthDouble(style);
288
289     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
290     // need to go ahead and round if we're really close to the next integer value.
291     result += result < 0 ? -0.01 : +0.01;
292
293     if (result > SHRT_MAX || result < SHRT_MIN)
294         return 0;
295     return static_cast<short>(result);
296 }
297
298 short CSSPrimitiveValue::computeLengthShort(RenderStyle* style, double multiplier)
299 {
300     double result = multiplier * computeLengthDouble(style);
301
302     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
303     // need to go ahead and round if we're really close to the next integer value.
304     result += result < 0 ? -0.01 : +0.01;
305
306     if (result > SHRT_MAX || result < SHRT_MIN)
307         return 0;
308     return static_cast<short>(result);
309 }
310
311 float CSSPrimitiveValue::computeLengthFloat(RenderStyle* style, bool applyZoomFactor)
312 {
313     return static_cast<float>(computeLengthDouble(style, applyZoomFactor));
314 }
315
316 double CSSPrimitiveValue::computeLengthDouble(RenderStyle* style, bool applyZoomFactor)
317 {
318     unsigned short type = primitiveType();
319
320     double factor = 1.0;
321     switch (type) {
322         case CSS_EMS:
323             factor = applyZoomFactor ? style->fontDescription().computedSize() : style->fontDescription().specifiedSize();
324             break;
325         case CSS_EXS:
326             // FIXME: We have a bug right now where the zoom will be applied multiple times to EX units.
327             // We really need to compute EX using fontMetrics for the original specifiedSize and not use
328             // our actual constructed rendering font.
329             factor = style->font().xHeight();
330             break;
331         case CSS_PX:
332             break;
333         case CSS_CM:
334             factor = cssPixelsPerInch / 2.54; // (2.54 cm/in)
335             break;
336         case CSS_MM:
337             factor = cssPixelsPerInch / 25.4;
338             break;
339         case CSS_IN:
340             factor = cssPixelsPerInch;
341             break;
342         case CSS_PT:
343             factor = cssPixelsPerInch / 72.0;
344             break;
345         case CSS_PC:
346             // 1 pc == 12 pt
347             factor = cssPixelsPerInch * 12.0 / 72.0;
348             break;
349         default:
350             return -1.0;
351     }
352
353     return getDoubleValue() * factor;
354 }
355
356 void CSSPrimitiveValue::setFloatValue(unsigned short unitType, double floatValue, ExceptionCode& ec)
357 {
358     ec = 0;
359
360     // FIXME: check if property supports this type
361     if (m_type > CSS_DIMENSION) {
362         ec = SYNTAX_ERR;
363         return;
364     }
365
366     cleanup();
367
368     //if(m_type > CSS_DIMENSION) throw DOMException(INVALID_ACCESS_ERR);
369     m_value.num = floatValue;
370     m_type = unitType;
371 }
372
373 double scaleFactorForConversion(unsigned short unitType)
374 {
375     double factor = 1.0;
376     switch (unitType) {
377         case CSSPrimitiveValue::CSS_PX:
378             break;
379         case CSSPrimitiveValue::CSS_CM:
380             factor = cssPixelsPerInch / 2.54; // (2.54 cm/in)
381             break;
382         case CSSPrimitiveValue::CSS_MM:
383             factor = cssPixelsPerInch / 25.4;
384             break;
385         case CSSPrimitiveValue::CSS_IN:
386             factor = cssPixelsPerInch;
387             break;
388         case CSSPrimitiveValue::CSS_PT:
389             factor = cssPixelsPerInch / 72.0;
390             break;
391         case CSSPrimitiveValue::CSS_PC:
392             factor = cssPixelsPerInch * 12.0 / 72.0; // 1 pc == 12 pt
393             break;
394         default:
395             break;
396     }
397
398     return factor;
399 }
400
401 double CSSPrimitiveValue::getDoubleValue(unsigned short unitType, ExceptionCode& ec)
402 {
403     ec = 0;
404     if (m_type < CSS_NUMBER || m_type > CSS_DIMENSION || unitType < CSS_NUMBER || unitType > CSS_DIMENSION) {
405         ec = INVALID_ACCESS_ERR;
406         return 0.0;
407     }
408
409     if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
410         return m_value.num;
411
412     double convertedValue = m_value.num;
413
414     // First convert the value from m_type into CSSPixels
415     double factor = scaleFactorForConversion(m_type);
416     convertedValue *= factor;
417
418     // Now convert from CSSPixels to the specified unitType
419     factor = scaleFactorForConversion(unitType);
420     convertedValue /= factor;
421
422     return convertedValue;
423 }
424
425 double CSSPrimitiveValue::getDoubleValue(unsigned short unitType)
426 {
427     if (m_type < CSS_NUMBER || m_type > CSS_DIMENSION || unitType < CSS_NUMBER || unitType > CSS_DIMENSION)
428         return 0;
429
430     if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
431         return m_value.num;
432
433     double convertedValue = m_value.num;
434
435     // First convert the value from m_type into CSSPixels
436     double factor = scaleFactorForConversion(m_type);
437     convertedValue *= factor;
438
439     // Now convert from CSSPixels to the specified unitType
440     factor = scaleFactorForConversion(unitType);
441     convertedValue /= factor;
442
443     return convertedValue;
444 }
445
446
447 void CSSPrimitiveValue::setStringValue(unsigned short stringType, const String& stringValue, ExceptionCode& ec)
448 {
449     ec = 0;
450
451     //if(m_type < CSS_STRING) throw DOMException(INVALID_ACCESS_ERR);
452     //if(m_type > CSS_ATTR) throw DOMException(INVALID_ACCESS_ERR);
453     if (m_type < CSS_STRING || m_type > CSS_ATTR) {
454         ec = SYNTAX_ERR;
455         return;
456     }
457
458     cleanup();
459
460     if (stringType != CSS_IDENT) {
461         m_value.string = stringValue.impl();
462         m_value.string->ref();
463         m_type = stringType;
464     }
465     // FIXME: parse ident
466 }
467
468 String CSSPrimitiveValue::getStringValue(ExceptionCode& ec) const
469 {
470     ec = 0;
471     switch (m_type) {
472         case CSS_STRING:
473         case CSS_ATTR:
474         case CSS_URI:
475             return m_value.string;
476         case CSS_IDENT:
477             return getValueName(m_value.ident);
478         default:
479             ec = INVALID_ACCESS_ERR;
480             break;
481     }
482
483     return String();
484 }
485
486 String CSSPrimitiveValue::getStringValue() const
487 {
488     switch (m_type) {
489         case CSS_STRING:
490         case CSS_ATTR:
491         case CSS_URI:
492             return m_value.string;
493         case CSS_IDENT:
494             return getValueName(m_value.ident);
495         default:
496             break;
497     }
498
499     return String();
500 }
501
502 Counter* CSSPrimitiveValue::getCounterValue(ExceptionCode& ec) const
503 {
504     ec = 0;
505     if (m_type != CSS_COUNTER) {
506         ec = INVALID_ACCESS_ERR;
507         return 0;
508     }
509
510     return m_value.counter;
511 }
512
513 Rect* CSSPrimitiveValue::getRectValue(ExceptionCode& ec) const
514 {
515     ec = 0;
516     if (m_type != CSS_RECT) {
517         ec = INVALID_ACCESS_ERR;
518         return 0;
519     }
520
521     return m_value.rect;
522 }
523
524 unsigned CSSPrimitiveValue::getRGBColorValue(ExceptionCode& ec) const
525 {
526     ec = 0;
527     if (m_type != CSS_RGBCOLOR) {
528         ec = INVALID_ACCESS_ERR;
529         return 0;
530     }
531
532     return m_value.rgbcolor;
533 }
534
535 Pair* CSSPrimitiveValue::getPairValue(ExceptionCode& ec) const
536 {
537     ec = 0;
538     if (m_type != CSS_PAIR) {
539         ec = INVALID_ACCESS_ERR;
540         return 0;
541     }
542
543     return m_value.pair;
544 }
545
546 unsigned short CSSPrimitiveValue::cssValueType() const
547 {
548     return CSS_PRIMITIVE_VALUE;
549 }
550
551 bool CSSPrimitiveValue::parseString(const String& /*string*/, bool /*strict*/)
552 {
553     // FIXME
554     return false;
555 }
556
557 int CSSPrimitiveValue::getIdent()
558 {
559     if (m_type != CSS_IDENT)
560         return 0;
561     return m_value.ident;
562 }
563
564 String CSSPrimitiveValue::cssText() const
565 {
566     // FIXME: return the original value instead of a generated one (e.g. color
567     // name if it was specified) - check what spec says about this
568     String text;
569     switch (m_type) {
570         case CSS_UNKNOWN:
571             // FIXME
572             break;
573         case CSS_NUMBER:
574             text = String::number(m_value.num);
575             break;
576         case CSS_PERCENTAGE:
577             text = String::format("%.6lg%%", m_value.num);
578             break;
579         case CSS_EMS:
580             text = String::format("%.6lgem", m_value.num);
581             break;
582         case CSS_EXS:
583             text = String::format("%.6lgex", m_value.num);
584             break;
585         case CSS_PX:
586             text = String::format("%.6lgpx", m_value.num);
587             break;
588         case CSS_CM:
589             text = String::format("%.6lgcm", m_value.num);
590             break;
591         case CSS_MM:
592             text = String::format("%.6lgmm", m_value.num);
593             break;
594         case CSS_IN:
595             text = String::format("%.6lgin", m_value.num);
596             break;
597         case CSS_PT:
598             text = String::format("%.6lgpt", m_value.num);
599             break;
600         case CSS_PC:
601             text = String::format("%.6lgpc", m_value.num);
602             break;
603         case CSS_DEG:
604             text = String::format("%.6lgdeg", m_value.num);
605             break;
606         case CSS_RAD:
607             text = String::format("%.6lgrad", m_value.num);
608             break;
609         case CSS_GRAD:
610             text = String::format("%.6lggrad", m_value.num);
611             break;
612         case CSS_MS:
613             text = String::format("%.6lgms", m_value.num);
614             break;
615         case CSS_S:
616             text = String::format("%.6lgs", m_value.num);
617             break;
618         case CSS_HZ:
619             text = String::format("%.6lghz", m_value.num);
620             break;
621         case CSS_KHZ:
622             text = String::format("%.6lgkhz", m_value.num);
623             break;
624         case CSS_DIMENSION:
625             // FIXME
626             break;
627         case CSS_STRING:
628             text = quoteStringIfNeeded(m_value.string);
629             break;
630         case CSS_URI:
631             text = "url(" + quoteURLIfNeeded(m_value.string) + ")";
632             break;
633         case CSS_IDENT:
634             text = getValueName(m_value.ident);
635             break;
636         case CSS_ATTR:
637             // FIXME
638             break;
639         case CSS_COUNTER:
640             text = "counter(";
641             text += String::number(m_value.num);
642             text += ")";
643             // FIXME: Add list-style and separator
644             break;
645         case CSS_RECT: {
646             Rect* rectVal = getRectValue();
647             text = "rect(";
648             text += rectVal->top()->cssText() + " ";
649             text += rectVal->right()->cssText() + " ";
650             text += rectVal->bottom()->cssText() + " ";
651             text += rectVal->left()->cssText() + ")";
652             break;
653         }
654         case CSS_RGBCOLOR: {
655             Color color(m_value.rgbcolor);
656             text = (color.alpha() < 0xFF) ? "rgba(" : "rgb(";
657             text += String::number(color.red()) + ", ";
658             text += String::number(color.green()) + ", ";
659             text += String::number(color.blue());
660             if (color.alpha() < 0xFF)
661                 text += ", " + String::number(static_cast<float>(color.alpha()) / 0xFF);
662             text += ")";
663             break;
664         }
665         case CSS_PAIR:
666             text = m_value.pair->first()->cssText();
667             text += " ";
668             text += m_value.pair->second()->cssText();
669             break;
670         case CSS_DASHBOARD_REGION:
671             for (DashboardRegion* region = getDashboardRegionValue(); region; region = region->m_next.get()) {
672                 if (!text.isEmpty())
673                     text.append(' ');
674                 text += "dashboard-region(";
675                 text += region->m_label;
676                 if (region->m_isCircle)
677                     text += " circle";
678                 else if (region->m_isRectangle)
679                     text += " rectangle";
680                 else
681                     break;
682                 if (region->top()->m_type == CSS_IDENT && region->top()->getIdent() == CSS_VAL_INVALID) {
683                     ASSERT(region->right()->m_type == CSS_IDENT);
684                     ASSERT(region->bottom()->m_type == CSS_IDENT);
685                     ASSERT(region->left()->m_type == CSS_IDENT);
686                     ASSERT(region->right()->getIdent() == CSS_VAL_INVALID);
687                     ASSERT(region->bottom()->getIdent() == CSS_VAL_INVALID);
688                     ASSERT(region->left()->getIdent() == CSS_VAL_INVALID);
689                 } else {
690                     text.append(' ');
691                     text += region->top()->cssText() + " ";
692                     text += region->right()->cssText() + " ";
693                     text += region->bottom()->cssText() + " ";
694                     text += region->left()->cssText();
695                 }
696                 text += ")";
697             }
698             break;
699     }
700     return text;
701 }
702
703 } // namespace WebCore