c7d84181a20807935f2897fd8e0f3f45f86131b3
[WebKit-https.git] / WebCore / css / CSSPrimitiveValue.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2005, 2006, 2007 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)
402 {
403     ASSERT(m_type <= CSS_DIMENSION);
404     ASSERT(unitType <= CSS_DIMENSION);
405
406     if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
407         return m_value.num;
408
409     double convertedValue = m_value.num;
410
411     // First convert the value from m_type into CSSPixels
412     double factor = scaleFactorForConversion(m_type);
413     convertedValue *= factor;
414
415     // Now convert from CSSPixels to the specified unitType
416     factor = scaleFactorForConversion(unitType);
417     convertedValue /= factor;
418
419     return convertedValue;
420 }
421
422 void CSSPrimitiveValue::setStringValue(unsigned short stringType, const String& stringValue, ExceptionCode& ec)
423 {
424     ec = 0;
425
426     //if(m_type < CSS_STRING) throw DOMException(INVALID_ACCESS_ERR);
427     //if(m_type > CSS_ATTR) throw DOMException(INVALID_ACCESS_ERR);
428     if (m_type < CSS_STRING || m_type > CSS_ATTR) {
429         ec = SYNTAX_ERR;
430         return;
431     }
432
433     cleanup();
434
435     if (stringType != CSS_IDENT) {
436         m_value.string = stringValue.impl();
437         m_value.string->ref();
438         m_type = stringType;
439     }
440     // FIXME: parse ident
441 }
442
443 String CSSPrimitiveValue::getStringValue() const
444 {
445     switch (m_type) {
446         case CSS_STRING:
447         case CSS_ATTR:
448         case CSS_URI:
449             return m_value.string;
450         case CSS_IDENT:
451             return getValueName(m_value.ident);
452         default:
453             // FIXME: The CSS 2.1 spec says you should throw an exception here.
454             break;
455     }
456
457     return String();
458 }
459
460 unsigned short CSSPrimitiveValue::cssValueType() const
461 {
462     return CSS_PRIMITIVE_VALUE;
463 }
464
465 bool CSSPrimitiveValue::parseString(const String& /*string*/, bool /*strict*/)
466 {
467     // FIXME
468     return false;
469 }
470
471 int CSSPrimitiveValue::getIdent()
472 {
473     if (m_type != CSS_IDENT)
474         return 0;
475     return m_value.ident;
476 }
477
478 String CSSPrimitiveValue::cssText() const
479 {
480     // FIXME: return the original value instead of a generated one (e.g. color
481     // name if it was specified) - check what spec says about this
482     String text;
483     switch (m_type) {
484         case CSS_UNKNOWN:
485             // FIXME
486             break;
487         case CSS_NUMBER:
488             text = String::number(m_value.num);
489             break;
490         case CSS_PERCENTAGE:
491             text = String::format("%.6lg%%", m_value.num);
492             break;
493         case CSS_EMS:
494             text = String::format("%.6lgem", m_value.num);
495             break;
496         case CSS_EXS:
497             text = String::format("%.6lgex", m_value.num);
498             break;
499         case CSS_PX:
500             text = String::format("%.6lgpx", m_value.num);
501             break;
502         case CSS_CM:
503             text = String::format("%.6lgcm", m_value.num);
504             break;
505         case CSS_MM:
506             text = String::format("%.6lgmm", m_value.num);
507             break;
508         case CSS_IN:
509             text = String::format("%.6lgin", m_value.num);
510             break;
511         case CSS_PT:
512             text = String::format("%.6lgpt", m_value.num);
513             break;
514         case CSS_PC:
515             text = String::format("%.6lgpc", m_value.num);
516             break;
517         case CSS_DEG:
518             text = String::format("%.6lgdeg", m_value.num);
519             break;
520         case CSS_RAD:
521             text = String::format("%.6lgrad", m_value.num);
522             break;
523         case CSS_GRAD:
524             text = String::format("%.6lggrad", m_value.num);
525             break;
526         case CSS_MS:
527             text = String::format("%.6lgms", m_value.num);
528             break;
529         case CSS_S:
530             text = String::format("%.6lgs", m_value.num);
531             break;
532         case CSS_HZ:
533             text = String::format("%.6lghz", m_value.num);
534             break;
535         case CSS_KHZ:
536             text = String::format("%.6lgkhz", m_value.num);
537             break;
538         case CSS_DIMENSION:
539             // FIXME
540             break;
541         case CSS_STRING:
542             text = quoteStringIfNeeded(m_value.string);
543             break;
544         case CSS_URI:
545             text = "url(" + quoteURLIfNeeded(m_value.string) + ")";
546             break;
547         case CSS_IDENT:
548             text = getValueName(m_value.ident);
549             break;
550         case CSS_ATTR:
551             // FIXME
552             break;
553         case CSS_COUNTER:
554             text = "counter(";
555             text += String::number(m_value.num);
556             text += ")";
557             // FIXME: Add list-style and separator
558             break;
559         case CSS_RECT: {
560             Rect* rectVal = getRectValue();
561             text = "rect(";
562             text += rectVal->top()->cssText() + " ";
563             text += rectVal->right()->cssText() + " ";
564             text += rectVal->bottom()->cssText() + " ";
565             text += rectVal->left()->cssText() + ")";
566             break;
567         }
568         case CSS_RGBCOLOR: {
569             Color color(m_value.rgbcolor);
570             text = (color.alpha() < 0xFF) ? "rgba(" : "rgb(";
571             text += String::number(color.red()) + ", ";
572             text += String::number(color.green()) + ", ";
573             text += String::number(color.blue());
574             if (color.alpha() < 0xFF)
575                 text += ", " + String::number(static_cast<float>(color.alpha()) / 0xFF);
576             text += ")";
577             break;
578         }
579         case CSS_PAIR:
580             text = m_value.pair->first()->cssText();
581             text += " ";
582             text += m_value.pair->second()->cssText();
583             break;
584         case CSS_DASHBOARD_REGION:
585             for (DashboardRegion* region = getDashboardRegionValue(); region; region = region->m_next.get()) {
586                 if (!text.isEmpty())
587                     text.append(' ');
588                 text += "dashboard-region(";
589                 text += region->m_label;
590                 if (region->m_isCircle)
591                     text += " circle";
592                 else if (region->m_isRectangle)
593                     text += " rectangle";
594                 else
595                     break;
596                 if (region->top()->m_type == CSS_IDENT && region->top()->getIdent() == CSS_VAL_INVALID) {
597                     ASSERT(region->right()->m_type == CSS_IDENT);
598                     ASSERT(region->bottom()->m_type == CSS_IDENT);
599                     ASSERT(region->left()->m_type == CSS_IDENT);
600                     ASSERT(region->right()->getIdent() == CSS_VAL_INVALID);
601                     ASSERT(region->bottom()->getIdent() == CSS_VAL_INVALID);
602                     ASSERT(region->left()->getIdent() == CSS_VAL_INVALID);
603                 } else {
604                     text.append(' ');
605                     text += region->top()->cssText() + " ";
606                     text += region->right()->cssText() + " ";
607                     text += region->bottom()->cssText() + " ";
608                     text += region->left()->cssText();
609                 }
610                 text += ")";
611             }
612             break;
613     }
614     return text;
615 }
616
617 } // namespace WebCore