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