f1da2cf4886ff46c33fd36ff7961990f5127541f
[WebKit-https.git] / WebCore / css / CSSPrimitiveValue.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
5  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22 #include "config.h"
23 #include "CSSPrimitiveValue.h"
24
25 #include "Color.h"
26 #include "Counter.h"
27 #include "CSSValueKeywords.h"
28 #include "DashboardRegion.h"
29 #include "ExceptionCode.h"
30 #include "Pair.h"
31 #include "RenderStyle.h"
32
33 namespace WebCore {
34
35 // "ident" from the CSS tokenizer, minus backslash-escape sequences
36 static bool isCSSTokenizerIdentifier(const String& string)
37 {
38     const UChar* p = string.characters();
39     const UChar* end = p + string.length();
40
41     // -?
42     if (p != end && p[0] == '-')
43         ++p;
44
45     // {nmstart}
46     if (p == end || !(p[0] == '_' || isalpha(p[0]) || p[0] >= 128))
47         return false;
48     ++p;
49
50     // {nmchar}*
51     for (; p != end; ++p)
52         if (!(p[0] == '_' || p[0] == '-' || isalnum(p[0]) || p[0] >= 128))
53             return false;
54
55     return true;
56 }
57
58 // "url" from the CSS tokenizer, minus backslash-escape sequences
59 static bool isCSSTokenizerURL(const String& string)
60 {
61     const UChar* p = string.characters();
62     const UChar* end = p + string.length();
63
64     for (; p != end; ++p)
65         switch (p[0]) {
66             case '!':
67             case '#':
68             case '$':
69             case '%':
70             case '&':
71             case '*':
72             case '-':
73             case '~':
74                 break;
75             default:
76                 if (p[0] < 128)
77                     return false;
78         }
79
80     return true;
81 }
82
83 // We use single quotes for now because markup.cpp uses double quotes.
84 static String quoteString(const String& string)
85 {
86     // FIXME: Also need to escape characters like '\n'.
87     String s = string;
88     s.replace('\\', "\\\\");
89     s.replace('\'', "\\'");
90     return "'" + s + "'";
91 }
92
93 static String quoteStringIfNeeded(const String& string)
94 {
95     return isCSSTokenizerIdentifier(string) ? string : quoteString(string);
96 }
97
98 static String quoteURLIfNeeded(const String& string)
99 {
100     return isCSSTokenizerURL(string) ? string : quoteString(string);
101 }
102
103 CSSPrimitiveValue::CSSPrimitiveValue()
104 {
105     m_type = 0;
106 }
107
108 CSSPrimitiveValue::CSSPrimitiveValue(int ident)
109 {
110     m_value.ident = ident;
111     m_type = CSS_IDENT;
112 }
113
114 CSSPrimitiveValue::CSSPrimitiveValue(double num, UnitTypes type)
115 {
116     m_value.num = num;
117     m_type = type;
118 }
119
120 CSSPrimitiveValue::CSSPrimitiveValue(const String& str, UnitTypes type)
121 {
122     if ((m_value.string = str.impl()))
123         m_value.string->ref();
124     m_type = type;
125 }
126
127 CSSPrimitiveValue::CSSPrimitiveValue(PassRefPtr<Counter> c)
128 {
129     m_value.counter = c.release();
130     m_type = CSS_COUNTER;
131 }
132
133 CSSPrimitiveValue::CSSPrimitiveValue(PassRefPtr<RectImpl> r)
134 {
135     m_value.rect = r.release();
136     m_type = CSS_RECT;
137 }
138
139 #if __APPLE__
140 CSSPrimitiveValue::CSSPrimitiveValue(PassRefPtr<DashboardRegion> r)
141 {
142     m_value.region = r.release();
143     m_type = CSS_DASHBOARD_REGION;
144 }
145 #endif
146
147 CSSPrimitiveValue::CSSPrimitiveValue(RGBA32 color)
148 {
149     m_value.rgbcolor = color;
150     m_type = CSS_RGBCOLOR;
151 }
152
153 CSSPrimitiveValue::CSSPrimitiveValue(PassRefPtr<Pair> p)
154 {
155     m_value.pair = p.release();
156     m_type = CSS_PAIR;
157 }
158
159 CSSPrimitiveValue::~CSSPrimitiveValue()
160 {
161     cleanup();
162 }
163
164 void CSSPrimitiveValue::cleanup()
165 {
166     switch(m_type) {
167     case CSS_STRING:
168     case CSS_URI:
169     case CSS_ATTR:
170         if (m_value.string)
171             m_value.string->deref();
172         break;
173     case CSS_COUNTER:
174         m_value.counter->deref();
175         break;
176     case CSS_RECT:
177         m_value.rect->deref();
178         break;
179     case CSS_PAIR:
180         m_value.pair->deref();
181         break;
182 #if __APPLE__
183     case CSS_DASHBOARD_REGION:
184         if (m_value.region)
185             m_value.region->deref();
186         break;
187 #endif
188     default:
189         break;
190     }
191
192     m_type = 0;
193 }
194
195 int CSSPrimitiveValue::computeLengthInt(RenderStyle *style)
196 {
197     double result = computeLengthFloat(style);
198     
199     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
200     // need to go ahead and round if we're really close to the next integer value.
201     result += result < 0 ? -0.01 : +0.01;
202     
203     if (result > INT_MAX || result < INT_MIN)
204         return 0;
205     return (int)result;    
206 }
207
208 int CSSPrimitiveValue::computeLengthInt(RenderStyle *style, double multiplier)
209 {
210     double result = multiplier * computeLengthFloat(style);
211     
212     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
213     // need to go ahead and round if we're really close to the next integer value.
214     result += result < 0 ? -0.01 : +0.01;
215     
216     if (result > INT_MAX || result < INT_MIN)
217         return 0;
218     return (int)result;  
219 }
220
221 const int intMaxForLength = 0x7ffffff; // max value for a 28-bit int
222 const int intMinForLength = (-0x7ffffff-1); // min value for a 28-bit int
223
224 // Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
225 int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle *style)
226 {
227     double result = computeLengthFloat(style);
228     
229     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
230     // need to go ahead and round if we're really close to the next integer value.
231     result += result < 0 ? -0.01 : +0.01;
232     
233     if (result > intMaxForLength || result < intMinForLength)
234         return 0;
235     return (int)result;    
236 }
237
238 // Lengths expect an int that is only 28-bits, so we have to check for a different overflow.
239 int CSSPrimitiveValue::computeLengthIntForLength(RenderStyle *style, double multiplier)
240 {
241     double result = multiplier * computeLengthFloat(style);
242     
243     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
244     // need to go ahead and round if we're really close to the next integer value.
245     result += result < 0 ? -0.01 : +0.01;
246     
247     if (result > intMaxForLength || result < intMinForLength)
248         return 0;
249     return (int)result;  
250 }
251
252 short CSSPrimitiveValue::computeLengthShort(RenderStyle *style)
253 {
254     double result = computeLengthFloat(style);
255     
256     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
257     // need to go ahead and round if we're really close to the next integer value.
258     result += result < 0 ? -0.01 : +0.01;
259     
260     if (result > SHRT_MAX || result < SHRT_MIN)
261         return 0;
262     return (short)result;    
263 }
264
265 short CSSPrimitiveValue::computeLengthShort(RenderStyle *style, double multiplier)
266 {
267     double result = multiplier * computeLengthFloat(style);
268     
269     // This conversion is imprecise, often resulting in values of, e.g., 44.99998.  We
270     // need to go ahead and round if we're really close to the next integer value.
271     result += result < 0 ? -0.01 : +0.01;
272     
273     if (result > SHRT_MAX || result < SHRT_MIN)
274         return 0;
275     return (short)result;  
276 }
277
278 double CSSPrimitiveValue::computeLengthFloat(RenderStyle *style, bool applyZoomFactor)
279 {
280     unsigned short type = primitiveType();
281
282     // We always assume 96 CSS pixels in a CSS inch. This is the cold hard truth of the Web.
283     // At high DPI, we may scale a CSS pixel, but the ratio of the CSS pixel to the so-called
284     // "absolute" CSS length units like inch and pt is always fixed and never changes.
285     double cssPixelsPerInch = 96.;
286
287     double factor = 1.;
288     switch(type) {
289     case CSS_EMS:
290         factor = applyZoomFactor ?
291           style->fontDescription().computedSize() :
292           style->fontDescription().specifiedSize();
293         break;
294     case CSS_EXS: {
295         // FIXME: We have a bug right now where the zoom will be applied multiple times to EX units.
296         // We really need to compute EX using fontMetrics for the original specifiedSize and not use
297         // our actual constructed rendering font.
298         factor = style->font().xHeight();
299         break;
300     }
301     case CSS_PX:
302         break;
303     case CSS_CM:
304         factor = cssPixelsPerInch/2.54; // (2.54 cm/in)
305         break;
306     case CSS_MM:
307         factor = cssPixelsPerInch/25.4;
308         break;
309     case CSS_IN:
310         factor = cssPixelsPerInch;
311         break;
312     case CSS_PT:
313         factor = cssPixelsPerInch/72.;
314         break;
315     case CSS_PC:
316         // 1 pc == 12 pt
317         factor = cssPixelsPerInch*12./72.;
318         break;
319     default:
320         return -1;
321     }
322
323     return getFloatValue() * factor;
324 }
325
326 void CSSPrimitiveValue::setFloatValue( unsigned short unitType, double floatValue, ExceptionCode& ec)
327 {
328     ec = 0;
329     
330     // ### check if property supports this type
331     if (m_type > CSS_DIMENSION) {
332         ec = SYNTAX_ERR;
333         return;
334     }
335     
336     cleanup();
337
338     //if(m_type > CSS_DIMENSION) throw DOMException(INVALID_ACCESS_ERR);
339     m_value.num = floatValue;
340     m_type = unitType;
341 }
342
343 double scaleFactorForConversion(unsigned short unitType)
344 {
345     double cssPixelsPerInch = 96.0;
346     double factor = 1.0;
347     
348     switch(unitType) {
349         case CSSPrimitiveValue::CSS_PX:
350             break;
351         case CSSPrimitiveValue::CSS_CM:
352             factor = cssPixelsPerInch / 2.54; // (2.54 cm/in)
353             break;
354         case CSSPrimitiveValue::CSS_MM:
355             factor = cssPixelsPerInch / 25.4;
356             break;
357         case CSSPrimitiveValue::CSS_IN:
358             factor = cssPixelsPerInch;
359             break;
360         case CSSPrimitiveValue::CSS_PT:
361             factor = cssPixelsPerInch / 72.0;
362             break;
363         case CSSPrimitiveValue::CSS_PC:
364             factor = cssPixelsPerInch * 12.0 / 72.0; // 1 pc == 12 pt
365             break;
366         default:
367             break;
368     }
369     
370     return factor;
371 }
372
373 double CSSPrimitiveValue::getFloatValue(unsigned short unitType)
374 {
375     if (unitType == m_type || unitType < CSS_PX || unitType > CSS_PC)
376         return m_value.num;
377     
378     double convertedValue = m_value.num;
379     
380     // First convert the value from m_type into CSSPixels
381     double factor = scaleFactorForConversion(m_type);
382     convertedValue *= factor;
383     
384     // Now convert from CSSPixels to the specified unitType
385     factor = scaleFactorForConversion(unitType);
386     convertedValue /= factor;
387     
388     return convertedValue;
389 }
390
391 void CSSPrimitiveValue::setStringValue( unsigned short stringType, const String &stringValue, ExceptionCode& ec)
392 {
393     ec = 0;
394         
395     //if(m_type < CSS_STRING) throw DOMException(INVALID_ACCESS_ERR);
396     //if(m_type > CSS_ATTR) throw DOMException(INVALID_ACCESS_ERR);
397     if (m_type < CSS_STRING || m_type > CSS_ATTR) {
398         ec = SYNTAX_ERR;
399         return;
400     }
401     
402     cleanup();
403
404     if (stringType != CSS_IDENT) {
405         m_value.string = stringValue.impl();
406         m_value.string->ref();
407         m_type = stringType;
408     }
409     // ### parse ident
410 }
411
412 String CSSPrimitiveValue::getStringValue() const
413 {
414     switch (m_type) {
415         case CSS_STRING:
416         case CSS_ATTR:
417         case CSS_URI:
418             return m_value.string;
419         case CSS_IDENT:
420             return getValueName(m_value.ident);
421         default:
422             // FIXME: The CSS 2.1 spec says you should throw an exception here.
423             break;
424     }
425     
426     return String();
427 }
428
429 unsigned short CSSPrimitiveValue::cssValueType() const
430 {
431     return CSS_PRIMITIVE_VALUE;
432 }
433
434 bool CSSPrimitiveValue::parseString( const String &/*string*/, bool )
435 {
436     // ###
437     return false;
438 }
439
440 int CSSPrimitiveValue::getIdent()
441 {
442     if(m_type != CSS_IDENT) return 0;
443     return m_value.ident;
444 }
445
446 String CSSPrimitiveValue::cssText() const
447 {
448     // ### return the original value instead of a generated one (e.g. color
449     // name if it was specified) - check what spec says about this
450     String text;
451     switch ( m_type ) {
452         case CSS_UNKNOWN:
453             // ###
454             break;
455         case CSS_NUMBER:
456             text = String::number(m_value.num);
457             break;
458         case CSS_PERCENTAGE:
459             text = String::number(m_value.num) + "%";
460             break;
461         case CSS_EMS:
462             text = String::number(m_value.num) + "em";
463             break;
464         case CSS_EXS:
465             text = String::number(m_value.num) + "ex";
466             break;
467         case CSS_PX:
468             text = String::number(m_value.num) + "px";
469             break;
470         case CSS_CM:
471             text = String::number(m_value.num) + "cm";
472             break;
473         case CSS_MM:
474             text = String::number(m_value.num) + "mm";
475             break;
476         case CSS_IN:
477             text = String::number(m_value.num) + "in";
478             break;
479         case CSS_PT:
480             text = String::number(m_value.num) + "pt";
481             break;
482         case CSS_PC:
483             text = String::number(m_value.num) + "pc";
484             break;
485         case CSS_DEG:
486             text = String::number(m_value.num) + "deg";
487             break;
488         case CSS_RAD:
489             text = String::number(m_value.num) + "rad";
490             break;
491         case CSS_GRAD:
492             text = String::number(m_value.num) + "grad";
493             break;
494         case CSS_MS:
495             text = String::number(m_value.num) + "ms";
496             break;
497         case CSS_S:
498             text = String::number(m_value.num) + "s";
499             break;
500         case CSS_HZ:
501             text = String::number(m_value.num) + "hz";
502             break;
503         case CSS_KHZ:
504             text = String::number(m_value.num) + "khz";
505             break;
506         case CSS_DIMENSION:
507             // ###
508             break;
509         case CSS_STRING:
510             text = quoteStringIfNeeded(m_value.string);
511             break;
512         case CSS_URI:
513             text = "url(" + quoteURLIfNeeded(m_value.string) + ")";
514             break;
515         case CSS_IDENT:
516             text = getValueName(m_value.ident);
517             break;
518         case CSS_ATTR:
519             // ###
520             break;
521         case CSS_COUNTER:
522             // ###
523             break;
524         case CSS_RECT: {
525             RectImpl* rectVal = getRectValue();
526             text = "rect(";
527             text += rectVal->top()->cssText() + " ";
528             text += rectVal->right()->cssText() + " ";
529             text += rectVal->bottom()->cssText() + " ";
530             text += rectVal->left()->cssText() + ")";
531             break;
532         }
533         case CSS_RGBCOLOR: {
534             Color color(m_value.rgbcolor);
535             if (color.alpha() < 0xFF)
536                 text = "rgba(";
537             else
538                 text = "rgb(";
539             text += String::number(color.red()) + ", ";
540             text += String::number(color.green()) + ", ";
541             text += String::number(color.blue());
542             if (color.alpha() < 0xFF)
543                 text += ", " + String::number((float)color.alpha() / 0xFF);
544             text += ")";
545             break;
546         }
547         case CSS_PAIR:
548             text = m_value.pair->first()->cssText();
549             text += " ";
550             text += m_value.pair->second()->cssText();
551             break;
552 #if __APPLE__
553         case CSS_DASHBOARD_REGION:
554             for (DashboardRegion* region = getDashboardRegionValue(); region; region = region->m_next.get()) {
555                 text = "dashboard-region(";
556                 text += region->m_label;
557                 if (region->m_isCircle)
558                     text += " circle ";
559                 else if (region->m_isRectangle)
560                     text += " rectangle ";
561                 else
562                     break;
563                 text += region->top()->cssText() + " ";
564                 text += region->right()->cssText() + " ";
565                 text += region->bottom()->cssText() + " ";
566                 text += region->left()->cssText();
567                 text += ")";
568             }
569             break;
570 #endif
571     }
572     return text;
573 }
574
575 }