/[suikacvs]/markup/html/whatpm/Whatpm/CSS/Parser.pm
Suika

Contents of /markup/html/whatpm/Whatpm/CSS/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.28 - (hide annotations) (download)
Sun Jan 6 14:15:36 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.27: +547 -38 lines
++ whatpm/Whatpm/CSS/ChangeLog	6 Jan 2008 14:15:24 -0000
	* Cascade.pm: The object now has new device-dependent
	parameter, |has_invert|.

	* Parser.pm: The object now has new device-dependent
	parameter, |clip_color| function.
	(parse_char_string): Inifinite-loop fixed for ignoring
	states.  No longer check for '(' and '[' matchings for
	compatibility with browsers.
	($default_serializer): Support for 'RGBA' data type.
	(color): Reimplemented.
	(backbround-color, border-top-color, border-right-color,
	border-bottom-color, border-left-color, outline-color): Implemented.

2008-01-06  Wakaba  <wakaba@suika.fam.cx>

1 wakaba 1.1 package Whatpm::CSS::Parser;
2     use strict;
3     use Whatpm::CSS::Tokenizer qw(:token);
4     require Whatpm::CSS::SelectorsParser;
5    
6     sub new ($) {
7 wakaba 1.3 my $self = bless {onerror => sub { }, must_level => 'm',
8 wakaba 1.5 message_level => 'w',
9 wakaba 1.3 unsupported_level => 'unsupported'}, shift;
10 wakaba 1.11 # $self->{base_uri}
11 wakaba 1.18 # $self->{unitless_px} = 1/0
12 wakaba 1.28 # $self->{hashless_rgb} = 1/0
13    
14     ## Media-dependent RGB color range clipper
15     $self->{clip_color} = sub {
16     shift; #my $self = shift;
17     my $value = shift;
18     if (defined $value and $value->[0] eq 'RGBA') {
19     my ($r, $g, $b) = @$value[1, 2, 3];
20     $r = 0 if $r < 0; $r = 255 if $r > 255;
21     $g = 0 if $g < 0; $g = 255 if $g > 255;
22     $b = 0 if $b < 0; $b = 255 if $b > 255;
23     return ['RGBA', $r, $g, $b, $value->[4]];
24     }
25     return $value;
26     };
27 wakaba 1.1
28     return $self;
29     } # new
30    
31     sub BEFORE_STATEMENT_STATE () { 0 }
32     sub BEFORE_DECLARATION_STATE () { 1 }
33     sub IGNORED_STATEMENT_STATE () { 2 }
34     sub IGNORED_DECLARATION_STATE () { 3 }
35    
36 wakaba 1.5 our $Prop; ## By CSS property name
37     our $Attr; ## By CSSOM attribute name
38     our $Key; ## By internal key
39    
40 wakaba 1.1 sub parse_char_string ($$) {
41     my $self = $_[0];
42    
43     my $s = $_[1];
44     pos ($s) = 0;
45 wakaba 1.2 my $line = 1;
46     my $column = 0;
47    
48     my $_onerror = $self->{onerror};
49     my $onerror = sub {
50     $_onerror->(@_, line => $line, column => $column);
51     };
52 wakaba 1.1
53     my $tt = Whatpm::CSS::Tokenizer->new;
54 wakaba 1.2 $tt->{onerror} = $onerror;
55 wakaba 1.1 $tt->{get_char} = sub {
56     if (pos $s < length $s) {
57 wakaba 1.2 my $c = ord substr $s, pos ($s)++, 1;
58     if ($c == 0x000A) {
59     $line++;
60     $column = 0;
61     } elsif ($c == 0x000D) {
62     unless (substr ($s, pos ($s), 1) eq "\x0A") {
63     $line++;
64     $column = 0;
65     } else {
66     $column++;
67     }
68     } else {
69     $column++;
70     }
71     return $c;
72 wakaba 1.1 } else {
73     return -1;
74     }
75     }; # $tt->{get_char}
76     $tt->init;
77    
78     my $sp = Whatpm::CSS::SelectorsParser->new;
79 wakaba 1.2 $sp->{onerror} = $onerror;
80 wakaba 1.1 $sp->{must_level} = $self->{must_level};
81 wakaba 1.2 $sp->{pseudo_element} = $self->{pseudo_element};
82     $sp->{pseudo_class} = $self->{pseudo_class};
83 wakaba 1.1
84 wakaba 1.4 my $nsmap = {};
85     $sp->{lookup_namespace_uri} = sub {
86     return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
87     }; # $sp->{lookup_namespace_uri}
88 wakaba 1.1
89     ## TODO: Supported pseudo classes and elements...
90    
91     require Message::DOM::CSSStyleSheet;
92     require Message::DOM::CSSRule;
93     require Message::DOM::CSSStyleDeclaration;
94    
95 wakaba 1.11 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
96    
97 wakaba 1.1 my $state = BEFORE_STATEMENT_STATE;
98     my $t = $tt->get_next_token;
99    
100     my $open_rules = [[]];
101     my $current_rules = $open_rules->[-1];
102     my $current_decls;
103     my $closing_tokens = [];
104 wakaba 1.3 my $charset_allowed = 1;
105 wakaba 1.4 my $namespace_allowed = 1;
106 wakaba 1.1
107     S: {
108     if ($state == BEFORE_STATEMENT_STATE) {
109     $t = $tt->get_next_token
110     while $t->{type} == S_TOKEN or
111     $t->{type} == CDO_TOKEN or
112     $t->{type} == CDC_TOKEN;
113    
114     if ($t->{type} == ATKEYWORD_TOKEN) {
115 wakaba 1.5 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
116 wakaba 1.4 $t = $tt->get_next_token;
117     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
118    
119     my $prefix;
120     if ($t->{type} == IDENT_TOKEN) {
121     $prefix = lc $t->{value};
122     ## TODO: Unicode lowercase
123    
124     $t = $tt->get_next_token;
125     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
126     }
127    
128     if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
129     my $uri = $t->{value};
130    
131     $t = $tt->get_next_token;
132     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
133    
134     ## ISSUE: On handling of empty namespace URI, Firefox 2 and
135     ## Opera 9 work differently (See SuikaWiki:namespace).
136     ## TODO: We need to check what we do once it is specced.
137    
138     if ($t->{type} == SEMICOLON_TOKEN) {
139     if ($namespace_allowed) {
140     $nsmap->{defined $prefix ? $prefix : ''} = $uri;
141     push @$current_rules,
142     Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
143     undef $charset_allowed;
144     } else {
145     $onerror->(type => 'at:namespace:not allowed',
146     level => $self->{must_level},
147     token => $t);
148     }
149    
150     $t = $tt->get_next_token;
151     ## Stay in the state.
152     redo S;
153     } else {
154     #
155     }
156     } else {
157     #
158     }
159    
160     $onerror->(type => 'syntax error:at:namespace',
161     level => $self->{must_level},
162     token => $t);
163     #
164 wakaba 1.5 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
165 wakaba 1.3 $t = $tt->get_next_token;
166     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
167    
168     if ($t->{type} == STRING_TOKEN) {
169     my $encoding = $t->{value};
170    
171     $t = $tt->get_next_token;
172     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
173    
174     if ($t->{type} == SEMICOLON_TOKEN) {
175     if ($charset_allowed) {
176     push @$current_rules,
177     Message::DOM::CSSCharsetRule->____new ($encoding);
178     undef $charset_allowed;
179     } else {
180     $onerror->(type => 'at:charset:not allowed',
181     level => $self->{must_level},
182     token => $t);
183     }
184    
185     ## TODO: Detect the conformance errors for @charset...
186    
187     $t = $tt->get_next_token;
188     ## Stay in the state.
189     redo S;
190     } else {
191     #
192     }
193     } else {
194     #
195     }
196    
197     $onerror->(type => 'syntax error:at:charset',
198     level => $self->{must_level},
199     token => $t);
200 wakaba 1.4 #
201 wakaba 1.3 ## NOTE: When adding support for new at-rule, insert code
202 wakaba 1.4 ## "undef $charset_allowed" and "undef $namespace_token" as
203     ## appropriate.
204 wakaba 1.3 } else {
205     $onerror->(type => 'not supported:at:'.$t->{value},
206     level => $self->{unsupported_level},
207     token => $t);
208     }
209 wakaba 1.1
210     $t = $tt->get_next_token;
211     $state = IGNORED_STATEMENT_STATE;
212     redo S;
213     } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
214     pop @$open_rules;
215     ## Stay in the state.
216     $t = $tt->get_next_token;
217     redo S;
218     } elsif ($t->{type} == EOF_TOKEN) {
219     if (@$open_rules > 1) {
220 wakaba 1.2 $onerror->(type => 'syntax error:block not closed',
221     level => $self->{must_level},
222     token => $t);
223 wakaba 1.1 }
224    
225     last S;
226     } else {
227 wakaba 1.3 undef $charset_allowed;
228 wakaba 1.4 undef $namespace_allowed;
229 wakaba 1.3
230 wakaba 1.1 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
231     ($tt, LBRACE_TOKEN, $t);
232    
233     $t = $tt->get_next_token
234     while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
235    
236     if ($t->{type} == LBRACE_TOKEN) {
237     $current_decls = Message::DOM::CSSStyleDeclaration->____new;
238     my $rs = Message::DOM::CSSStyleRule->____new
239     ($selectors, $current_decls);
240     push @{$current_rules}, $rs if defined $selectors;
241    
242     $state = BEFORE_DECLARATION_STATE;
243     $t = $tt->get_next_token;
244     redo S;
245     } else {
246 wakaba 1.2 $onerror->(type => 'syntax error:after selectors',
247     level => $self->{must_level},
248     token => $t);
249 wakaba 1.1
250     ## Stay in the state.
251     $t = $tt->get_next_token;
252     redo S;
253     }
254     }
255     } elsif ($state == BEFORE_DECLARATION_STATE) {
256     ## NOTE: DELIM? in declaration will be removed:
257     ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
258    
259 wakaba 1.5 my $prop_def;
260     my $prop_value;
261     my $prop_flag;
262 wakaba 1.1 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
263     if ($t->{type} == IDENT_TOKEN) { # property
264 wakaba 1.5 my $prop_name = lc $t->{value}; ## TODO: case folding
265     $t = $tt->get_next_token;
266     if ($t->{type} == COLON_TOKEN) {
267     $t = $tt->get_next_token;
268     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
269    
270     $prop_def = $Prop->{$prop_name};
271 wakaba 1.6 if ($prop_def and $self->{prop}->{$prop_name}) {
272 wakaba 1.5 ($t, $prop_value)
273     = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
274     if ($prop_value) {
275     ## NOTE: {parse} don't have to consume trailing spaces.
276     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
277    
278     if ($t->{type} == EXCLAMATION_TOKEN) {
279     $t = $tt->get_next_token;
280     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
281     if ($t->{type} == IDENT_TOKEN and
282     lc $t->{value} eq 'important') { ## TODO: case folding
283     $prop_flag = 'important';
284    
285     $t = $tt->get_next_token;
286     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
287    
288     #
289     } else {
290     $onerror->(type => 'syntax error:important',
291     level => $self->{must_level},
292     token => $t);
293    
294     ## Reprocess.
295     $state = IGNORED_DECLARATION_STATE;
296     redo S;
297     }
298     }
299    
300     #
301     } else {
302     ## Syntax error.
303    
304     ## Reprocess.
305     $state = IGNORED_DECLARATION_STATE;
306     redo S;
307     }
308     } else {
309     $onerror->(type => 'not supported:property',
310     level => $self->{unsupported_level},
311     token => $t, value => $prop_name);
312    
313     #
314     $state = IGNORED_DECLARATION_STATE;
315     redo S;
316     }
317     } else {
318     $onerror->(type => 'syntax error:property colon',
319     level => $self->{must_level},
320     token => $t);
321 wakaba 1.1
322 wakaba 1.5 #
323     $state = IGNORED_DECLARATION_STATE;
324     redo S;
325     }
326     }
327    
328     if ($t->{type} == RBRACE_TOKEN) {
329 wakaba 1.1 $t = $tt->get_next_token;
330 wakaba 1.5 $state = BEFORE_STATEMENT_STATE;
331     #redo S;
332     } elsif ($t->{type} == SEMICOLON_TOKEN) {
333 wakaba 1.1 $t = $tt->get_next_token;
334 wakaba 1.5 ## Stay in the state.
335     #redo S;
336 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
337 wakaba 1.2 $onerror->(type => 'syntax error:ruleset not closed',
338     level => $self->{must_level},
339     token => $t);
340 wakaba 1.1 ## Reprocess.
341     $state = BEFORE_STATEMENT_STATE;
342 wakaba 1.5 #redo S;
343     } else {
344     if ($prop_value) {
345     $onerror->(type => 'syntax error:property semicolon',
346     level => $self->{must_level},
347     token => $t);
348     } else {
349     $onerror->(type => 'syntax error:property name',
350     level => $self->{must_level},
351     token => $t);
352     }
353    
354     #
355     $state = IGNORED_DECLARATION_STATE;
356 wakaba 1.1 redo S;
357     }
358    
359 wakaba 1.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
360     for my $set_prop_name (keys %{$prop_value or {}}) {
361     my $set_prop_def = $Prop->{$set_prop_name};
362     $$current_decls->{$set_prop_def->{key}}
363     = [$prop_value->{$set_prop_name}, $prop_flag]
364     if $important or
365     not $$current_decls->{$set_prop_def->{key}} or
366     not defined $$current_decls->{$set_prop_def->{key}}->[1];
367 wakaba 1.5 }
368 wakaba 1.1 redo S;
369     } elsif ($state == IGNORED_STATEMENT_STATE or
370     $state == IGNORED_DECLARATION_STATE) {
371     if (@$closing_tokens) { ## Something is yet in opening state.
372     if ($t->{type} == EOF_TOKEN) {
373     @$closing_tokens = ();
374     ## Reprocess.
375     $state = $state == IGNORED_STATEMENT_STATE
376     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
377     redo S;
378     } elsif ($t->{type} == $closing_tokens->[-1]) {
379     pop @$closing_tokens;
380     if (@$closing_tokens == 0 and
381     $t->{type} == RBRACE_TOKEN and
382     $state == IGNORED_STATEMENT_STATE) {
383     $t = $tt->get_next_token;
384     $state = BEFORE_STATEMENT_STATE;
385     redo S;
386     } else {
387     $t = $tt->get_next_token;
388     ## Stay in the state.
389     redo S;
390     }
391 wakaba 1.28 } elsif ({
392     RBRACE_TOKEN, 1,
393     #RBRACKET_TOKEN, 1,
394     #RPAREN_TOKEN, 1,
395     SEMICOLON_TOKEN, 1,
396     }->{$t->{type}}) {
397     $t = $tt->get_next_token;
398     ## Stay in the state.
399     #
400 wakaba 1.1 } else {
401     #
402     }
403     } else {
404     if ($t->{type} == SEMICOLON_TOKEN) {
405     $t = $tt->get_next_token;
406     $state = $state == IGNORED_STATEMENT_STATE
407     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
408     redo S;
409 wakaba 1.28 } elsif ($t->{type} == RBRACE_TOKEN) {
410     if ($state == IGNORED_DECLARATION_STATE) {
411     $t = $tt->get_next_token;
412     $state = BEFORE_STATEMENT_STATE;
413     redo S;
414     } else {
415     ## NOTE: Maybe this state cannot be reached.
416     $t = $tt->get_next_token;
417     ## Stay in the state.
418     redo S;
419     }
420 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
421     ## Reprocess.
422     $state = $state == IGNORED_STATEMENT_STATE
423     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
424     redo S;
425 wakaba 1.28 #} elsif ($t->{type} == RBRACKET_TOKEN or $t->{type} == RPAREN_TOKEN) {
426     # $t = $tt->get_next_token;
427     # ## Stay in the state.
428     # #
429 wakaba 1.1 } else {
430     #
431     }
432     }
433    
434     while (not {
435     EOF_TOKEN, 1,
436     RBRACE_TOKEN, 1,
437 wakaba 1.28 ## NOTE: ']' and ')' are disabled for browser compatibility.
438     #RBRACKET_TOKEN, 1,
439     #RPAREN_TOKEN, 1,
440 wakaba 1.1 SEMICOLON_TOKEN, 1,
441     }->{$t->{type}}) {
442     if ($t->{type} == LBRACE_TOKEN) {
443     push @$closing_tokens, RBRACE_TOKEN;
444 wakaba 1.28 #} elsif ($t->{type} == LBRACKET_TOKEN) {
445     # push @$closing_tokens, RBRACKET_TOKEN;
446     #} elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
447     # push @$closing_tokens, RPAREN_TOKEN;
448 wakaba 1.1 }
449    
450     $t = $tt->get_next_token;
451     }
452    
453     #
454     ## Stay in the state.
455     redo S;
456     } else {
457     die "$0: parse_char_string: Unknown state: $state";
458     }
459     } # S
460    
461     my $ss = Message::DOM::CSSStyleSheet->____new
462 wakaba 1.11 (manakai_base_uri => $self->{base_uri},
463     css_rules => $open_rules->[0],
464 wakaba 1.1 ## TODO: href
465     ## TODO: owner_node
466     ## TODO: media
467     type => 'text/css', ## TODO: OK?
468     _parser => $self);
469     return $ss;
470     } # parse_char_string
471    
472 wakaba 1.9 my $compute_as_specified = sub ($$$$) {
473     #my ($self, $element, $prop_name, $specified_value) = @_;
474     return $_[3];
475     }; # $compute_as_specified
476    
477 wakaba 1.11 my $default_serializer = sub {
478     my ($self, $prop_name, $value) = @_;
479 wakaba 1.15 if ($value->[0] eq 'NUMBER' or $value->[0] eq 'WEIGHT') {
480     ## TODO: What we currently do for 'font-weight' is different from
481     ## any browser for lighter/bolder cases. We need to fix this, but
482     ## how?
483 wakaba 1.11 return $value->[1]; ## TODO: big or small number cases?
484 wakaba 1.18 } elsif ($value->[0] eq 'DIMENSION') {
485     return $value->[1] . $value->[2]; ## NOTE: This is what browsers do.
486 wakaba 1.22 } elsif ($value->[0] eq 'PERCENTAGE') {
487     return $value->[1] . '%';
488 wakaba 1.11 } elsif ($value->[0] eq 'KEYWORD') {
489     return $value->[1];
490     } elsif ($value->[0] eq 'URI') {
491     ## NOTE: This is what browsers do.
492     return 'url('.$value->[1].')';
493 wakaba 1.28 } elsif ($value->[0] eq 'RGBA') {
494     if ($value->[4] == 1) {
495     return 'rgb('.$value->[1].', '.$value->[2].', '.$value->[3].')';
496     } elsif ($value->[4] == 0) {
497     ## TODO: check what browsers do...
498     return 'transparent';
499     } else {
500     return 'rgba('.$value->[1].', '.$value->[2].', '.$value->[3].', '
501     .$value->[4].')';
502     }
503 wakaba 1.11 } elsif ($value->[0] eq 'INHERIT') {
504     return 'inherit';
505 wakaba 1.16 } elsif ($value->[0] eq 'DECORATION') {
506     my @v = ();
507     push @v, 'underline' if $value->[1];
508     push @v, 'overline' if $value->[2];
509     push @v, 'line-through' if $value->[3];
510     push @v, 'blink' if $value->[4];
511     return 'none' unless @v;
512     return join ' ', @v;
513 wakaba 1.11 } else {
514     return undef;
515     }
516     }; # $default_serializer
517    
518 wakaba 1.28 my $x11_colors = {
519     'aliceblue' => [0xf0, 0xf8, 0xff],
520     'antiquewhite' => [0xfa, 0xeb, 0xd7],
521     'aqua' => [0x00, 0xff, 0xff],
522     'aquamarine' => [0x7f, 0xff, 0xd4],
523     'azure' => [0xf0, 0xff, 0xff],
524     'beige' => [0xf5, 0xf5, 0xdc],
525     'bisque' => [0xff, 0xe4, 0xc4],
526     'black' => [0x00, 0x00, 0x00],
527     'blanchedalmond' => [0xff, 0xeb, 0xcd],
528     'blue' => [0x00, 0x00, 0xff],
529     'blueviolet' => [0x8a, 0x2b, 0xe2],
530     'brown' => [0xa5, 0x2a, 0x2a],
531     'burlywood' => [0xde, 0xb8, 0x87],
532     'cadetblue' => [0x5f, 0x9e, 0xa0],
533     'chartreuse' => [0x7f, 0xff, 0x00],
534     'chocolate' => [0xd2, 0x69, 0x1e],
535     'coral' => [0xff, 0x7f, 0x50],
536     'cornflowerblue' => [0x64, 0x95, 0xed],
537     'cornsilk' => [0xff, 0xf8, 0xdc],
538     'crimson' => [0xdc, 0x14, 0x3c],
539     'cyan' => [0x00, 0xff, 0xff],
540     'darkblue' => [0x00, 0x00, 0x8b],
541     'darkcyan' => [0x00, 0x8b, 0x8b],
542     'darkgoldenrod' => [0xb8, 0x86, 0x0b],
543     'darkgray' => [0xa9, 0xa9, 0xa9],
544     'darkgreen' => [0x00, 0x64, 0x00],
545     'darkgrey' => [0xa9, 0xa9, 0xa9],
546     'darkkhaki' => [0xbd, 0xb7, 0x6b],
547     'darkmagenta' => [0x8b, 0x00, 0x8b],
548     'darkolivegreen' => [0x55, 0x6b, 0x2f],
549     'darkorange' => [0xff, 0x8c, 0x00],
550     'darkorchid' => [0x99, 0x32, 0xcc],
551     'darkred' => [0x8b, 0x00, 0x00],
552     'darksalmon' => [0xe9, 0x96, 0x7a],
553     'darkseagreen' => [0x8f, 0xbc, 0x8f],
554     'darkslateblue' => [0x48, 0x3d, 0x8b],
555     'darkslategray' => [0x2f, 0x4f, 0x4f],
556     'darkslategrey' => [0x2f, 0x4f, 0x4f],
557     'darkturquoise' => [0x00, 0xce, 0xd1],
558     'darkviolet' => [0x94, 0x00, 0xd3],
559     'deeppink' => [0xff, 0x14, 0x93],
560     'deepskyblue' => [0x00, 0xbf, 0xff],
561     'dimgray' => [0x69, 0x69, 0x69],
562     'dimgrey' => [0x69, 0x69, 0x69],
563     'dodgerblue' => [0x1e, 0x90, 0xff],
564     'firebrick' => [0xb2, 0x22, 0x22],
565     'floralwhite' => [0xff, 0xfa, 0xf0],
566     'forestgreen' => [0x22, 0x8b, 0x22],
567     'fuchsia' => [0xff, 0x00, 0xff],
568     'gainsboro' => [0xdc, 0xdc, 0xdc],
569     'ghostwhite' => [0xf8, 0xf8, 0xff],
570     'gold' => [0xff, 0xd7, 0x00],
571     'goldenrod' => [0xda, 0xa5, 0x20],
572     'gray' => [0x80, 0x80, 0x80],
573     'green' => [0x00, 0x80, 0x00],
574     'greenyellow' => [0xad, 0xff, 0x2f],
575     'grey' => [0x80, 0x80, 0x80],
576     'honeydew' => [0xf0, 0xff, 0xf0],
577     'hotpink' => [0xff, 0x69, 0xb4],
578     'indianred' => [0xcd, 0x5c, 0x5c],
579     'indigo' => [0x4b, 0x00, 0x82],
580     'ivory' => [0xff, 0xff, 0xf0],
581     'khaki' => [0xf0, 0xe6, 0x8c],
582     'lavender' => [0xe6, 0xe6, 0xfa],
583     'lavenderblush' => [0xff, 0xf0, 0xf5],
584     'lawngreen' => [0x7c, 0xfc, 0x00],
585     'lemonchiffon' => [0xff, 0xfa, 0xcd],
586     'lightblue' => [0xad, 0xd8, 0xe6],
587     'lightcoral' => [0xf0, 0x80, 0x80],
588     'lightcyan' => [0xe0, 0xff, 0xff],
589     'lightgoldenrodyellow' => [0xfa, 0xfa, 0xd2],
590     'lightgray' => [0xd3, 0xd3, 0xd3],
591     'lightgreen' => [0x90, 0xee, 0x90],
592     'lightgrey' => [0xd3, 0xd3, 0xd3],
593     'lightpink' => [0xff, 0xb6, 0xc1],
594     'lightsalmon' => [0xff, 0xa0, 0x7a],
595     'lightseagreen' => [0x20, 0xb2, 0xaa],
596     'lightskyblue' => [0x87, 0xce, 0xfa],
597     'lightslategray' => [0x77, 0x88, 0x99],
598     'lightslategrey' => [0x77, 0x88, 0x99],
599     'lightsteelblue' => [0xb0, 0xc4, 0xde],
600     'lightyellow' => [0xff, 0xff, 0xe0],
601     'lime' => [0x00, 0xff, 0x00],
602     'limegreen' => [0x32, 0xcd, 0x32],
603     'linen' => [0xfa, 0xf0, 0xe6],
604     'magenta' => [0xff, 0x00, 0xff],
605     'maroon' => [0x80, 0x00, 0x00],
606     'mediumaquamarine' => [0x66, 0xcd, 0xaa],
607     'mediumblue' => [0x00, 0x00, 0xcd],
608     'mediumorchid' => [0xba, 0x55, 0xd3],
609     'mediumpurple' => [0x93, 0x70, 0xdb],
610     'mediumseagreen' => [0x3c, 0xb3, 0x71],
611     'mediumslateblue' => [0x7b, 0x68, 0xee],
612     'mediumspringgreen' => [0x00, 0xfa, 0x9a],
613     'mediumturquoise' => [0x48, 0xd1, 0xcc],
614     'mediumvioletred' => [0xc7, 0x15, 0x85],
615     'midnightblue' => [0x19, 0x19, 0x70],
616     'mintcream' => [0xf5, 0xff, 0xfa],
617     'mistyrose' => [0xff, 0xe4, 0xe1],
618     'moccasin' => [0xff, 0xe4, 0xb5],
619     'navajowhite' => [0xff, 0xde, 0xad],
620     'navy' => [0x00, 0x00, 0x80],
621     'oldlace' => [0xfd, 0xf5, 0xe6],
622     'olive' => [0x80, 0x80, 0x00],
623     'olivedrab' => [0x6b, 0x8e, 0x23],
624     'orange' => [0xff, 0xa5, 0x00],
625     'orangered' => [0xff, 0x45, 0x00],
626     'orchid' => [0xda, 0x70, 0xd6],
627     'palegoldenrod' => [0xee, 0xe8, 0xaa],
628     'palegreen' => [0x98, 0xfb, 0x98],
629     'paleturquoise' => [0xaf, 0xee, 0xee],
630     'palevioletred' => [0xdb, 0x70, 0x93],
631     'papayawhip' => [0xff, 0xef, 0xd5],
632     'peachpuff' => [0xff, 0xda, 0xb9],
633     'peru' => [0xcd, 0x85, 0x3f],
634     'pink' => [0xff, 0xc0, 0xcb],
635     'plum' => [0xdd, 0xa0, 0xdd],
636     'powderblue' => [0xb0, 0xe0, 0xe6],
637     'purple' => [0x80, 0x00, 0x80],
638     'red' => [0xff, 0x00, 0x00],
639     'rosybrown' => [0xbc, 0x8f, 0x8f],
640     'royalblue' => [0x41, 0x69, 0xe1],
641     'saddlebrown' => [0x8b, 0x45, 0x13],
642     'salmon' => [0xfa, 0x80, 0x72],
643     'sandybrown' => [0xf4, 0xa4, 0x60],
644     'seagreen' => [0x2e, 0x8b, 0x57],
645     'seashell' => [0xff, 0xf5, 0xee],
646     'sienna' => [0xa0, 0x52, 0x2d],
647     'silver' => [0xc0, 0xc0, 0xc0],
648     'skyblue' => [0x87, 0xce, 0xeb],
649     'slateblue' => [0x6a, 0x5a, 0xcd],
650     'slategray' => [0x70, 0x80, 0x90],
651     'slategrey' => [0x70, 0x80, 0x90],
652     'snow' => [0xff, 0xfa, 0xfa],
653     'springgreen' => [0x00, 0xff, 0x7f],
654     'steelblue' => [0x46, 0x82, 0xb4],
655     'tan' => [0xd2, 0xb4, 0x8c],
656     'teal' => [0x00, 0x80, 0x80],
657     'thistle' => [0xd8, 0xbf, 0xd8],
658     'tomato' => [0xff, 0x63, 0x47],
659     'turquoise' => [0x40, 0xe0, 0xd0],
660     'violet' => [0xee, 0x82, 0xee],
661     'wheat' => [0xf5, 0xde, 0xb3],
662     'white' => [0xff, 0xff, 0xff],
663     'whitesmoke' => [0xf5, 0xf5, 0xf5],
664     'yellow' => [0xff, 0xff, 0x00],
665     'yellowgreen' => [0x9a, 0xcd, 0x32],
666     }; # $x11_colors
667    
668     my $system_colors = {
669     activeborder => 1, activecaption => 1, appworkspace => 1, background => 1,
670     buttonface => 1, buttonhighlight => 1, buttonshadow => 1, buttontext => 1,
671     captiontext => 1, graytext => 1, highlight => 1, highlighttext => 1,
672     inactiveborder => 1, inactivecaption => 1, inactivecaptiontext => 1,
673     infobackground => 1, infotext => 1, menu => 1, menutext => 1,
674     scrollbar => 1, threeddarkshadow => 1, threedface => 1, threedhighlight => 1,
675     threedlightshadow => 1, threedshadow => 1, window => 1, windowframe => 1,
676     windowtext => 1,
677     }; # $system_colors
678    
679     my $parse_color = sub {
680     my ($self, $prop_name, $tt, $t, $onerror) = @_;
681    
682     ## See
683     ## <http://suika.fam.cx/gate/2005/sw/%3Ccolor%3E>,
684     ## <http://suika.fam.cx/gate/2005/sw/rgb>,
685     ## <http://suika.fam.cx/gate/2005/sw/-moz-rgba>,
686     ## <http://suika.fam.cx/gate/2005/sw/hsl>,
687     ## <http://suika.fam.cx/gate/2005/sw/-moz-hsla>, and
688     ## <http://suika.fam.cx/gate/2005/sw/color>
689     ## for browser compatibility issue.
690    
691     ## NOTE: Implementing CSS3 Color CR (2003), except for attr(),
692     ## rgba(), and hsla().
693     ## NOTE: rgb(...{EOF} is not supported (only Opera does).
694    
695     if ($t->{type} == IDENT_TOKEN) {
696     my $value = lc $t->{value}; ## TODO: case
697     if ($x11_colors->{$value} or
698     $system_colors->{$value}) {
699     $t = $tt->get_next_token;
700     return ($t, {$prop_name => ['KEYWORD', $value]});
701     } elsif ({
702     transparent => 1, ## For 'background-color' in CSS2.1, everywhre in CSS3.
703     flavor => 1, ## CSS3.
704     invert => 1, ## For 'outline-color' in CSS2.1.
705     '-moz-use-text-color' => 1, ## For <border-color> in Gecko.
706     '-manakai-default' => 1, ## CSS2.1 initial for 'color'
707     '-manakai-invert-or-currentcolor' => 1, ## CSS2.1 initial4'outline-color'
708     }->{$value} and $self->{prop_value}->{$prop_name}->{$value}) {
709     $t = $tt->get_next_token;
710     return ($t, {$prop_name => ['KEYWORD', $value]});
711     } elsif ($value eq 'currentcolor' or $value eq '-moz-use-text-color') {
712     $t = $tt->get_next_token;
713     if ($prop_name eq 'color') {
714     return ($t, {$prop_name => ['INHERIT']});
715     } else {
716     return ($t, {$prop_name => ['KEYWORD', $value]});
717     }
718     } elsif ($value eq 'inherit') {
719     $t = $tt->get_next_token;
720     return ($t, {$prop_name => ['INHERIT']});
721     }
722     }
723    
724     if ($t->{type} == HASH_TOKEN or
725     ($self->{hashless_rgb} and $t->{type} == IDENT_TOKEN)) {
726     my $v = lc $t->{value}; ## TODO: case
727     if ($v =~ /\A([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/) {
728     $t = $tt->get_next_token;
729     return ($t, {$prop_name => ['RGBA', hex $1, hex $2, hex $3, 1]});
730     } elsif ($v =~ /\A([0-9a-f])([0-9a-f])([0-9a-f])\z/) {
731     $t = $tt->get_next_token;
732     return ($t, {$prop_name => ['RGBA', hex $1.$1, hex $2.$2,
733     hex $3.$3, 1]});
734     }
735     }
736    
737     if ($t->{type} == FUNCTION_TOKEN) {
738     my $func = lc $t->{value}; ## TODO: case
739     if ($func eq 'rgb') {
740     $t = $tt->get_next_token;
741     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
742     my $sign = 1;
743     if ($t->{type} == MINUS_TOKEN) {
744     $sign = -1;
745     $t = $tt->get_next_token;
746     }
747     if ($t->{type} == NUMBER_TOKEN) {
748     my $r = $t->{number} * $sign;
749     $t = $tt->get_next_token;
750     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
751     if ($t->{type} == COMMA_TOKEN) {
752     $t = $tt->get_next_token;
753     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
754     $sign = 1;
755     if ($t->{type} == MINUS_TOKEN) {
756     $sign = -1;
757     $t = $tt->get_next_token;
758     }
759     if ($t->{type} == NUMBER_TOKEN) {
760     my $g = $t->{number} * $sign;
761     $t = $tt->get_next_token;
762     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
763     if ($t->{type} == COMMA_TOKEN) {
764     $t = $tt->get_next_token;
765     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
766     $sign = 1;
767     if ($t->{type} == MINUS_TOKEN) {
768     $sign = -1;
769     $t = $tt->get_next_token;
770     }
771     if ($t->{type} == NUMBER_TOKEN) {
772     my $b = $t->{number} * $sign;
773     $t = $tt->get_next_token;
774     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
775     if ($t->{type} == RPAREN_TOKEN) {
776     $t = $tt->get_next_token;
777     return ($t,
778     {$prop_name =>
779     $self->{clip_color}->($self,
780     ['RGBA', $r, $g, $b, 1])});
781     }
782     }
783     }
784     }
785     }
786     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
787     my $r = $t->{number} * 255 / 100 * $sign;
788     $t = $tt->get_next_token;
789     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
790     if ($t->{type} == COMMA_TOKEN) {
791     $t = $tt->get_next_token;
792     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
793     $sign = 1;
794     if ($t->{type} == MINUS_TOKEN) {
795     $sign = -1;
796     $t = $tt->get_next_token;
797     }
798     if ($t->{type} == PERCENTAGE_TOKEN) {
799     my $g = $t->{number} * 255 / 100 * $sign;
800     $t = $tt->get_next_token;
801     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
802     if ($t->{type} == COMMA_TOKEN) {
803     $t = $tt->get_next_token;
804     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
805     $sign = 1;
806     if ($t->{type} == MINUS_TOKEN) {
807     $sign = -1;
808     $t = $tt->get_next_token;
809     }
810     if ($t->{type} == PERCENTAGE_TOKEN) {
811     my $b = $t->{number} * 255 / 100 * $sign;
812     $t = $tt->get_next_token;
813     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
814     if ($t->{type} == RPAREN_TOKEN) {
815     $t = $tt->get_next_token;
816     return ($t,
817     {$prop_name =>
818     $self->{clip_color}->($self,
819     ['RGBA', $r, $g, $b, 1])});
820     }
821     }
822     }
823     }
824     }
825     }
826     } elsif ($func eq 'hsl') {
827     $t = $tt->get_next_token;
828     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
829     my $sign = 1;
830     if ($t->{type} == MINUS_TOKEN) {
831     $sign = -1;
832     $t = $tt->get_next_token;
833     }
834     if ($t->{type} == NUMBER_TOKEN) {
835     my $h = (((($t->{number} * $sign) % 360) + 360) % 360) / 360;
836     $t = $tt->get_next_token;
837     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
838     if ($t->{type} == COMMA_TOKEN) {
839     $t = $tt->get_next_token;
840     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
841     $sign = 1;
842     if ($t->{type} == MINUS_TOKEN) {
843     $sign = -1;
844     $t = $tt->get_next_token;
845     }
846     if ($t->{type} == PERCENTAGE_TOKEN) {
847     my $s = $t->{number} * $sign / 100;
848     $s = 0 if $s < 0;
849     $s = 1 if $s > 1;
850     $t = $tt->get_next_token;
851     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
852     if ($t->{type} == COMMA_TOKEN) {
853     $t = $tt->get_next_token;
854     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
855     $sign = 1;
856     if ($t->{type} == MINUS_TOKEN) {
857     $sign = -1;
858     $t = $tt->get_next_token;
859     }
860     if ($t->{type} == PERCENTAGE_TOKEN) {
861     my $l = $t->{number} * $sign / 100;
862     $l = 0 if $l < 0;
863     $l = 1 if $l > 1;
864     $t = $tt->get_next_token;
865     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
866     if ($t->{type} == RPAREN_TOKEN) {
867     my $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
868     my $m1 = $l * 2 - $m2;
869     my $hue2rgb = sub ($$$) {
870     my ($m1, $m2, $h) = @_;
871     $h++ if $h < 0;
872     $h-- if $h > 1;
873     return $m1 + ($m2 - $m1) * $h * 6 if $h * 6 < 1;
874     return $m2 if $h * 2 < 1;
875     return $m1 + ($m2 - $m1) * (2/3 - $h) * 6 if $h * 3 < 2;
876     return $m1;
877     };
878     $t = $tt->get_next_token;
879     return ($t,
880     {$prop_name =>
881     $self->{clip_color}
882     ->($self,
883     ['RGBA',
884     $hue2rgb->($m1, $m2, $h + 1/3),
885     $hue2rgb->($m1, $m2, $h),
886     $hue2rgb->($m1, $m2, $h - 1/3), 1])});
887     }
888     }
889     }
890     }
891     }
892     }
893     }
894     }
895    
896     $onerror->(type => 'syntax error:'.$prop_name,
897     level => $self->{must_level},
898     token => $t);
899    
900     return ($t, undef);
901     }; # $parse_color
902    
903 wakaba 1.5 $Prop->{color} = {
904     css => 'color',
905     dom => 'color',
906     key => 'color',
907 wakaba 1.28 parse => $parse_color,
908     serialize => $default_serializer,
909     initial => ['KEYWORD', '-manakai-default'],
910     inherited => 1,
911     compute => sub ($$$$) {
912     my ($self, $element, $prop_name, $specified_value) = @_;
913 wakaba 1.5
914 wakaba 1.28 if (defined $specified_value) {
915     if ($specified_value->[0] eq 'KEYWORD') {
916     if ($x11_colors->{$specified_value->[1]}) {
917     return ['RGBA', @{$x11_colors->{$specified_value->[1]}}, 1];
918     } elsif ($specified_value->[1] eq 'transparent') {
919     return ['RGBA', 0, 0, 0, 0];
920     } elsif ($specified_value->[1] eq 'currentcolor' or
921     $specified_value->[1] eq '-moz-use-text-color' or
922     ($specified_value->[1] eq '-manakai-invert-or-currentcolor'and
923     not $self->{has_invert})) {
924     unless ($prop_name eq 'color') {
925     return $self->get_computed_value ($element, 'color');
926     } else {
927     ## NOTE: This is an error, since it should have been
928     ## converted to 'inherit' at parse time.
929     return ['KEYWORD', '-manakai-default'];
930     }
931     } elsif ($specified_value->[1] eq '-manakai-invert-or-currentcolor') {
932     return ['KEYWORD', 'invert'];
933     }
934 wakaba 1.5 }
935     }
936    
937 wakaba 1.28 return $specified_value;
938 wakaba 1.5 },
939     };
940     $Attr->{color} = $Prop->{color};
941     $Key->{color} = $Prop->{color};
942    
943 wakaba 1.28 $Prop->{'background-color'} = {
944     css => 'background-color',
945     dom => 'background_color',
946     key => 'background_color',
947     parse => $parse_color,
948     serialize => $default_serializer,
949     initial => ['KEYWORD', 'transparent'],
950     #inherited => 0,
951     compute => $Prop->{color}->{compute},
952     };
953     $Attr->{background_color} = $Prop->{'background-color'};
954     $Key->{background_color} = $Prop->{'background-color'};
955    
956     $Prop->{'border-top-color'} = {
957     css => 'border-top-color',
958     dom => 'border_top_color',
959     key => 'border_top_color',
960     parse => $parse_color,
961     serialize => $default_serializer,
962     initial => ['KEYWORD', 'currentcolor'],
963     #inherited => 0,
964     compute => $Prop->{color}->{compute},
965     };
966     $Attr->{border_top_color} = $Prop->{'border-top-color'};
967     $Key->{border_top_color} = $Prop->{'border-top-color'};
968    
969     $Prop->{'border-right-color'} = {
970     css => 'border-right-color',
971     dom => 'border_right_color',
972     key => 'border_right_color',
973     parse => $parse_color,
974     serialize => $default_serializer,
975     initial => ['KEYWORD', 'currentcolor'],
976     #inherited => 0,
977     compute => $Prop->{color}->{compute},
978     };
979     $Attr->{border_right} = $Prop->{'border-right-color'};
980     $Key->{border_right} = $Prop->{'border-right-color'};
981    
982     $Prop->{'border-bottom-color'} = {
983     css => 'border-bottom-color',
984     dom => 'border_bottom_color',
985     key => 'border_bottom_color',
986     parse => $parse_color,
987     serialize => $default_serializer,
988     initial => ['KEYWORD', 'currentcolor'],
989     #inherited => 0,
990     compute => $Prop->{color}->{compute},
991     };
992     $Attr->{border_bottom_color} = $Prop->{'border-bottom-color'};
993     $Key->{border_bottom_color} = $Prop->{'border-bottom-color'};
994    
995     $Prop->{'border-left-color'} = {
996     css => 'border-left-color',
997     dom => 'border_left_color',
998     key => 'border_left_color',
999     parse => $parse_color,
1000     serialize => $default_serializer,
1001     initial => ['KEYWORD', 'currentcolor'],
1002     #inherited => 0,
1003     compute => $Prop->{color}->{compute},
1004     };
1005     $Attr->{border_left_color} = $Prop->{'border-left-color'};
1006     $Key->{border_left_color} = $Prop->{'border-left-color'};
1007    
1008     $Prop->{'outline-color'} = {
1009     css => 'outline-color',
1010     dom => 'outline_color',
1011     key => 'outline_color',
1012     parse => $parse_color,
1013     serialize => $default_serializer,
1014     initial => ['KEYWORD', '-manakai-invert-or-currentcolor'],
1015     #inherited => 0,
1016     compute => $Prop->{color}->{compute},
1017     };
1018     $Attr->{outline_color} = $Prop->{'outline-color'};
1019     $Key->{outline_color} = $Prop->{'outline-color'};
1020    
1021 wakaba 1.6 my $one_keyword_parser = sub {
1022     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1023    
1024     if ($t->{type} == IDENT_TOKEN) {
1025     my $prop_value = lc $t->{value}; ## TODO: case folding
1026     $t = $tt->get_next_token;
1027     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
1028     $self->{prop_value}->{$prop_name}->{$prop_value}) {
1029 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
1030 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
1031 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
1032 wakaba 1.6 }
1033     }
1034    
1035 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
1036 wakaba 1.6 level => $self->{must_level},
1037     token => $t);
1038     return ($t, undef);
1039     };
1040    
1041     $Prop->{display} = {
1042     css => 'display',
1043     dom => 'display',
1044     key => 'display',
1045     parse => $one_keyword_parser,
1046 wakaba 1.11 serialize => $default_serializer,
1047 wakaba 1.6 keyword => {
1048     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
1049     'list-item' => 1, none => 1,
1050     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
1051     'table-column-group' => 1, 'table-header-group' => 1,
1052     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
1053     },
1054 wakaba 1.9 initial => ["KEYWORD", "inline"],
1055     #inherited => 0,
1056     compute => sub {
1057     my ($self, $element, $prop_name, $specified_value) = @_;
1058     ## NOTE: CSS 2.1 Section 9.7.
1059    
1060     ## WARNING: |compute| for 'float' property invoke this CODE
1061     ## in some case. Careless modification might cause a infinite loop.
1062    
1063 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1064 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1065     ## Case 1 [CSS 2.1]
1066     return $specified_value;
1067     } else {
1068     my $position = $self->get_computed_value ($element, 'position');
1069     if ($position->[0] eq 'KEYWORD' and
1070     ($position->[1] eq 'absolute' or
1071     $position->[1] eq 'fixed')) {
1072     ## Case 2 [CSS 2.1]
1073     #
1074     } else {
1075     my $float = $self->get_computed_value ($element, 'float');
1076     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
1077     ## Caes 3 [CSS 2.1]
1078     #
1079     } elsif (not defined $element->manakai_parent_element) {
1080     ## Case 4 [CSS 2.1]
1081     #
1082     } else {
1083     ## Case 5 [CSS 2.1]
1084     return $specified_value;
1085     }
1086     }
1087    
1088     return ["KEYWORD",
1089     {
1090     'inline-table' => 'table',
1091     inline => 'block',
1092     'run-in' => 'block',
1093     'table-row-group' => 'block',
1094     'table-column' => 'block',
1095     'table-column-group' => 'block',
1096     'table-header-group' => 'block',
1097     'table-footer-group' => 'block',
1098     'table-row' => 'block',
1099     'table-cell' => 'block',
1100     'table-caption' => 'block',
1101     'inline-block' => 'block',
1102     }->{$specified_value->[1]} || $specified_value->[1]];
1103     }
1104     } else {
1105     return $specified_value; ## Maybe an error of the implementation.
1106     }
1107     },
1108 wakaba 1.6 };
1109     $Attr->{display} = $Prop->{display};
1110     $Key->{display} = $Prop->{display};
1111    
1112     $Prop->{position} = {
1113     css => 'position',
1114     dom => 'position',
1115     key => 'position',
1116     parse => $one_keyword_parser,
1117 wakaba 1.11 serialize => $default_serializer,
1118 wakaba 1.6 keyword => {
1119     static => 1, relative => 1, absolute => 1, fixed => 1,
1120     },
1121 wakaba 1.10 initial => ["KEYWORD", "static"],
1122 wakaba 1.9 #inherited => 0,
1123     compute => $compute_as_specified,
1124 wakaba 1.6 };
1125     $Attr->{position} = $Prop->{position};
1126     $Key->{position} = $Prop->{position};
1127    
1128     $Prop->{float} = {
1129     css => 'float',
1130     dom => 'css_float',
1131     key => 'float',
1132     parse => $one_keyword_parser,
1133 wakaba 1.11 serialize => $default_serializer,
1134 wakaba 1.6 keyword => {
1135     left => 1, right => 1, none => 1,
1136     },
1137 wakaba 1.9 initial => ["KEYWORD", "none"],
1138     #inherited => 0,
1139     compute => sub {
1140     my ($self, $element, $prop_name, $specified_value) = @_;
1141     ## NOTE: CSS 2.1 Section 9.7.
1142    
1143     ## WARNING: |compute| for 'display' property invoke this CODE
1144     ## in some case. Careless modification might cause a infinite loop.
1145    
1146 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1147 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1148     ## Case 1 [CSS 2.1]
1149     return $specified_value;
1150     } else {
1151     my $position = $self->get_computed_value ($element, 'position');
1152     if ($position->[0] eq 'KEYWORD' and
1153     ($position->[1] eq 'absolute' or
1154     $position->[1] eq 'fixed')) {
1155     ## Case 2 [CSS 2.1]
1156     return ["KEYWORD", "none"];
1157     }
1158     }
1159     }
1160    
1161     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
1162     ## on computed value of 'float' property.
1163    
1164     ## Case 3, 4, and 5 [CSS 2.1]
1165     return $specified_value;
1166     },
1167 wakaba 1.6 };
1168     $Attr->{css_float} = $Prop->{float};
1169     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
1170     $Key->{float} = $Prop->{float};
1171    
1172     $Prop->{clear} = {
1173     css => 'clear',
1174     dom => 'clear',
1175     key => 'clear',
1176     parse => $one_keyword_parser,
1177 wakaba 1.11 serialize => $default_serializer,
1178 wakaba 1.6 keyword => {
1179     left => 1, right => 1, none => 1, both => 1,
1180     },
1181 wakaba 1.9 initial => ["KEYWORD", "none"],
1182     #inherited => 0,
1183     compute => $compute_as_specified,
1184 wakaba 1.6 };
1185     $Attr->{clear} = $Prop->{clear};
1186     $Key->{clear} = $Prop->{clear};
1187    
1188     $Prop->{direction} = {
1189     css => 'direction',
1190     dom => 'direction',
1191     key => 'direction',
1192     parse => $one_keyword_parser,
1193 wakaba 1.11 serialize => $default_serializer,
1194 wakaba 1.6 keyword => {
1195     ltr => 1, rtl => 1,
1196     },
1197 wakaba 1.9 initial => ["KEYWORD", "ltr"],
1198     inherited => 1,
1199     compute => $compute_as_specified,
1200 wakaba 1.6 };
1201     $Attr->{direction} = $Prop->{direction};
1202     $Key->{direction} = $Prop->{direction};
1203    
1204     $Prop->{'unicode-bidi'} = {
1205     css => 'unicode-bidi',
1206     dom => 'unicode_bidi',
1207     key => 'unicode_bidi',
1208     parse => $one_keyword_parser,
1209 wakaba 1.11 serialize => $default_serializer,
1210 wakaba 1.6 keyword => {
1211     normal => 1, embed => 1, 'bidi-override' => 1,
1212     },
1213 wakaba 1.9 initial => ["KEYWORD", "normal"],
1214     #inherited => 0,
1215     compute => $compute_as_specified,
1216 wakaba 1.6 };
1217     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
1218     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
1219    
1220 wakaba 1.11 $Prop->{overflow} = {
1221     css => 'overflow',
1222     dom => 'overflow',
1223     key => 'overflow',
1224     parse => $one_keyword_parser,
1225     serialize => $default_serializer,
1226     keyword => {
1227     visible => 1, hidden => 1, scroll => 1, auto => 1,
1228     },
1229     initial => ["KEYWORD", "visible"],
1230     #inherited => 0,
1231     compute => $compute_as_specified,
1232     };
1233     $Attr->{overflow} = $Prop->{overflow};
1234     $Key->{overflow} = $Prop->{overflow};
1235    
1236     $Prop->{visibility} = {
1237     css => 'visibility',
1238     dom => 'visibility',
1239     key => 'visibility',
1240     parse => $one_keyword_parser,
1241     serialize => $default_serializer,
1242     keyword => {
1243     visible => 1, hidden => 1, collapse => 1,
1244     },
1245     initial => ["KEYWORD", "visible"],
1246     #inherited => 0,
1247     compute => $compute_as_specified,
1248     };
1249     $Attr->{visibility} = $Prop->{visibility};
1250     $Key->{visibility} = $Prop->{visibility};
1251    
1252     $Prop->{'list-style-type'} = {
1253     css => 'list-style-type',
1254     dom => 'list_style_type',
1255     key => 'list_style_type',
1256     parse => $one_keyword_parser,
1257     serialize => $default_serializer,
1258     keyword => {
1259     qw/
1260     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
1261     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
1262     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
1263     none 1
1264     /,
1265     },
1266     initial => ["KEYWORD", 'disc'],
1267     inherited => 1,
1268     compute => $compute_as_specified,
1269     };
1270     $Attr->{list_style_type} = $Prop->{'list-style-type'};
1271     $Key->{list_style_type} = $Prop->{'list-style-type'};
1272    
1273     $Prop->{'list-style-position'} = {
1274     css => 'list-style-position',
1275     dom => 'list_style_position',
1276     key => 'list_style_position',
1277     parse => $one_keyword_parser,
1278     serialize => $default_serializer,
1279     keyword => {
1280     inside => 1, outside => 1,
1281     },
1282     initial => ["KEYWORD", 'outside'],
1283     inherited => 1,
1284     compute => $compute_as_specified,
1285     };
1286     $Attr->{list_style_position} = $Prop->{'list-style-position'};
1287     $Key->{list_style_position} = $Prop->{'list-style-position'};
1288    
1289 wakaba 1.12 $Prop->{'page-break-before'} = {
1290     css => 'page-break-before',
1291     dom => 'page_break_before',
1292     key => 'page_break_before',
1293     parse => $one_keyword_parser,
1294     serialize => $default_serializer,
1295     keyword => {
1296     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1297     },
1298     initial => ["KEYWORD", 'auto'],
1299 wakaba 1.15 #inherited => 0,
1300 wakaba 1.12 compute => $compute_as_specified,
1301     };
1302     $Attr->{page_break_before} = $Prop->{'page-break-before'};
1303     $Key->{page_break_before} = $Prop->{'page-break-before'};
1304    
1305     $Prop->{'page-break-after'} = {
1306     css => 'page-break-after',
1307     dom => 'page_break_after',
1308     key => 'page_break_after',
1309     parse => $one_keyword_parser,
1310     serialize => $default_serializer,
1311     keyword => {
1312     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1313     },
1314     initial => ["KEYWORD", 'auto'],
1315 wakaba 1.15 #inherited => 0,
1316 wakaba 1.12 compute => $compute_as_specified,
1317     };
1318     $Attr->{page_break_after} = $Prop->{'page-break-after'};
1319     $Key->{page_break_after} = $Prop->{'page-break-after'};
1320    
1321     $Prop->{'page-break-inside'} = {
1322     css => 'page-break-inside',
1323     dom => 'page_break_inside',
1324     key => 'page_break_inside',
1325     parse => $one_keyword_parser,
1326     serialize => $default_serializer,
1327     keyword => {
1328     auto => 1, avoid => 1,
1329     },
1330     initial => ["KEYWORD", 'auto'],
1331     inherited => 1,
1332     compute => $compute_as_specified,
1333     };
1334     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
1335     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
1336    
1337 wakaba 1.15 $Prop->{'background-repeat'} = {
1338     css => 'background-repeat',
1339     dom => 'background_repeat',
1340     key => 'background_repeat',
1341     parse => $one_keyword_parser,
1342     serialize => $default_serializer,
1343     keyword => {
1344     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
1345     },
1346     initial => ["KEYWORD", 'repeat'],
1347     #inherited => 0,
1348     compute => $compute_as_specified,
1349     };
1350     $Attr->{background_repeat} = $Prop->{'background-repeat'};
1351     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
1352    
1353     $Prop->{'background-attachment'} = {
1354     css => 'background-attachment',
1355     dom => 'background_attachment',
1356     key => 'background_attachment',
1357     parse => $one_keyword_parser,
1358     serialize => $default_serializer,
1359     keyword => {
1360     scroll => 1, fixed => 1,
1361     },
1362     initial => ["KEYWORD", 'scroll'],
1363     #inherited => 0,
1364     compute => $compute_as_specified,
1365     };
1366     $Attr->{background_attachment} = $Prop->{'background-attachment'};
1367     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
1368    
1369     $Prop->{'font-style'} = {
1370     css => 'font-style',
1371 wakaba 1.18 dom => 'font_style',
1372     key => 'font_style',
1373 wakaba 1.15 parse => $one_keyword_parser,
1374     serialize => $default_serializer,
1375     keyword => {
1376     normal => 1, italic => 1, oblique => 1,
1377     },
1378     initial => ["KEYWORD", 'normal'],
1379     inherited => 1,
1380     compute => $compute_as_specified,
1381     };
1382     $Attr->{font_style} = $Prop->{'font-style'};
1383     $Key->{font_style} = $Prop->{'font-style'};
1384    
1385     $Prop->{'font-variant'} = {
1386     css => 'font-variant',
1387     dom => 'font_variant',
1388     key => 'font_variant',
1389     parse => $one_keyword_parser,
1390     serialize => $default_serializer,
1391     keyword => {
1392     normal => 1, 'small-caps' => 1,
1393     },
1394     initial => ["KEYWORD", 'normal'],
1395     inherited => 1,
1396     compute => $compute_as_specified,
1397     };
1398     $Attr->{font_variant} = $Prop->{'font-variant'};
1399     $Key->{font_variant} = $Prop->{'font-variant'};
1400    
1401 wakaba 1.16 $Prop->{'text-align'} = {
1402     css => 'text-align',
1403     dom => 'text_align',
1404     key => 'text_align',
1405     parse => $one_keyword_parser,
1406     serialize => $default_serializer,
1407     keyword => {
1408     left => 1, right => 1, center => 1, justify => 1, ## CSS 2
1409     begin => 1, end => 1, ## CSS 3
1410     },
1411     initial => ["KEYWORD", 'begin'],
1412     inherited => 1,
1413     compute => $compute_as_specified,
1414     };
1415     $Attr->{text_align} = $Prop->{'text-align'};
1416     $Key->{text_align} = $Prop->{'text-align'};
1417    
1418     $Prop->{'text-transform'} = {
1419     css => 'text-transform',
1420     dom => 'text_transform',
1421     key => 'text_transform',
1422     parse => $one_keyword_parser,
1423     serialize => $default_serializer,
1424     keyword => {
1425     capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
1426     },
1427     initial => ["KEYWORD", 'none'],
1428     inherited => 1,
1429     compute => $compute_as_specified,
1430     };
1431     $Attr->{text_transform} = $Prop->{'text-transform'};
1432     $Key->{text_transform} = $Prop->{'text-transform'};
1433    
1434     $Prop->{'white-space'} = {
1435     css => 'white-space',
1436     dom => 'white_space',
1437     key => 'white_space',
1438     parse => $one_keyword_parser,
1439     serialize => $default_serializer,
1440     keyword => {
1441     normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
1442     },
1443     initial => ["KEYWORD", 'normal'],
1444     inherited => 1,
1445     compute => $compute_as_specified,
1446     };
1447     $Attr->{white_space} = $Prop->{'white-space'};
1448     $Key->{white_space} = $Prop->{'white-space'};
1449    
1450     $Prop->{'caption-side'} = {
1451     css => 'caption-side',
1452     dom => 'caption_side',
1453     key => 'caption_side',
1454     parse => $one_keyword_parser,
1455     serialize => $default_serializer,
1456     keyword => {
1457     top => 1, bottom => 1,
1458     },
1459     initial => ['KEYWORD', 'top'],
1460     inherited => 1,
1461     compute => $compute_as_specified,
1462     };
1463     $Attr->{caption_side} = $Prop->{'caption-side'};
1464     $Key->{caption_side} = $Prop->{'caption-side'};
1465    
1466     $Prop->{'table-layout'} = {
1467     css => 'table-layout',
1468     dom => 'table_layout',
1469     key => 'table_layout',
1470     parse => $one_keyword_parser,
1471     serialize => $default_serializer,
1472     keyword => {
1473     auto => 1, fixed => 1,
1474     },
1475     initial => ['KEYWORD', 'auto'],
1476     #inherited => 0,
1477     compute => $compute_as_specified,
1478     };
1479     $Attr->{table_layout} = $Prop->{'table-layout'};
1480     $Key->{table_layout} = $Prop->{'table-layout'};
1481    
1482     $Prop->{'border-collapse'} = {
1483     css => 'border-collapse',
1484     dom => 'border_collapse',
1485     key => 'border_collapse',
1486     parse => $one_keyword_parser,
1487     serialize => $default_serializer,
1488     keyword => {
1489     collapse => 1, separate => 1,
1490     },
1491     initial => ['KEYWORD', 'separate'],
1492     inherited => 1,
1493     compute => $compute_as_specified,
1494     };
1495     $Attr->{border_collapse} = $Prop->{'border-collapse'};
1496     $Key->{border_collapse} = $Prop->{'border-collapse'};
1497    
1498     $Prop->{'empty-cells'} = {
1499     css => 'empty-cells',
1500     dom => 'empty_cells',
1501     key => 'empty_cells',
1502     parse => $one_keyword_parser,
1503     serialize => $default_serializer,
1504     keyword => {
1505     show => 1, hide => 1,
1506     },
1507     initial => ['KEYWORD', 'show'],
1508     inherited => 1,
1509     compute => $compute_as_specified,
1510     };
1511     $Attr->{empty_cells} = $Prop->{'empty-cells'};
1512     $Key->{empty_cells} = $Prop->{'empty-cells'};
1513    
1514 wakaba 1.11 $Prop->{'z-index'} = {
1515     css => 'z-index',
1516     dom => 'z_index',
1517     key => 'z_index',
1518     parse => sub {
1519     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1520    
1521 wakaba 1.12 my $sign = 1;
1522     if ($t->{type} == MINUS_TOKEN) {
1523     $sign = -1;
1524     $t = $tt->get_next_token;
1525     }
1526    
1527 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
1528     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1529     ## browser compatibility issue.
1530     my $value = $t->{number};
1531     $t = $tt->get_next_token;
1532 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1533     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1534 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
1535     $t = $tt->get_next_token;
1536     if ($value eq 'auto') {
1537     ## NOTE: |z-index| is the default value and therefore it must be
1538     ## supported anyway.
1539     return ($t, {$prop_name => ["KEYWORD", 'auto']});
1540     } elsif ($value eq 'inherit') {
1541     return ($t, {$prop_name => ['INHERIT']});
1542     }
1543     }
1544    
1545     $onerror->(type => 'syntax error:'.$prop_name,
1546     level => $self->{must_level},
1547     token => $t);
1548     return ($t, undef);
1549     },
1550     serialize => $default_serializer,
1551     initial => ['KEYWORD', 'auto'],
1552     #inherited => 0,
1553     compute => $compute_as_specified,
1554     };
1555     $Attr->{z_index} = $Prop->{'z-index'};
1556     $Key->{z_index} = $Prop->{'z-index'};
1557    
1558 wakaba 1.12 $Prop->{orphans} = {
1559     css => 'orphans',
1560     dom => 'orphans',
1561     key => 'orphans',
1562     parse => sub {
1563     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1564    
1565     my $sign = 1;
1566     if ($t->{type} == MINUS_TOKEN) {
1567     $t = $tt->get_next_token;
1568     $sign = -1;
1569     }
1570    
1571     if ($t->{type} == NUMBER_TOKEN) {
1572     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1573     ## <http://suika.fam.cx/gate/2005/sw/widows> for
1574     ## browser compatibility issue.
1575     my $value = $t->{number};
1576     $t = $tt->get_next_token;
1577     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1578     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1579     my $value = lc $t->{value}; ## TODO: case
1580     $t = $tt->get_next_token;
1581     if ($value eq 'inherit') {
1582     return ($t, {$prop_name => ['INHERIT']});
1583     }
1584     }
1585    
1586     $onerror->(type => 'syntax error:'.$prop_name,
1587     level => $self->{must_level},
1588     token => $t);
1589     return ($t, undef);
1590     },
1591     serialize => $default_serializer,
1592     initial => ['NUMBER', 2],
1593     inherited => 1,
1594     compute => $compute_as_specified,
1595     };
1596     $Attr->{orphans} = $Prop->{orphans};
1597     $Key->{orphans} = $Prop->{orphans};
1598    
1599     $Prop->{widows} = {
1600     css => 'widows',
1601     dom => 'widows',
1602     key => 'widows',
1603     parse => $Prop->{orphans}->{parse},
1604     serialize => $default_serializer,
1605     initial => ['NUMBER', 2],
1606     inherited => 1,
1607     compute => $compute_as_specified,
1608     };
1609     $Attr->{widows} = $Prop->{widows};
1610     $Key->{widows} = $Prop->{widows};
1611    
1612 wakaba 1.19 my $length_unit = {
1613     em => 1, ex => 1, px => 1,
1614     in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
1615     };
1616    
1617 wakaba 1.18 $Prop->{'font-size'} = {
1618     css => 'font-size',
1619     dom => 'font_size',
1620     key => 'font_size',
1621     parse => sub {
1622     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1623    
1624     my $sign = 1;
1625     if ($t->{type} == MINUS_TOKEN) {
1626     $t = $tt->get_next_token;
1627     $sign = -1;
1628     }
1629    
1630     if ($t->{type} == DIMENSION_TOKEN) {
1631     my $value = $t->{number} * $sign;
1632     my $unit = lc $t->{value}; ## TODO: case
1633     $t = $tt->get_next_token;
1634 wakaba 1.19 if ($length_unit->{$unit} and $value >= 0) {
1635 wakaba 1.18 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1636     }
1637     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1638     my $value = $t->{number} * $sign;
1639     $t = $tt->get_next_token;
1640     return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1641 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1642 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1643 wakaba 1.18 my $value = $t->{number} * $sign;
1644     $t = $tt->get_next_token;
1645     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1646     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1647     my $value = lc $t->{value}; ## TODO: case
1648     $t = $tt->get_next_token;
1649     if ({
1650     'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
1651     large => 1, 'x-large' => 1, 'xx-large' => 1,
1652     '-manakai-xxx-large' => 1, # -webkit-xxx-large
1653     larger => 1, smaller => 1,
1654     }->{$value}) {
1655     return ($t, {$prop_name => ['KEYWORD', $value]});
1656     } elsif ($value eq 'inherit') {
1657     return ($t, {$prop_name => ['INHERIT']});
1658     }
1659     }
1660    
1661     $onerror->(type => 'syntax error:'.$prop_name,
1662     level => $self->{must_level},
1663     token => $t);
1664     return ($t, undef);
1665     },
1666     serialize => $default_serializer,
1667     initial => ['KEYWORD', 'medium'],
1668     inherited => 1,
1669     compute => sub {
1670     my ($self, $element, $prop_name, $specified_value) = @_;
1671    
1672     if (defined $specified_value) {
1673     if ($specified_value->[0] eq 'DIMENSION') {
1674     my $unit = $specified_value->[2];
1675     my $value = $specified_value->[1];
1676    
1677     if ($unit eq 'em' or $unit eq 'ex') {
1678     $value *= 0.5 if $unit eq 'ex';
1679     ## TODO: Preferred way to determine the |ex| size is defined
1680     ## in CSS 2.1.
1681    
1682     my $parent_element = $element->manakai_parent_element;
1683     if (defined $parent_element) {
1684     $value *= $self->get_computed_value ($parent_element, $prop_name)
1685     ->[1];
1686     } else {
1687     $value *= $self->{font_size}->[3]; # medium
1688     }
1689     $unit = 'px';
1690     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1691     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1692     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1693     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1694     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1695     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1696     }
1697 wakaba 1.19 ## else: consistency error
1698 wakaba 1.18
1699     return ['DIMENSION', $value, $unit];
1700     } elsif ($specified_value->[0] eq 'PERCENTAGE') {
1701     my $parent_element = $element->manakai_parent_element;
1702     my $parent_cv;
1703     if (defined $parent_element) {
1704     $parent_cv = $self->get_computed_value
1705     ($parent_element, $prop_name);
1706     } else {
1707     $parent_cv = [undef, $self->{font_size}->[3]];
1708     }
1709     return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
1710     'px'];
1711     } elsif ($specified_value->[0] eq 'KEYWORD') {
1712     if ($specified_value->[1] eq 'larger') {
1713     my $parent_element = $element->manakai_parent_element;
1714     if (defined $parent_element) {
1715     my $parent_cv = $self->get_computed_value
1716     ($parent_element, $prop_name);
1717     return ['DIMENSION',
1718     $self->{get_larger_font_size}->($self, $parent_cv->[1]),
1719     'px'];
1720     } else { ## 'larger' relative to 'medium', initial of 'font-size'
1721     return ['DIMENSION', $self->{font_size}->[4], 'px'];
1722     }
1723     } elsif ($specified_value->[1] eq 'smaller') {
1724     my $parent_element = $element->manakai_parent_element;
1725     if (defined $parent_element) {
1726     my $parent_cv = $self->get_computed_value
1727     ($parent_element, $prop_name);
1728     return ['DIMENSION',
1729     $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
1730     'px'];
1731     } else { ## 'smaller' relative to 'medium', initial of 'font-size'
1732     return ['DIMENSION', $self->{font_size}->[2], 'px'];
1733     }
1734     } else {
1735     return ['DIMENSION', $self->{font_size}->[{
1736     'xx-small' => 0,
1737     'x-small' => 1,
1738     small => 2,
1739     medium => 3,
1740     large => 4,
1741     'x-large' => 5,
1742     'xx-large' => 6,
1743     '-manakai-xxx-large' => 7,
1744     }->{$specified_value->[1]}], 'px'];
1745     }
1746     }
1747     }
1748    
1749     return $specified_value;
1750     },
1751     };
1752     $Attr->{font_size} = $Prop->{'font-size'};
1753     $Key->{font_size} = $Prop->{'font-size'};
1754    
1755 wakaba 1.19 my $compute_length = sub {
1756     my ($self, $element, $prop_name, $specified_value) = @_;
1757    
1758     if (defined $specified_value) {
1759     if ($specified_value->[0] eq 'DIMENSION') {
1760     my $unit = $specified_value->[2];
1761     my $value = $specified_value->[1];
1762    
1763     if ($unit eq 'em' or $unit eq 'ex') {
1764     $value *= 0.5 if $unit eq 'ex';
1765     ## TODO: Preferred way to determine the |ex| size is defined
1766     ## in CSS 2.1.
1767    
1768     $value *= $self->get_computed_value ($element, 'font-size')->[1];
1769     $unit = 'px';
1770     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1771     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1772     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1773     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1774     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1775     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1776     }
1777    
1778     return ['DIMENSION', $value, $unit];
1779     }
1780     }
1781    
1782     return $specified_value;
1783     }; # $compute_length
1784    
1785 wakaba 1.23 $Prop->{'letter-spacing'} = {
1786     css => 'letter-spacing',
1787     dom => 'letter_spacing',
1788     key => 'letter_spacing',
1789     parse => sub {
1790     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1791    
1792 wakaba 1.24 ## NOTE: Used also for 'word-spacing', '-manakai-border-spacing-x',
1793     ## and '-manakai-border-spacing-y'.
1794 wakaba 1.23
1795     my $sign = 1;
1796     if ($t->{type} == MINUS_TOKEN) {
1797     $t = $tt->get_next_token;
1798     $sign = -1;
1799     }
1800     my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1801    
1802     if ($t->{type} == DIMENSION_TOKEN) {
1803     my $value = $t->{number} * $sign;
1804     my $unit = lc $t->{value}; ## TODO: case
1805     $t = $tt->get_next_token;
1806 wakaba 1.24 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1807 wakaba 1.23 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1808     }
1809     } elsif ($t->{type} == NUMBER_TOKEN and
1810     ($self->{unitless_px} or $t->{number} == 0)) {
1811     my $value = $t->{number} * $sign;
1812     $t = $tt->get_next_token;
1813 wakaba 1.24 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1814     if $allow_negative or $value >= 0;
1815 wakaba 1.23 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1816     my $value = lc $t->{value}; ## TODO: case
1817     $t = $tt->get_next_token;
1818 wakaba 1.24 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1819 wakaba 1.23 return ($t, {$prop_name => ['KEYWORD', $value]});
1820     } elsif ($value eq 'inherit') {
1821     return ($t, {$prop_name => ['INHERIT']});
1822     }
1823     }
1824    
1825     $onerror->(type => 'syntax error:'.$prop_name,
1826     level => $self->{must_level},
1827     token => $t);
1828     return ($t, undef);
1829     },
1830 wakaba 1.24 allow_negative => 1,
1831     keyword => {normal => 1},
1832 wakaba 1.23 serialize => $default_serializer,
1833     initial => ['KEYWORD', 'normal'],
1834     inherited => 1,
1835     compute => $compute_length,
1836     };
1837     $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
1838     $Key->{letter_spacing} = $Prop->{'letter-spacing'};
1839    
1840     $Prop->{'word-spacing'} = {
1841     css => 'word-spacing',
1842     dom => 'word_spacing',
1843     key => 'word_spacing',
1844     parse => $Prop->{'letter-spacing'}->{parse},
1845 wakaba 1.24 allow_negative => 1,
1846     keyword => {normal => 1},
1847 wakaba 1.23 serialize => $default_serializer,
1848     initial => ['KEYWORD', 'normal'],
1849     inherited => 1,
1850     compute => $compute_length,
1851     };
1852     $Attr->{word_spacing} = $Prop->{'word-spacing'};
1853     $Key->{word_spacing} = $Prop->{'word-spacing'};
1854    
1855 wakaba 1.24 $Prop->{'-manakai-border-spacing-x'} = {
1856     css => '-manakai-border-spacing-x',
1857     dom => '_manakai_border_spacing_x',
1858     key => 'border_spacing_x',
1859     parse => $Prop->{'letter-spacing'}->{parse},
1860     #allow_negative => 0,
1861     #keyword => {},
1862     serialize => $default_serializer,
1863 wakaba 1.25 serialize_multiple => sub {
1864     my $self = shift;
1865    
1866     local $Error::Depth = $Error::Depth + 1;
1867     my $x = $self->_manakai_border_spacing_x;
1868     my $y = $self->_manakai_border_spacing_y;
1869     my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
1870     my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
1871     $xi = ' !' . $xi if length $xi;
1872     $yi = ' !' . $yi if length $yi;
1873     if (defined $x) {
1874     if (defined $y) {
1875 wakaba 1.26 if ($xi eq $yi) {
1876 wakaba 1.25 if ($x eq $y) {
1877     return {'border-spacing' => $x . $xi};
1878     } else {
1879     return {'border-spacing' => $x . ' ' . $y . $xi};
1880     }
1881     } else {
1882     return {'-manakai-border-spacing-x' => $x . $xi,
1883     '-manakai-border-spacing-y' => $y . $yi};
1884     }
1885     } else {
1886     return {'-manakai-border-spacing-x' => $x . $xi};
1887     }
1888     } else {
1889     if (defined $y) {
1890     return {'-manakai-border-spacing-y' => $y . $yi};
1891     } else {
1892     return {};
1893     }
1894     }
1895     },
1896 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
1897     inherited => 1,
1898     compute => $compute_length,
1899     };
1900     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1901     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1902    
1903     $Prop->{'-manakai-border-spacing-y'} = {
1904     css => '-manakai-border-spacing-y',
1905     dom => '_manakai_border_spacing_y',
1906     key => 'border_spacing_y',
1907     parse => $Prop->{'letter-spacing'}->{parse},
1908     #allow_negative => 0,
1909     #keyword => {},
1910     serialize => $default_serializer,
1911 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
1912     ->{serialize_multiple},
1913 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
1914     inherited => 1,
1915     compute => $compute_length,
1916     };
1917     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1918     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1919    
1920 wakaba 1.19 $Prop->{'margin-top'} = {
1921     css => 'margin-top',
1922     dom => 'margin_top',
1923     key => 'margin_top',
1924     parse => sub {
1925     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1926    
1927 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1928 wakaba 1.22 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
1929     ## 'padding-right', 'padding-bottom', 'padding-left',
1930     ## 'border-top-width', 'border-right-width', 'border-bottom-width',
1931 wakaba 1.27 ## 'border-left-width', 'text-indent', 'background-position-x',
1932     ## and 'background-position-y'.
1933 wakaba 1.21
1934 wakaba 1.19 my $sign = 1;
1935     if ($t->{type} == MINUS_TOKEN) {
1936     $t = $tt->get_next_token;
1937     $sign = -1;
1938     }
1939 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1940 wakaba 1.19
1941     if ($t->{type} == DIMENSION_TOKEN) {
1942     my $value = $t->{number} * $sign;
1943     my $unit = lc $t->{value}; ## TODO: case
1944     $t = $tt->get_next_token;
1945 wakaba 1.22 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1946 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1947     }
1948     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1949     my $value = $t->{number} * $sign;
1950     $t = $tt->get_next_token;
1951 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1952     if $allow_negative or $value >= 0;
1953 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1954 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1955     my $value = $t->{number} * $sign;
1956     $t = $tt->get_next_token;
1957 wakaba 1.22 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1958     if $allow_negative or $value >= 0;
1959 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1960     my $value = lc $t->{value}; ## TODO: case
1961     $t = $tt->get_next_token;
1962 wakaba 1.22 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1963 wakaba 1.19 return ($t, {$prop_name => ['KEYWORD', $value]});
1964     } elsif ($value eq 'inherit') {
1965     return ($t, {$prop_name => ['INHERIT']});
1966     }
1967     }
1968    
1969     $onerror->(type => 'syntax error:'.$prop_name,
1970     level => $self->{must_level},
1971     token => $t);
1972     return ($t, undef);
1973     },
1974 wakaba 1.22 allow_negative => 1,
1975     keyword => {auto => 1},
1976 wakaba 1.19 serialize => $default_serializer,
1977     initial => ['DIMENSION', 0, 'px'],
1978     #inherited => 0,
1979     compute => $compute_length,
1980     };
1981     $Attr->{margin_top} = $Prop->{'margin-top'};
1982     $Key->{margin_top} = $Prop->{'margin-top'};
1983    
1984     $Prop->{'margin-bottom'} = {
1985     css => 'margin-bottom',
1986     dom => 'margin_bottom',
1987     key => 'margin_bottom',
1988     parse => $Prop->{'margin-top'}->{parse},
1989 wakaba 1.22 allow_negative => 1,
1990     keyword => {auto => 1},
1991 wakaba 1.19 serialize => $default_serializer,
1992     initial => ['DIMENSION', 0, 'px'],
1993     #inherited => 0,
1994     compute => $compute_length,
1995     };
1996     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1997     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1998    
1999     $Prop->{'margin-right'} = {
2000     css => 'margin-right',
2001     dom => 'margin_right',
2002     key => 'margin_right',
2003     parse => $Prop->{'margin-top'}->{parse},
2004 wakaba 1.22 allow_negative => 1,
2005     keyword => {auto => 1},
2006 wakaba 1.19 serialize => $default_serializer,
2007     initial => ['DIMENSION', 0, 'px'],
2008     #inherited => 0,
2009     compute => $compute_length,
2010     };
2011     $Attr->{margin_right} = $Prop->{'margin-right'};
2012     $Key->{margin_right} = $Prop->{'margin-right'};
2013    
2014     $Prop->{'margin-left'} = {
2015     css => 'margin-left',
2016     dom => 'margin_left',
2017     key => 'margin_left',
2018     parse => $Prop->{'margin-top'}->{parse},
2019 wakaba 1.22 allow_negative => 1,
2020     keyword => {auto => 1},
2021 wakaba 1.19 serialize => $default_serializer,
2022     initial => ['DIMENSION', 0, 'px'],
2023     #inherited => 0,
2024     compute => $compute_length,
2025     };
2026     $Attr->{margin_left} = $Prop->{'margin-left'};
2027     $Key->{margin_left} = $Prop->{'margin-left'};
2028    
2029 wakaba 1.21 $Prop->{top} = {
2030     css => 'top',
2031     dom => 'top',
2032     key => 'top',
2033     parse => $Prop->{'margin-top'}->{parse},
2034 wakaba 1.22 allow_negative => 1,
2035     keyword => {auto => 1},
2036 wakaba 1.21 serialize => $default_serializer,
2037     initial => ['KEYWORD', 'auto'],
2038     #inherited => 0,
2039     compute_multiple => sub {
2040     my ($self, $element, $eid, $prop_name) = @_;
2041    
2042     my $pos_value = $self->get_computed_value ($element, 'position');
2043     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2044     if ($pos_value->[1] eq 'static') {
2045     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2046     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2047     return;
2048     } elsif ($pos_value->[1] eq 'relative') {
2049     my $top_specified = $self->get_specified_value_no_inherit
2050     ($element, 'top');
2051     if (defined $top_specified and
2052     ($top_specified->[0] eq 'DIMENSION' or
2053     $top_specified->[0] eq 'PERCENTAGE')) {
2054     my $tv = $self->{computed_value}->{$eid}->{top}
2055     = $compute_length->($self, $element, 'top', $top_specified);
2056     $self->{computed_value}->{$eid}->{bottom}
2057     = [$tv->[0], -$tv->[1], $tv->[2]];
2058     } else { # top: auto
2059     my $bottom_specified = $self->get_specified_value_no_inherit
2060     ($element, 'bottom');
2061     if (defined $bottom_specified and
2062     ($bottom_specified->[0] eq 'DIMENSION' or
2063     $bottom_specified->[0] eq 'PERCENTAGE')) {
2064     my $tv = $self->{computed_value}->{$eid}->{bottom}
2065     = $compute_length->($self, $element, 'bottom',
2066     $bottom_specified);
2067     $self->{computed_value}->{$eid}->{top}
2068     = [$tv->[0], -$tv->[1], $tv->[2]];
2069     } else { # bottom: auto
2070     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2071     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2072     }
2073     }
2074     return;
2075     }
2076     }
2077    
2078     my $top_specified = $self->get_specified_value_no_inherit
2079     ($element, 'top');
2080     $self->{computed_value}->{$eid}->{top}
2081     = $compute_length->($self, $element, 'top', $top_specified);
2082     my $bottom_specified = $self->get_specified_value_no_inherit
2083     ($element, 'bottom');
2084     $self->{computed_value}->{$eid}->{bottom}
2085     = $compute_length->($self, $element, 'bottom', $bottom_specified);
2086     },
2087     };
2088     $Attr->{top} = $Prop->{top};
2089     $Key->{top} = $Prop->{top};
2090    
2091     $Prop->{bottom} = {
2092     css => 'bottom',
2093     dom => 'bottom',
2094     key => 'bottom',
2095     parse => $Prop->{'margin-top'}->{parse},
2096 wakaba 1.22 allow_negative => 1,
2097     keyword => {auto => 1},
2098 wakaba 1.21 serialize => $default_serializer,
2099     initial => ['KEYWORD', 'auto'],
2100     #inherited => 0,
2101     compute_multiple => $Prop->{top}->{compute_multiple},
2102     };
2103     $Attr->{bottom} = $Prop->{bottom};
2104     $Key->{bottom} = $Prop->{bottom};
2105    
2106     $Prop->{left} = {
2107     css => 'left',
2108     dom => 'left',
2109     key => 'left',
2110     parse => $Prop->{'margin-top'}->{parse},
2111 wakaba 1.22 allow_negative => 1,
2112     keyword => {auto => 1},
2113 wakaba 1.21 serialize => $default_serializer,
2114     initial => ['KEYWORD', 'auto'],
2115     #inherited => 0,
2116     compute_multiple => sub {
2117     my ($self, $element, $eid, $prop_name) = @_;
2118    
2119     my $pos_value = $self->get_computed_value ($element, 'position');
2120     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2121     if ($pos_value->[1] eq 'static') {
2122     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
2123     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
2124     return;
2125     } elsif ($pos_value->[1] eq 'relative') {
2126     my $left_specified = $self->get_specified_value_no_inherit
2127     ($element, 'left');
2128     if (defined $left_specified and
2129     ($left_specified->[0] eq 'DIMENSION' or
2130     $left_specified->[0] eq 'PERCENTAGE')) {
2131     my $right_specified = $self->get_specified_value_no_inherit
2132     ($element, 'right');
2133     if (defined $right_specified and
2134     ($right_specified->[0] eq 'DIMENSION' or
2135     $right_specified->[0] eq 'PERCENTAGE')) {
2136     my $direction = $self->get_computed_value ($element, 'direction');
2137     if (defined $direction and $direction->[0] eq 'KEYWORD' and
2138     $direction->[0] eq 'ltr') {
2139     my $tv = $self->{computed_value}->{$eid}->{left}
2140     = $compute_length->($self, $element, 'left',
2141     $left_specified);
2142     $self->{computed_value}->{$eid}->{right}
2143     = [$tv->[0], -$tv->[1], $tv->[2]];
2144     } else {
2145     my $tv = $self->{computed_value}->{$eid}->{right}
2146     = $compute_length->($self, $element, 'right',
2147     $right_specified);
2148     $self->{computed_value}->{$eid}->{left}
2149     = [$tv->[0], -$tv->[1], $tv->[2]];
2150     }
2151     } else {
2152     my $tv = $self->{computed_value}->{$eid}->{left}
2153     = $compute_length->($self, $element, 'left', $left_specified);
2154     $self->{computed_value}->{$eid}->{right}
2155     = [$tv->[0], -$tv->[1], $tv->[2]];
2156     }
2157     } else { # left: auto
2158     my $right_specified = $self->get_specified_value_no_inherit
2159     ($element, 'right');
2160     if (defined $right_specified and
2161     ($right_specified->[0] eq 'DIMENSION' or
2162     $right_specified->[0] eq 'PERCENTAGE')) {
2163     my $tv = $self->{computed_value}->{$eid}->{right}
2164     = $compute_length->($self, $element, 'right',
2165     $right_specified);
2166     $self->{computed_value}->{$eid}->{left}
2167     = [$tv->[0], -$tv->[1], $tv->[2]];
2168     } else { # right: auto
2169     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
2170     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
2171     }
2172     }
2173     return;
2174     }
2175     }
2176    
2177     my $left_specified = $self->get_specified_value_no_inherit
2178     ($element, 'left');
2179     $self->{computed_value}->{$eid}->{left}
2180     = $compute_length->($self, $element, 'left', $left_specified);
2181     my $right_specified = $self->get_specified_value_no_inherit
2182     ($element, 'right');
2183     $self->{computed_value}->{$eid}->{right}
2184     = $compute_length->($self, $element, 'right', $right_specified);
2185     },
2186     };
2187     $Attr->{left} = $Prop->{left};
2188     $Key->{left} = $Prop->{left};
2189    
2190     $Prop->{right} = {
2191     css => 'right',
2192     dom => 'right',
2193     key => 'right',
2194     parse => $Prop->{'margin-top'}->{parse},
2195     serialize => $default_serializer,
2196     initial => ['KEYWORD', 'auto'],
2197     #inherited => 0,
2198     compute_multiple => $Prop->{left}->{compute_multiple},
2199     };
2200     $Attr->{right} = $Prop->{right};
2201     $Key->{right} = $Prop->{right};
2202    
2203 wakaba 1.22 $Prop->{width} = {
2204     css => 'width',
2205     dom => 'width',
2206     key => 'width',
2207     parse => $Prop->{'margin-top'}->{parse},
2208     #allow_negative => 0,
2209     keyword => {auto => 1},
2210     serialize => $default_serializer,
2211     initial => ['KEYWORD', 'auto'],
2212     #inherited => 0,
2213     compute => $compute_length,
2214     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
2215     ## browser compatibility issues.
2216     };
2217     $Attr->{width} = $Prop->{width};
2218     $Key->{width} = $Prop->{width};
2219    
2220     $Prop->{'min-width'} = {
2221     css => 'min-width',
2222     dom => 'min_width',
2223     key => 'min_width',
2224     parse => $Prop->{'margin-top'}->{parse},
2225     #allow_negative => 0,
2226     #keyword => {},
2227     serialize => $default_serializer,
2228     initial => ['DIMENSION', 0, 'px'],
2229     #inherited => 0,
2230     compute => $compute_length,
2231     };
2232     $Attr->{min_width} = $Prop->{'min-width'};
2233     $Key->{min_width} = $Prop->{'min-width'};
2234    
2235     $Prop->{'max-width'} = {
2236     css => 'max-width',
2237     dom => 'max_width',
2238     key => 'max_width',
2239     parse => $Prop->{'margin-top'}->{parse},
2240     #allow_negative => 0,
2241     keyword => {none => 1},
2242     serialize => $default_serializer,
2243     initial => ['KEYWORD', 'none'],
2244     #inherited => 0,
2245     compute => $compute_length,
2246     };
2247     $Attr->{max_width} = $Prop->{'max-width'};
2248     $Key->{max_width} = $Prop->{'max-width'};
2249    
2250     $Prop->{height} = {
2251     css => 'height',
2252     dom => 'height',
2253     key => 'height',
2254     parse => $Prop->{'margin-top'}->{parse},
2255     #allow_negative => 0,
2256     keyword => {auto => 1},
2257     serialize => $default_serializer,
2258     initial => ['KEYWORD', 'auto'],
2259     #inherited => 0,
2260     compute => $compute_length,
2261     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
2262     ## browser compatibility issues.
2263     };
2264     $Attr->{height} = $Prop->{height};
2265     $Key->{height} = $Prop->{height};
2266    
2267     $Prop->{'min-height'} = {
2268     css => 'min-height',
2269     dom => 'min_height',
2270     key => 'min_height',
2271     parse => $Prop->{'margin-top'}->{parse},
2272     #allow_negative => 0,
2273     #keyword => {},
2274     serialize => $default_serializer,
2275     initial => ['DIMENSION', 0, 'px'],
2276     #inherited => 0,
2277     compute => $compute_length,
2278     };
2279     $Attr->{min_height} = $Prop->{'min-height'};
2280     $Key->{min_height} = $Prop->{'min-height'};
2281    
2282     $Prop->{'max-height'} = {
2283     css => 'max-height',
2284     dom => 'max_height',
2285     key => 'max_height',
2286     parse => $Prop->{'margin-top'}->{parse},
2287     #allow_negative => 0,
2288     keyword => {none => 1},
2289     serialize => $default_serializer,
2290     initial => ['KEYWORD', 'none'],
2291     #inherited => 0,
2292     compute => $compute_length,
2293     };
2294     $Attr->{max_height} = $Prop->{'max-height'};
2295     $Key->{max_height} = $Prop->{'max-height'};
2296    
2297     $Prop->{'line-height'} = {
2298     css => 'line-height',
2299     dom => 'line_height',
2300     key => 'line_height',
2301 wakaba 1.19 parse => sub {
2302     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2303    
2304 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
2305     ## for unitless numbers.
2306    
2307 wakaba 1.19 my $sign = 1;
2308     if ($t->{type} == MINUS_TOKEN) {
2309     $t = $tt->get_next_token;
2310     $sign = -1;
2311     }
2312 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2313 wakaba 1.19
2314     if ($t->{type} == DIMENSION_TOKEN) {
2315     my $value = $t->{number} * $sign;
2316     my $unit = lc $t->{value}; ## TODO: case
2317     $t = $tt->get_next_token;
2318     if ($length_unit->{$unit} and $value >= 0) {
2319     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2320     }
2321     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2322     my $value = $t->{number} * $sign;
2323     $t = $tt->get_next_token;
2324 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
2325     if $value >= 0;
2326     } elsif ($t->{type} == NUMBER_TOKEN) {
2327 wakaba 1.19 my $value = $t->{number} * $sign;
2328     $t = $tt->get_next_token;
2329 wakaba 1.22 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
2330 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2331     my $value = lc $t->{value}; ## TODO: case
2332     $t = $tt->get_next_token;
2333 wakaba 1.22 if ($value eq 'normal') {
2334     return ($t, {$prop_name => ['KEYWORD', $value]});
2335     } elsif ($value eq 'inherit') {
2336 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2337     }
2338     }
2339    
2340     $onerror->(type => 'syntax error:'.$prop_name,
2341     level => $self->{must_level},
2342     token => $t);
2343     return ($t, undef);
2344     },
2345     serialize => $default_serializer,
2346 wakaba 1.22 initial => ['KEYWORD', 'normal'],
2347     inherited => 1,
2348     compute => $compute_length,
2349     };
2350     $Attr->{line_height} = $Prop->{'line-height'};
2351     $Key->{line_height} = $Prop->{'line-height'};
2352    
2353     $Prop->{'vertical-align'} = {
2354     css => 'vertical-align',
2355     dom => 'vertical_align',
2356     key => 'vertical_align',
2357     parse => $Prop->{'margin-top'}->{parse},
2358     allow_negative => 1,
2359     keyword => {
2360     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
2361     middle => 1, bottom => 1, 'text-bottom' => 1,
2362     },
2363     ## NOTE: Currently, we don't support option to select subset of keywords
2364     ## supported by application (i.e.
2365     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
2366     ## it?
2367     serialize => $default_serializer,
2368     initial => ['KEYWORD', 'baseline'],
2369     #inherited => 0,
2370     compute => $compute_length,
2371     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
2372     ## browser compatibility issues.
2373     };
2374     $Attr->{vertical_align} = $Prop->{'vertical-align'};
2375     $Key->{vertical_align} = $Prop->{'vertical-align'};
2376    
2377 wakaba 1.23 $Prop->{'text-indent'} = {
2378     css => 'text-indent',
2379     dom => 'text_indent',
2380     key => 'text_indent',
2381     parse => $Prop->{'margin-top'}->{parse},
2382     allow_negative => 1,
2383     keyword => {},
2384     serialize => $default_serializer,
2385     initial => ['DIMENSION', 0, 'px'],
2386     inherited => 1,
2387     compute => $compute_length,
2388     };
2389     $Attr->{text_indent} = $Prop->{'text-indent'};
2390     $Key->{text_indent} = $Prop->{'text-indent'};
2391    
2392 wakaba 1.27 $Prop->{'background-position-x'} = {
2393     css => 'background-position-x',
2394     dom => 'background_position_x',
2395     key => 'background_position_x',
2396     parse => $Prop->{'margin-top'}->{parse},
2397     allow_negative => 1,
2398     keyword => {left => 1, center => 1, right => 1},
2399     serialize => $default_serializer,
2400     initial => ['PERCENTAGE', 0],
2401     #inherited => 0,
2402     compute => sub {
2403     my ($self, $element, $prop_name, $specified_value) = @_;
2404    
2405     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2406     my $v = {
2407     left => 0, center => 50, right => 100, top => 0, bottom => 100,
2408     }->{$specified_value->[1]};
2409     if (defined $v) {
2410     return ['PERCENTAGE', $v];
2411     } else {
2412     return $specified_value;
2413     }
2414     } else {
2415     return $compute_length->(@_);
2416     }
2417     },
2418     serialize_multiple => sub {
2419     my $self = shift;
2420    
2421     local $Error::Depth = $Error::Depth + 1;
2422     my $x = $self->background_position_x;
2423     my $y = $self->background_position_y;
2424     my $xi = $self->get_property_priority ('background-position-x');
2425     my $yi = $self->get_property_priority ('background-position-y');
2426     $xi = ' !' . $xi if length $xi;
2427     $yi = ' !' . $yi if length $yi;
2428     if (defined $x) {
2429     if (defined $y) {
2430     if ($xi eq $yi) {
2431     return {'background-position' => $x . ' ' . $y . $xi};
2432     } else {
2433     return {'background-position-x' => $x . $xi,
2434     'background-position-y' => $y . $yi};
2435     }
2436     } else {
2437     return {'background-position-x' => $x . $xi};
2438     }
2439     } else {
2440     if (defined $y) {
2441     return {'background-position-y' => $y . $yi};
2442     } else {
2443     return {};
2444     }
2445     }
2446     },
2447     };
2448     $Attr->{background_position_x} = $Prop->{'background-position-x'};
2449     $Key->{background_position_x} = $Prop->{'background-position-x'};
2450    
2451     $Prop->{'background-position-y'} = {
2452     css => 'background-position-y',
2453     dom => 'background_position_y',
2454     key => 'background_position_y',
2455     parse => $Prop->{'margin-top'}->{parse},
2456     allow_negative => 1,
2457     keyword => {top => 1, center => 1, bottom => 1},
2458     serialize => $default_serializer,
2459     serialize_multiple => $Prop->{'background-position-x'}->{serialize_multiple},
2460     initial => ['PERCENTAGE', 0],
2461     #inherited => 0,
2462     compute => $Prop->{'background-position-x'}->{compute},
2463     };
2464     $Attr->{background_position_y} = $Prop->{'background-position-y'};
2465     $Key->{background_position_y} = $Prop->{'background-position-y'};
2466    
2467 wakaba 1.22 $Prop->{'padding-top'} = {
2468     css => 'padding-top',
2469     dom => 'padding_top',
2470     key => 'padding_top',
2471     parse => $Prop->{'margin-top'}->{parse},
2472     #allow_negative => 0,
2473     #keyword => {},
2474     serialize => $default_serializer,
2475 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2476     #inherited => 0,
2477     compute => $compute_length,
2478     };
2479     $Attr->{padding_top} = $Prop->{'padding-top'};
2480     $Key->{padding_top} = $Prop->{'padding-top'};
2481    
2482     $Prop->{'padding-bottom'} = {
2483     css => 'padding-bottom',
2484     dom => 'padding_bottom',
2485     key => 'padding_bottom',
2486     parse => $Prop->{'padding-top'}->{parse},
2487 wakaba 1.22 #allow_negative => 0,
2488     #keyword => {},
2489 wakaba 1.19 serialize => $default_serializer,
2490     initial => ['DIMENSION', 0, 'px'],
2491     #inherited => 0,
2492     compute => $compute_length,
2493     };
2494     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
2495     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
2496    
2497     $Prop->{'padding-right'} = {
2498     css => 'padding-right',
2499     dom => 'padding_right',
2500     key => 'padding_right',
2501     parse => $Prop->{'padding-top'}->{parse},
2502 wakaba 1.22 #allow_negative => 0,
2503     #keyword => {},
2504 wakaba 1.19 serialize => $default_serializer,
2505     initial => ['DIMENSION', 0, 'px'],
2506     #inherited => 0,
2507     compute => $compute_length,
2508     };
2509     $Attr->{padding_right} = $Prop->{'padding-right'};
2510     $Key->{padding_right} = $Prop->{'padding-right'};
2511    
2512     $Prop->{'padding-left'} = {
2513     css => 'padding-left',
2514     dom => 'padding_left',
2515     key => 'padding_left',
2516     parse => $Prop->{'padding-top'}->{parse},
2517 wakaba 1.22 #allow_negative => 0,
2518     #keyword => {},
2519 wakaba 1.19 serialize => $default_serializer,
2520     initial => ['DIMENSION', 0, 'px'],
2521     #inherited => 0,
2522     compute => $compute_length,
2523     };
2524     $Attr->{padding_left} = $Prop->{'padding-left'};
2525     $Key->{padding_left} = $Prop->{'padding-left'};
2526    
2527 wakaba 1.20 $Prop->{'border-top-width'} = {
2528     css => 'border-top-width',
2529     dom => 'border_top_width',
2530     key => 'border_top_width',
2531 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
2532     #allow_negative => 0,
2533     keyword => {thin => 1, medium => 1, thick => 1},
2534 wakaba 1.20 serialize => $default_serializer,
2535     initial => ['KEYWORD', 'medium'],
2536     #inherited => 0,
2537     compute => sub {
2538     my ($self, $element, $prop_name, $specified_value) = @_;
2539    
2540 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
2541     ## 'border-bottom-width', 'border-right-width', and
2542     ## 'outline-width'.
2543    
2544 wakaba 1.20 my $style_prop = $prop_name;
2545     $style_prop =~ s/width/style/;
2546     my $style = $self->get_computed_value ($element, $style_prop);
2547     if (defined $style and $style->[0] eq 'KEYWORD' and
2548     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
2549     return ['DIMENSION', 0, 'px'];
2550     }
2551    
2552     my $value = $compute_length->(@_);
2553     if (defined $value and $value->[0] eq 'KEYWORD') {
2554     if ($value->[1] eq 'thin') {
2555     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
2556     } elsif ($value->[1] eq 'medium') {
2557     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
2558     } elsif ($value->[1] eq 'thick') {
2559     return ['DIMENSION', 5, 'px']; ## Firefox
2560     }
2561     }
2562     return $value;
2563     },
2564 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
2565     ## Opera 9 has already implemented it.
2566 wakaba 1.20 };
2567     $Attr->{border_top_width} = $Prop->{'border-top-width'};
2568     $Key->{border_top_width} = $Prop->{'border-top-width'};
2569    
2570     $Prop->{'border-right-width'} = {
2571     css => 'border-right-width',
2572     dom => 'border_right_width',
2573     key => 'border_right_width',
2574     parse => $Prop->{'border-top-width'}->{parse},
2575 wakaba 1.22 #allow_negative => 0,
2576     keyword => {thin => 1, medium => 1, thick => 1},
2577 wakaba 1.20 serialize => $default_serializer,
2578     initial => ['KEYWORD', 'medium'],
2579     #inherited => 0,
2580     compute => $Prop->{'border-top-width'}->{compute},
2581     };
2582     $Attr->{border_right_width} = $Prop->{'border-right-width'};
2583     $Key->{border_right_width} = $Prop->{'border-right-width'};
2584    
2585     $Prop->{'border-bottom-width'} = {
2586     css => 'border-bottom-width',
2587     dom => 'border_bottom_width',
2588     key => 'border_bottom_width',
2589     parse => $Prop->{'border-top-width'}->{parse},
2590 wakaba 1.22 #allow_negative => 0,
2591     keyword => {thin => 1, medium => 1, thick => 1},
2592 wakaba 1.20 serialize => $default_serializer,
2593     initial => ['KEYWORD', 'medium'],
2594     #inherited => 0,
2595     compute => $Prop->{'border-top-width'}->{compute},
2596     };
2597     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
2598     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
2599    
2600     $Prop->{'border-left-width'} = {
2601     css => 'border-left-width',
2602     dom => 'border_left_width',
2603     key => 'border_left_width',
2604     parse => $Prop->{'border-top-width'}->{parse},
2605 wakaba 1.22 #allow_negative => 0,
2606     keyword => {thin => 1, medium => 1, thick => 1},
2607 wakaba 1.20 serialize => $default_serializer,
2608     initial => ['KEYWORD', 'medium'],
2609     #inherited => 0,
2610     compute => $Prop->{'border-top-width'}->{compute},
2611     };
2612     $Attr->{border_left_width} = $Prop->{'border-left-width'};
2613     $Key->{border_left_width} = $Prop->{'border-left-width'};
2614    
2615 wakaba 1.23 $Prop->{'outline-width'} = {
2616     css => 'outline-width',
2617     dom => 'outline_width',
2618     key => 'outline_width',
2619     parse => $Prop->{'border-top-width'}->{parse},
2620     #allow_negative => 0,
2621     keyword => {thin => 1, medium => 1, thick => 1},
2622     serialize => $default_serializer,
2623     initial => ['KEYWORD', 'medium'],
2624     #inherited => 0,
2625     compute => $Prop->{'border-top-width'}->{compute},
2626     };
2627     $Attr->{outline_width} = $Prop->{'outline-width'};
2628     $Key->{outline_width} = $Prop->{'outline-width'};
2629    
2630 wakaba 1.15 $Prop->{'font-weight'} = {
2631     css => 'font-weight',
2632     dom => 'font_weight',
2633     key => 'font_weight',
2634     parse => sub {
2635     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2636    
2637     if ($t->{type} == NUMBER_TOKEN) {
2638     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2639     ## browser compatibility issue.
2640     my $value = $t->{number};
2641     $t = $tt->get_next_token;
2642     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2643     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2644     }
2645     } elsif ($t->{type} == IDENT_TOKEN) {
2646     my $value = lc $t->{value}; ## TODO: case
2647     $t = $tt->get_next_token;
2648     if ({
2649     normal => 1, bold => 1, bolder => 1, lighter => 1,
2650     }->{$value}) {
2651     return ($t, {$prop_name => ['KEYWORD', $value]});
2652     } elsif ($value eq 'inherit') {
2653     return ($t, {$prop_name => ['INHERIT']});
2654     }
2655     }
2656    
2657     $onerror->(type => 'syntax error:'.$prop_name,
2658     level => $self->{must_level},
2659     token => $t);
2660     return ($t, undef);
2661     },
2662     serialize => $default_serializer,
2663     initial => ['KEYWORD', 'normal'],
2664     inherited => 1,
2665     compute => sub {
2666     my ($self, $element, $prop_name, $specified_value) = @_;
2667    
2668 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2669 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
2670     return ['WEIGHT', 400, 0];
2671     } elsif ($specified_value->[1] eq 'bold') {
2672     return ['WEIGHT', 700, 0];
2673     } elsif ($specified_value->[1] eq 'bolder') {
2674     my $parent_element = $element->manakai_parent_element;
2675     if (defined $parent_element) {
2676     my $parent_value = $self->get_cascaded_value
2677     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2678     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2679     } else {
2680     return ['WEIGHT', 400, 1];
2681     }
2682     } elsif ($specified_value->[1] eq 'lighter') {
2683     my $parent_element = $element->manakai_parent_element;
2684     if (defined $parent_element) {
2685     my $parent_value = $self->get_cascaded_value
2686     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2687     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2688     } else {
2689     return ['WEIGHT', 400, 1];
2690     }
2691     }
2692 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2693 wakaba 1.15 #
2694     }
2695    
2696     return $specified_value;
2697     },
2698     };
2699     $Attr->{font_weight} = $Prop->{'font-weight'};
2700     $Key->{font_weight} = $Prop->{'font-weight'};
2701    
2702 wakaba 1.13 my $uri_or_none_parser = sub {
2703 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2704    
2705 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
2706 wakaba 1.11 my $value = $t->{value};
2707     $t = $tt->get_next_token;
2708     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2709     } elsif ($t->{type} == IDENT_TOKEN) {
2710     my $value = lc $t->{value}; ## TODO: case
2711     $t = $tt->get_next_token;
2712     if ($value eq 'none') {
2713     ## NOTE: |none| is the default value and therefore it must be
2714     ## supported anyway.
2715     return ($t, {$prop_name => ["KEYWORD", 'none']});
2716     } elsif ($value eq 'inherit') {
2717     return ($t, {$prop_name => ['INHERIT']});
2718     }
2719     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2720     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2721     # my $value = $t->{value};
2722     # $t = $tt->get_next_token;
2723     # if ($t->{type} == EOF_TOKEN) {
2724     # $onerror->(type => 'syntax error:eof:'.$prop_name,
2725     # level => $self->{must_level},
2726     # token => $t);
2727     #
2728     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2729     # }
2730     }
2731    
2732     $onerror->(type => 'syntax error:'.$prop_name,
2733     level => $self->{must_level},
2734     token => $t);
2735     return ($t, undef);
2736 wakaba 1.13 }; # $uri_or_none_parser
2737    
2738 wakaba 1.14 my $compute_uri_or_none = sub {
2739 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
2740    
2741     if (defined $specified_value and
2742     $specified_value->[0] eq 'URI' and
2743     defined $specified_value->[2]) {
2744     require Message::DOM::DOMImplementation;
2745     return ['URI',
2746     Message::DOM::DOMImplementation->create_uri_reference
2747     ($specified_value->[1])
2748     ->get_absolute_reference (${$specified_value->[2]})
2749     ->get_uri_reference,
2750     $specified_value->[2]];
2751     }
2752    
2753     return $specified_value;
2754 wakaba 1.14 }; # $compute_uri_or_none
2755    
2756     $Prop->{'list-style-image'} = {
2757     css => 'list-style-image',
2758     dom => 'list_style_image',
2759     key => 'list_style_image',
2760     parse => $uri_or_none_parser,
2761     serialize => $default_serializer,
2762     initial => ['KEYWORD', 'none'],
2763     inherited => 1,
2764     compute => $compute_uri_or_none,
2765 wakaba 1.11 };
2766     $Attr->{list_style_image} = $Prop->{'list-style-image'};
2767     $Key->{list_style_image} = $Prop->{'list-style-image'};
2768    
2769 wakaba 1.15 $Prop->{'background-image'} = {
2770     css => 'background-image',
2771     dom => 'background_image',
2772     key => 'background_image',
2773     parse => $uri_or_none_parser,
2774     serialize => $default_serializer,
2775     initial => ['KEYWORD', 'none'],
2776     #inherited => 0,
2777     compute => $compute_uri_or_none,
2778     };
2779     $Attr->{background_image} = $Prop->{'background-image'};
2780     $Key->{background_image} = $Prop->{'background-image'};
2781    
2782 wakaba 1.7 my $border_style_keyword = {
2783     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2784     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2785     };
2786    
2787     $Prop->{'border-top-style'} = {
2788     css => 'border-top-style',
2789     dom => 'border_top_style',
2790     key => 'border_top_style',
2791     parse => $one_keyword_parser,
2792 wakaba 1.11 serialize => $default_serializer,
2793 wakaba 1.7 keyword => $border_style_keyword,
2794 wakaba 1.9 initial => ["KEYWORD", "none"],
2795     #inherited => 0,
2796     compute => $compute_as_specified,
2797 wakaba 1.7 };
2798     $Attr->{border_top_style} = $Prop->{'border-top-style'};
2799     $Key->{border_top_style} = $Prop->{'border-top-style'};
2800    
2801     $Prop->{'border-right-style'} = {
2802     css => 'border-right-style',
2803     dom => 'border_right_style',
2804     key => 'border_right_style',
2805     parse => $one_keyword_parser,
2806 wakaba 1.11 serialize => $default_serializer,
2807 wakaba 1.7 keyword => $border_style_keyword,
2808 wakaba 1.9 initial => ["KEYWORD", "none"],
2809     #inherited => 0,
2810     compute => $compute_as_specified,
2811 wakaba 1.7 };
2812     $Attr->{border_right_style} = $Prop->{'border-right-style'};
2813     $Key->{border_right_style} = $Prop->{'border-right-style'};
2814    
2815     $Prop->{'border-bottom-style'} = {
2816     css => 'border-bottom-style',
2817     dom => 'border_bottom_style',
2818     key => 'border_bottom_style',
2819     parse => $one_keyword_parser,
2820 wakaba 1.11 serialize => $default_serializer,
2821 wakaba 1.7 keyword => $border_style_keyword,
2822 wakaba 1.9 initial => ["KEYWORD", "none"],
2823     #inherited => 0,
2824     compute => $compute_as_specified,
2825 wakaba 1.7 };
2826     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2827     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2828    
2829     $Prop->{'border-left-style'} = {
2830     css => 'border-left-style',
2831     dom => 'border_left_style',
2832     key => 'border_left_style',
2833     parse => $one_keyword_parser,
2834 wakaba 1.11 serialize => $default_serializer,
2835 wakaba 1.7 keyword => $border_style_keyword,
2836 wakaba 1.9 initial => ["KEYWORD", "none"],
2837     #inherited => 0,
2838     compute => $compute_as_specified,
2839 wakaba 1.7 };
2840     $Attr->{border_left_style} = $Prop->{'border-left-style'};
2841     $Key->{border_left_style} = $Prop->{'border-left-style'};
2842    
2843 wakaba 1.16 $Prop->{'outline-style'} = {
2844     css => 'outline-style',
2845     dom => 'outline_style',
2846     key => 'outline_style',
2847     parse => $one_keyword_parser,
2848     serialize => $default_serializer,
2849 wakaba 1.23 keyword => {%$border_style_keyword},
2850 wakaba 1.16 initial => ['KEYWORD', 'none'],
2851     #inherited => 0,
2852     compute => $compute_as_specified,
2853     };
2854     $Attr->{outline_style} = $Prop->{'outline-style'};
2855     $Key->{outline_style} = $Prop->{'outline-style'};
2856 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2857 wakaba 1.16
2858 wakaba 1.15 $Prop->{'font-family'} = {
2859     css => 'font-family',
2860     dom => 'font_family',
2861     key => 'font_family',
2862     parse => sub {
2863     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2864    
2865     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2866     ## how chaotic browsers are!
2867    
2868     my @prop_value;
2869    
2870     my $font_name = '';
2871     my $may_be_generic = 1;
2872     my $may_be_inherit = 1;
2873     my $has_s = 0;
2874     F: {
2875     if ($t->{type} == IDENT_TOKEN) {
2876     undef $may_be_inherit if $has_s or length $font_name;
2877     undef $may_be_generic if $has_s or length $font_name;
2878     $font_name .= ' ' if $has_s;
2879     $font_name .= $t->{value};
2880     undef $has_s;
2881     $t = $tt->get_next_token;
2882     } elsif ($t->{type} == STRING_TOKEN) {
2883     $font_name .= ' ' if $has_s;
2884     $font_name .= $t->{value};
2885     undef $may_be_inherit;
2886     undef $may_be_generic;
2887     undef $has_s;
2888     $t = $tt->get_next_token;
2889     } elsif ($t->{type} == COMMA_TOKEN) {
2890     if ($may_be_generic and
2891     {
2892     serif => 1, 'sans-serif' => 1, cursive => 1,
2893     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2894     }->{lc $font_name}) { ## TODO: case
2895     push @prop_value, ['KEYWORD', $font_name];
2896     } elsif (not $may_be_generic or length $font_name) {
2897     push @prop_value, ["STRING", $font_name];
2898     }
2899     undef $may_be_inherit;
2900     $may_be_generic = 1;
2901     undef $has_s;
2902     $font_name = '';
2903     $t = $tt->get_next_token;
2904     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2905     } elsif ($t->{type} == S_TOKEN) {
2906     $has_s = 1;
2907     $t = $tt->get_next_token;
2908     } else {
2909     if ($may_be_generic and
2910     {
2911     serif => 1, 'sans-serif' => 1, cursive => 1,
2912     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2913     }->{lc $font_name}) { ## TODO: case
2914     push @prop_value, ['KEYWORD', $font_name];
2915     } elsif (not $may_be_generic or length $font_name) {
2916     push @prop_value, ['STRING', $font_name];
2917     } else {
2918     $onerror->(type => 'syntax error:'.$prop_name,
2919     level => $self->{must_level},
2920     token => $t);
2921     return ($t, undef);
2922     }
2923     last F;
2924     }
2925     redo F;
2926     } # F
2927    
2928     if ($may_be_inherit and
2929     @prop_value == 1 and
2930     $prop_value[0]->[0] eq 'STRING' and
2931     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2932     return ($t, {$prop_name => ['INHERIT']});
2933     } else {
2934     unshift @prop_value, 'FONT';
2935     return ($t, {$prop_name => \@prop_value});
2936     }
2937     },
2938     serialize => sub {
2939     my ($self, $prop_name, $value) = @_;
2940    
2941     if ($value->[0] eq 'FONT') {
2942     return join ', ', map {
2943     if ($_->[0] eq 'STRING') {
2944     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2945     } elsif ($_->[0] eq 'KEYWORD') {
2946     $_->[1]; ## NOTE: This is what Firefox does.
2947     } else {
2948     ## NOTE: This should be an error.
2949     '""';
2950     }
2951     } @$value[1..$#$value];
2952     } elsif ($value->[0] eq 'INHERIT') {
2953     return 'inherit';
2954     } else {
2955     return undef;
2956     }
2957     },
2958     initial => ['FONT', ['KEYWORD', '-manakai-default']],
2959     inherited => 1,
2960     compute => $compute_as_specified,
2961     };
2962     $Attr->{font_family} = $Prop->{'font-family'};
2963     $Key->{font_family} = $Prop->{'font-family'};
2964    
2965 wakaba 1.17 $Prop->{cursor} = {
2966     css => 'cursor',
2967     dom => 'cursor',
2968     key => 'cursor',
2969     parse => sub {
2970     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2971    
2972     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2973     ## compatibility issues.
2974    
2975     my @prop_value = ('CURSOR');
2976    
2977     F: {
2978     if ($t->{type} == IDENT_TOKEN) {
2979     my $v = lc $t->{value}; ## TODO: case
2980     $t = $tt->get_next_token;
2981     if ($Prop->{$prop_name}->{keyword}->{$v}) {
2982     push @prop_value, ['KEYWORD', $v];
2983     last F;
2984     } elsif ($v eq 'inherit' and @prop_value == 1) {
2985     return ($t, {$prop_name => ['INHERIT']});
2986     } else {
2987     $onerror->(type => 'syntax error:'.$prop_name,
2988     level => $self->{must_level},
2989     token => $t);
2990     return ($t, undef);
2991     }
2992     } elsif ($t->{type} == URI_TOKEN) {
2993     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2994     $t = $tt->get_next_token;
2995     } else {
2996     $onerror->(type => 'syntax error:'.$prop_name,
2997     level => $self->{must_level},
2998     token => $t);
2999     return ($t, undef);
3000     }
3001    
3002     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3003     if ($t->{type} == COMMA_TOKEN) {
3004     $t = $tt->get_next_token;
3005     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3006     redo F;
3007     }
3008     } # F
3009    
3010     return ($t, {$prop_name => \@prop_value});
3011     },
3012     serialize => sub {
3013     my ($self, $prop_name, $value) = @_;
3014    
3015     if ($value->[0] eq 'CURSOR') {
3016     return join ', ', map {
3017     if ($_->[0] eq 'URI') {
3018     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
3019     } elsif ($_->[0] eq 'KEYWORD') {
3020     $_->[1];
3021     } else {
3022     ## NOTE: This should be an error.
3023     '""';
3024     }
3025     } @$value[1..$#$value];
3026     } elsif ($value->[0] eq 'INHERIT') {
3027     return 'inherit';
3028     } else {
3029     return undef;
3030     }
3031     },
3032     keyword => {
3033     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
3034     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
3035     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
3036     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
3037     },
3038     initial => ['CURSOR', ['KEYWORD', 'auto']],
3039     inherited => 1,
3040     compute => sub {
3041     my ($self, $element, $prop_name, $specified_value) = @_;
3042    
3043     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
3044     my @new_value = ('CURSOR');
3045     for my $value (@$specified_value[1..$#$specified_value]) {
3046     if ($value->[0] eq 'URI') {
3047     if (defined $value->[2]) {
3048     require Message::DOM::DOMImplementation;
3049     push @new_value, ['URI',
3050     Message::DOM::DOMImplementation
3051     ->create_uri_reference ($value->[1])
3052     ->get_absolute_reference (${$value->[2]})
3053     ->get_uri_reference,
3054     $value->[2]];
3055     } else {
3056     push @new_value, $value;
3057     }
3058     } else {
3059     push @new_value, $value;
3060     }
3061     }
3062     return \@new_value;
3063     }
3064    
3065     return $specified_value;
3066     },
3067     };
3068     $Attr->{cursor} = $Prop->{cursor};
3069     $Key->{cursor} = $Prop->{cursor};
3070    
3071 wakaba 1.7 $Prop->{'border-style'} = {
3072     css => 'border-style',
3073     dom => 'border_style',
3074     parse => sub {
3075     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3076    
3077     my %prop_value;
3078     if ($t->{type} == IDENT_TOKEN) {
3079     my $prop_value = lc $t->{value}; ## TODO: case folding
3080     $t = $tt->get_next_token;
3081     if ($border_style_keyword->{$prop_value} and
3082     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
3083     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
3084     } elsif ($prop_value eq 'inherit') {
3085 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
3086 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3087     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3088     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3089     return ($t, \%prop_value);
3090 wakaba 1.7 } else {
3091     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3092     level => $self->{must_level},
3093     token => $t);
3094     return ($t, undef);
3095     }
3096     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3097     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3098     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3099     } else {
3100     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3101     level => $self->{must_level},
3102     token => $t);
3103     return ($t, undef);
3104     }
3105    
3106     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3107     if ($t->{type} == IDENT_TOKEN) {
3108     my $prop_value = lc $t->{value}; ## TODO: case folding
3109     $t = $tt->get_next_token;
3110 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
3111 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
3112     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
3113     } else {
3114     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3115     level => $self->{must_level},
3116     token => $t);
3117     return ($t, undef);
3118     }
3119     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3120    
3121     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3122     if ($t->{type} == IDENT_TOKEN) {
3123     my $prop_value = lc $t->{value}; ## TODO: case folding
3124     $t = $tt->get_next_token;
3125     if ($border_style_keyword->{$prop_value} and
3126     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
3127     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
3128     } else {
3129     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3130     level => $self->{must_level},
3131     token => $t);
3132     return ($t, undef);
3133     }
3134    
3135     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3136     if ($t->{type} == IDENT_TOKEN) {
3137     my $prop_value = lc $t->{value}; ## TODO: case folding
3138     $t = $tt->get_next_token;
3139     if ($border_style_keyword->{$prop_value} and
3140     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
3141     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
3142     } else {
3143     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3144     level => $self->{must_level},
3145     token => $t);
3146     return ($t, undef);
3147     }
3148     }
3149     }
3150     }
3151    
3152     return ($t, \%prop_value);
3153     },
3154     serialize => sub {
3155     my ($self, $prop_name, $value) = @_;
3156    
3157     local $Error::Depth = $Error::Depth + 1;
3158     my @v;
3159     push @v, $self->border_top_style;
3160     return undef unless defined $v[-1];
3161     push @v, $self->border_right_style;
3162     return undef unless defined $v[-1];
3163     push @v, $self->border_bottom_style;
3164     return undef unless defined $v[-1];
3165 wakaba 1.19 push @v, $self->border_left_style;
3166 wakaba 1.7 return undef unless defined $v[-1];
3167    
3168     pop @v if $v[1] eq $v[3];
3169     pop @v if $v[0] eq $v[2];
3170     pop @v if $v[0] eq $v[1];
3171     return join ' ', @v;
3172     },
3173     };
3174     $Attr->{border_style} = $Prop->{'border-style'};
3175    
3176 wakaba 1.19 $Prop->{margin} = {
3177     css => 'margin',
3178     dom => 'margin',
3179     parse => sub {
3180     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3181    
3182     my %prop_value;
3183    
3184     my $sign = 1;
3185     if ($t->{type} == MINUS_TOKEN) {
3186     $t = $tt->get_next_token;
3187     $sign = -1;
3188     }
3189    
3190     if ($t->{type} == DIMENSION_TOKEN) {
3191     my $value = $t->{number} * $sign;
3192     my $unit = lc $t->{value}; ## TODO: case
3193     $t = $tt->get_next_token;
3194     if ($length_unit->{$unit}) {
3195     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
3196     } else {
3197     $onerror->(type => 'syntax error:'.$prop_name,
3198     level => $self->{must_level},
3199     token => $t);
3200     return ($t, undef);
3201     }
3202     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3203     my $value = $t->{number} * $sign;
3204     $t = $tt->get_next_token;
3205     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
3206 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3207 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3208     my $value = $t->{number} * $sign;
3209     $t = $tt->get_next_token;
3210     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
3211     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3212     my $prop_value = lc $t->{value}; ## TODO: case folding
3213     $t = $tt->get_next_token;
3214     if ($prop_value eq 'auto') {
3215     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
3216     } elsif ($prop_value eq 'inherit') {
3217     $prop_value{'margin-top'} = ['INHERIT'];
3218     $prop_value{'margin-right'} = $prop_value{'margin-top'};
3219     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
3220     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3221     return ($t, \%prop_value);
3222     } else {
3223     $onerror->(type => 'syntax error:'.$prop_name,
3224     level => $self->{must_level},
3225     token => $t);
3226     return ($t, undef);
3227     }
3228     } else {
3229     $onerror->(type => 'syntax error:'.$prop_name,
3230     level => $self->{must_level},
3231     token => $t);
3232     return ($t, undef);
3233     }
3234     $prop_value{'margin-right'} = $prop_value{'margin-top'};
3235     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
3236     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3237    
3238     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3239     $sign = 1;
3240     if ($t->{type} == MINUS_TOKEN) {
3241     $t = $tt->get_next_token;
3242     $sign = -1;
3243     }
3244    
3245     if ($t->{type} == DIMENSION_TOKEN) {
3246     my $value = $t->{number} * $sign;
3247     my $unit = lc $t->{value}; ## TODO: case
3248     $t = $tt->get_next_token;
3249     if ($length_unit->{$unit}) {
3250     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
3251     } else {
3252     $onerror->(type => 'syntax error:'.$prop_name,
3253     level => $self->{must_level},
3254     token => $t);
3255     return ($t, undef);
3256     }
3257     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3258     my $value = $t->{number} * $sign;
3259     $t = $tt->get_next_token;
3260     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
3261 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3262 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3263     my $value = $t->{number} * $sign;
3264     $t = $tt->get_next_token;
3265     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
3266     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3267     my $prop_value = lc $t->{value}; ## TODO: case folding
3268     $t = $tt->get_next_token;
3269     if ($prop_value eq 'auto') {
3270     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
3271     } else {
3272     $onerror->(type => 'syntax error:'.$prop_name,
3273     level => $self->{must_level},
3274     token => $t);
3275     return ($t, undef);
3276     }
3277     } else {
3278 wakaba 1.24 if ($sign < 0) {
3279     $onerror->(type => 'syntax error:'.$prop_name,
3280     level => $self->{must_level},
3281     token => $t);
3282     return ($t, undef);
3283     }
3284 wakaba 1.19 return ($t, \%prop_value);
3285     }
3286     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3287    
3288     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3289     $sign = 1;
3290     if ($t->{type} == MINUS_TOKEN) {
3291     $t = $tt->get_next_token;
3292     $sign = -1;
3293     }
3294    
3295     if ($t->{type} == DIMENSION_TOKEN) {
3296     my $value = $t->{number} * $sign;
3297     my $unit = lc $t->{value}; ## TODO: case
3298     $t = $tt->get_next_token;
3299     if ($length_unit->{$unit}) {
3300     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
3301     } else {
3302     $onerror->(type => 'syntax error:'.$prop_name,
3303     level => $self->{must_level},
3304     token => $t);
3305     return ($t, undef);
3306     }
3307     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3308     my $value = $t->{number} * $sign;
3309     $t = $tt->get_next_token;
3310     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
3311 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3312 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3313     my $value = $t->{number} * $sign;
3314     $t = $tt->get_next_token;
3315     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
3316     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3317     my $prop_value = lc $t->{value}; ## TODO: case folding
3318     $t = $tt->get_next_token;
3319     if ($prop_value eq 'auto') {
3320     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
3321     } else {
3322     $onerror->(type => 'syntax error:'.$prop_name,
3323     level => $self->{must_level},
3324     token => $t);
3325     return ($t, undef);
3326     }
3327     } else {
3328 wakaba 1.24 if ($sign < 0) {
3329     $onerror->(type => 'syntax error:'.$prop_name,
3330     level => $self->{must_level},
3331     token => $t);
3332     return ($t, undef);
3333     }
3334 wakaba 1.19 return ($t, \%prop_value);
3335     }
3336    
3337     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3338     $sign = 1;
3339     if ($t->{type} == MINUS_TOKEN) {
3340     $t = $tt->get_next_token;
3341     $sign = -1;
3342     }
3343    
3344     if ($t->{type} == DIMENSION_TOKEN) {
3345     my $value = $t->{number} * $sign;
3346     my $unit = lc $t->{value}; ## TODO: case
3347     $t = $tt->get_next_token;
3348     if ($length_unit->{$unit}) {
3349     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
3350     } else {
3351     $onerror->(type => 'syntax error:'.$prop_name,
3352     level => $self->{must_level},
3353     token => $t);
3354     return ($t, undef);
3355     }
3356     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3357     my $value = $t->{number} * $sign;
3358     $t = $tt->get_next_token;
3359     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
3360 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3361 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3362     my $value = $t->{number} * $sign;
3363     $t = $tt->get_next_token;
3364     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
3365     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3366     my $prop_value = lc $t->{value}; ## TODO: case folding
3367     $t = $tt->get_next_token;
3368     if ($prop_value eq 'auto') {
3369     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
3370     } else {
3371     $onerror->(type => 'syntax error:'.$prop_name,
3372     level => $self->{must_level},
3373     token => $t);
3374     return ($t, undef);
3375     }
3376     } else {
3377 wakaba 1.24 if ($sign < 0) {
3378     $onerror->(type => 'syntax error:'.$prop_name,
3379     level => $self->{must_level},
3380     token => $t);
3381     return ($t, undef);
3382     }
3383 wakaba 1.19 return ($t, \%prop_value);
3384     }
3385    
3386     return ($t, \%prop_value);
3387     },
3388     serialize => sub {
3389     my ($self, $prop_name, $value) = @_;
3390    
3391     local $Error::Depth = $Error::Depth + 1;
3392     my @v;
3393     push @v, $self->margin_top;
3394     return undef unless defined $v[-1];
3395     push @v, $self->margin_right;
3396     return undef unless defined $v[-1];
3397     push @v, $self->margin_bottom;
3398     return undef unless defined $v[-1];
3399     push @v, $self->margin_left;
3400     return undef unless defined $v[-1];
3401    
3402     pop @v if $v[1] eq $v[3];
3403     pop @v if $v[0] eq $v[2];
3404     pop @v if $v[0] eq $v[1];
3405     return join ' ', @v;
3406     },
3407     };
3408     $Attr->{margin} = $Prop->{margin};
3409    
3410     $Prop->{padding} = {
3411     css => 'padding',
3412     dom => 'padding',
3413     parse => sub {
3414     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3415    
3416     my %prop_value;
3417    
3418     my $sign = 1;
3419     if ($t->{type} == MINUS_TOKEN) {
3420     $t = $tt->get_next_token;
3421     $sign = -1;
3422     }
3423    
3424     if ($t->{type} == DIMENSION_TOKEN) {
3425     my $value = $t->{number} * $sign;
3426     my $unit = lc $t->{value}; ## TODO: case
3427     $t = $tt->get_next_token;
3428     if ($length_unit->{$unit} and $value >= 0) {
3429     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
3430     } else {
3431     $onerror->(type => 'syntax error:'.$prop_name,
3432     level => $self->{must_level},
3433     token => $t);
3434     return ($t, undef);
3435     }
3436     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3437     my $value = $t->{number} * $sign;
3438     $t = $tt->get_next_token;
3439     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
3440     unless ($value >= 0) {
3441     $onerror->(type => 'syntax error:'.$prop_name,
3442     level => $self->{must_level},
3443     token => $t);
3444     return ($t, undef);
3445     }
3446 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3447 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3448     my $value = $t->{number} * $sign;
3449     $t = $tt->get_next_token;
3450     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
3451     unless ($value >= 0) {
3452     $onerror->(type => 'syntax error:'.$prop_name,
3453     level => $self->{must_level},
3454     token => $t);
3455     return ($t, undef);
3456     }
3457     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3458     my $prop_value = lc $t->{value}; ## TODO: case folding
3459     $t = $tt->get_next_token;
3460     if ($prop_value eq 'inherit') {
3461     $prop_value{'padding-top'} = ['INHERIT'];
3462     $prop_value{'padding-right'} = $prop_value{'padding-top'};
3463     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
3464     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3465     return ($t, \%prop_value);
3466     } else {
3467     $onerror->(type => 'syntax error:'.$prop_name,
3468     level => $self->{must_level},
3469     token => $t);
3470     return ($t, undef);
3471     }
3472     } else {
3473     $onerror->(type => 'syntax error:'.$prop_name,
3474     level => $self->{must_level},
3475     token => $t);
3476     return ($t, undef);
3477     }
3478     $prop_value{'padding-right'} = $prop_value{'padding-top'};
3479     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
3480     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3481    
3482     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3483     $sign = 1;
3484     if ($t->{type} == MINUS_TOKEN) {
3485     $t = $tt->get_next_token;
3486     $sign = -1;
3487     }
3488    
3489     if ($t->{type} == DIMENSION_TOKEN) {
3490     my $value = $t->{number} * $sign;
3491     my $unit = lc $t->{value}; ## TODO: case
3492     $t = $tt->get_next_token;
3493     if ($length_unit->{$unit} and $value >= 0) {
3494     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
3495     } else {
3496     $onerror->(type => 'syntax error:'.$prop_name,
3497     level => $self->{must_level},
3498     token => $t);
3499     return ($t, undef);
3500     }
3501     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3502     my $value = $t->{number} * $sign;
3503     $t = $tt->get_next_token;
3504     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
3505     unless ($value >= 0) {
3506     $onerror->(type => 'syntax error:'.$prop_name,
3507     level => $self->{must_level},
3508     token => $t);
3509     return ($t, undef);
3510     }
3511 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3512 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3513     my $value = $t->{number} * $sign;
3514     $t = $tt->get_next_token;
3515     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
3516     unless ($value >= 0) {
3517     $onerror->(type => 'syntax error:'.$prop_name,
3518     level => $self->{must_level},
3519     token => $t);
3520     return ($t, undef);
3521     }
3522     } else {
3523 wakaba 1.24 if ($sign < 0) {
3524     $onerror->(type => 'syntax error:'.$prop_name,
3525     level => $self->{must_level},
3526     token => $t);
3527     return ($t, undef);
3528     }
3529 wakaba 1.19 return ($t, \%prop_value);
3530     }
3531     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3532    
3533     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3534     $sign = 1;
3535     if ($t->{type} == MINUS_TOKEN) {
3536     $t = $tt->get_next_token;
3537     $sign = -1;
3538     }
3539    
3540     if ($t->{type} == DIMENSION_TOKEN) {
3541     my $value = $t->{number} * $sign;
3542     my $unit = lc $t->{value}; ## TODO: case
3543     $t = $tt->get_next_token;
3544     if ($length_unit->{$unit} and $value >= 0) {
3545     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
3546     } else {
3547     $onerror->(type => 'syntax error:'.$prop_name,
3548     level => $self->{must_level},
3549     token => $t);
3550     return ($t, undef);
3551     }
3552     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3553     my $value = $t->{number} * $sign;
3554     $t = $tt->get_next_token;
3555     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
3556     unless ($value >= 0) {
3557     $onerror->(type => 'syntax error:'.$prop_name,
3558     level => $self->{must_level},
3559     token => $t);
3560     return ($t, undef);
3561     }
3562 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3563 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3564     my $value = $t->{number} * $sign;
3565     $t = $tt->get_next_token;
3566     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
3567     unless ($value >= 0) {
3568     $onerror->(type => 'syntax error:'.$prop_name,
3569     level => $self->{must_level},
3570     token => $t);
3571     return ($t, undef);
3572     }
3573     } else {
3574 wakaba 1.24 if ($sign < 0) {
3575     $onerror->(type => 'syntax error:'.$prop_name,
3576     level => $self->{must_level},
3577     token => $t);
3578     return ($t, undef);
3579     }
3580 wakaba 1.19 return ($t, \%prop_value);
3581     }
3582    
3583     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3584     $sign = 1;
3585     if ($t->{type} == MINUS_TOKEN) {
3586     $t = $tt->get_next_token;
3587     $sign = -1;
3588     }
3589    
3590     if ($t->{type} == DIMENSION_TOKEN) {
3591     my $value = $t->{number} * $sign;
3592     my $unit = lc $t->{value}; ## TODO: case
3593     $t = $tt->get_next_token;
3594     if ($length_unit->{$unit} and $value >= 0) {
3595     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
3596     } else {
3597     $onerror->(type => 'syntax error:'.$prop_name,
3598     level => $self->{must_level},
3599     token => $t);
3600     return ($t, undef);
3601     }
3602     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3603     my $value = $t->{number} * $sign;
3604     $t = $tt->get_next_token;
3605     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
3606     unless ($value >= 0) {
3607     $onerror->(type => 'syntax error:'.$prop_name,
3608     level => $self->{must_level},
3609     token => $t);
3610     return ($t, undef);
3611     }
3612 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3613 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3614     my $value = $t->{number} * $sign;
3615     $t = $tt->get_next_token;
3616     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
3617     unless ($value >= 0) {
3618     $onerror->(type => 'syntax error:'.$prop_name,
3619     level => $self->{must_level},
3620     token => $t);
3621     return ($t, undef);
3622     }
3623     } else {
3624 wakaba 1.24 if ($sign < 0) {
3625     $onerror->(type => 'syntax error:'.$prop_name,
3626     level => $self->{must_level},
3627     token => $t);
3628     return ($t, undef);
3629     }
3630 wakaba 1.19 return ($t, \%prop_value);
3631     }
3632    
3633     return ($t, \%prop_value);
3634     },
3635     serialize => sub {
3636     my ($self, $prop_name, $value) = @_;
3637    
3638     local $Error::Depth = $Error::Depth + 1;
3639     my @v;
3640     push @v, $self->padding_top;
3641     return undef unless defined $v[-1];
3642     push @v, $self->padding_right;
3643     return undef unless defined $v[-1];
3644     push @v, $self->padding_bottom;
3645     return undef unless defined $v[-1];
3646     push @v, $self->padding_left;
3647     return undef unless defined $v[-1];
3648    
3649     pop @v if $v[1] eq $v[3];
3650     pop @v if $v[0] eq $v[2];
3651     pop @v if $v[0] eq $v[1];
3652     return join ' ', @v;
3653     },
3654     };
3655     $Attr->{padding} = $Prop->{padding};
3656    
3657 wakaba 1.24 $Prop->{'border-spacing'} = {
3658     css => 'border-spacing',
3659     dom => 'border_spacing',
3660     parse => sub {
3661     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3662    
3663     my %prop_value;
3664    
3665     my $sign = 1;
3666     if ($t->{type} == MINUS_TOKEN) {
3667     $t = $tt->get_next_token;
3668     $sign = -1;
3669     }
3670    
3671     if ($t->{type} == DIMENSION_TOKEN) {
3672     my $value = $t->{number} * $sign;
3673     my $unit = lc $t->{value}; ## TODO: case
3674     $t = $tt->get_next_token;
3675     if ($length_unit->{$unit} and $value >= 0) {
3676     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
3677     } else {
3678     $onerror->(type => 'syntax error:'.$prop_name,
3679     level => $self->{must_level},
3680     token => $t);
3681     return ($t, undef);
3682     }
3683     } elsif ($t->{type} == NUMBER_TOKEN and
3684     ($self->{unitless_px} or $t->{number} == 0)) {
3685     my $value = $t->{number} * $sign;
3686     $t = $tt->get_next_token;
3687     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
3688     unless ($value >= 0) {
3689     $onerror->(type => 'syntax error:'.$prop_name,
3690     level => $self->{must_level},
3691     token => $t);
3692     return ($t, undef);
3693     }
3694     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3695     my $prop_value = lc $t->{value}; ## TODO: case folding
3696     $t = $tt->get_next_token;
3697     if ($prop_value eq 'inherit') {
3698     $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
3699     $prop_value{'-manakai-border-spacing-y'}
3700     = $prop_value{'-manakai-border-spacing-x'};
3701     return ($t, \%prop_value);
3702     } else {
3703     $onerror->(type => 'syntax error:'.$prop_name,
3704     level => $self->{must_level},
3705     token => $t);
3706     return ($t, undef);
3707     }
3708     } else {
3709     $onerror->(type => 'syntax error:'.$prop_name,
3710     level => $self->{must_level},
3711     token => $t);
3712     return ($t, undef);
3713     }
3714     $prop_value{'-manakai-border-spacing-y'}
3715     = $prop_value{'-manakai-border-spacing-x'};
3716    
3717     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3718     $sign = 1;
3719     if ($t->{type} == MINUS_TOKEN) {
3720     $t = $tt->get_next_token;
3721     $sign = -1;
3722     }
3723    
3724     if ($t->{type} == DIMENSION_TOKEN) {
3725     my $value = $t->{number} * $sign;
3726     my $unit = lc $t->{value}; ## TODO: case
3727     $t = $tt->get_next_token;
3728     if ($length_unit->{$unit} and $value >= 0) {
3729     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
3730     } else {
3731     $onerror->(type => 'syntax error:'.$prop_name,
3732     level => $self->{must_level},
3733     token => $t);
3734     return ($t, undef);
3735     }
3736     } elsif ($t->{type} == NUMBER_TOKEN and
3737     ($self->{unitless_px} or $t->{number} == 0)) {
3738     my $value = $t->{number} * $sign;
3739     $t = $tt->get_next_token;
3740     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
3741     unless ($value >= 0) {
3742     $onerror->(type => 'syntax error:'.$prop_name,
3743     level => $self->{must_level},
3744     token => $t);
3745     return ($t, undef);
3746     }
3747     } else {
3748     if ($sign < 0) {
3749     $onerror->(type => 'syntax error:'.$prop_name,
3750     level => $self->{must_level},
3751     token => $t);
3752     return ($t, undef);
3753     }
3754     return ($t, \%prop_value);
3755     }
3756    
3757     return ($t, \%prop_value);
3758     },
3759     serialize => sub {
3760     my ($self, $prop_name, $value) = @_;
3761    
3762     local $Error::Depth = $Error::Depth + 1;
3763     my @v;
3764     push @v, $self->_manakai_border_spacing_x;
3765     return undef unless defined $v[-1];
3766     push @v, $self->_manakai_border_spacing_y;
3767     return undef unless defined $v[-1];
3768    
3769     pop @v if $v[0] eq $v[1];
3770     return join ' ', @v;
3771     },
3772 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
3773     ->{serialize_multiple},
3774 wakaba 1.24 };
3775     $Attr->{border_spacing} = $Prop->{'border-spacing'};
3776    
3777 wakaba 1.27 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
3778     ## browser compatibility problems.
3779     $Prop->{'background-position'} = {
3780     css => 'background-position',
3781     dom => 'background_position',
3782     parse => sub {
3783     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3784    
3785     my %prop_value;
3786    
3787     my $sign = 1;
3788     if ($t->{type} == MINUS_TOKEN) {
3789     $t = $tt->get_next_token;
3790     $sign = -1;
3791     }
3792    
3793     if ($t->{type} == DIMENSION_TOKEN) {
3794     my $value = $t->{number} * $sign;
3795     my $unit = lc $t->{value}; ## TODO: case
3796     $t = $tt->get_next_token;
3797     if ($length_unit->{$unit}) {
3798     $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
3799     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3800     } else {
3801     $onerror->(type => 'syntax error:'.$prop_name,
3802     level => $self->{must_level},
3803     token => $t);
3804     return ($t, undef);
3805     }
3806     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3807     my $value = $t->{number} * $sign;
3808     $t = $tt->get_next_token;
3809     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
3810     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3811     } elsif ($t->{type} == NUMBER_TOKEN and
3812     ($self->{unitless_px} or $t->{number} == 0)) {
3813     my $value = $t->{number} * $sign;
3814     $t = $tt->get_next_token;
3815     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
3816     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3817     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3818     my $prop_value = lc $t->{value}; ## TODO: case folding
3819     $t = $tt->get_next_token;
3820     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3821     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3822     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
3823     } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
3824     $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
3825    
3826     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3827     if ($t->{type} == IDENT_TOKEN) {
3828     my $prop_value = lc $t->{value}; ## TODO: case folding
3829     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3830     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3831     $t = $tt->get_next_token;
3832     return ($t, \%prop_value);
3833     }
3834     }
3835     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
3836     return ($t, \%prop_value);
3837     } elsif ($prop_value eq 'inherit') {
3838     $prop_value{'background-position-x'} = ['INHERIT'];
3839     $prop_value{'background-position-y'} = ['INHERIT'];
3840     return ($t, \%prop_value);
3841     } else {
3842     $onerror->(type => 'syntax error:'.$prop_name,
3843     level => $self->{must_level},
3844     token => $t);
3845     return ($t, undef);
3846     }
3847     } else {
3848     $onerror->(type => 'syntax error:'.$prop_name,
3849     level => $self->{must_level},
3850     token => $t);
3851     return ($t, undef);
3852     }
3853    
3854     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3855     $sign = 1;
3856     if ($t->{type} == MINUS_TOKEN) {
3857     $t = $tt->get_next_token;
3858     $sign = -1;
3859     }
3860    
3861     if ($t->{type} == DIMENSION_TOKEN) {
3862     my $value = $t->{number} * $sign;
3863     my $unit = lc $t->{value}; ## TODO: case
3864     $t = $tt->get_next_token;
3865     if ($length_unit->{$unit}) {
3866     $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
3867     } else {
3868     $onerror->(type => 'syntax error:'.$prop_name,
3869     level => $self->{must_level},
3870     token => $t);
3871     return ($t, undef);
3872     }
3873     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3874     my $value = $t->{number} * $sign;
3875     $t = $tt->get_next_token;
3876     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
3877     } elsif ($t->{type} == NUMBER_TOKEN and $self->{unitless_px}) {
3878     my $value = $t->{number} * $sign;
3879     $t = $tt->get_next_token;
3880     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
3881     } elsif ($t->{type} == IDENT_TOKEN) {
3882     my $value = lc $t->{value}; ## TODO: case
3883     if ({top => 1, center => 1, bottom => 1}->{$value}) {
3884     $prop_value{'background-position-y'} = ['KEYWORD', $value];
3885     $t = $tt->get_next_token;
3886     }
3887     } else {
3888     if ($sign < 0) {
3889     $onerror->(type => 'syntax error:'.$prop_name,
3890     level => $self->{must_level},
3891     token => $t);
3892     return ($t, undef);
3893     }
3894     return ($t, \%prop_value);
3895     }
3896    
3897     return ($t, \%prop_value);
3898     },
3899     serialize => sub {
3900     my ($self, $prop_name, $value) = @_;
3901    
3902     local $Error::Depth = $Error::Depth + 1;
3903     my $x = $self->background_position_x;
3904     my $y = $self->background_position_y;
3905     return $x . ' ' . $y if defined $x and defined $y;
3906     return undef;
3907     },
3908     serialize_multiple => $Prop->{'background-position-x'}
3909     ->{serialize_multiple},
3910     };
3911     $Attr->{background_position} = $Prop->{'background-position'};
3912    
3913 wakaba 1.20 $Prop->{'border-width'} = {
3914     css => 'border-width',
3915     dom => 'border_width',
3916     parse => sub {
3917     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3918    
3919     my %prop_value;
3920    
3921     my $sign = 1;
3922     if ($t->{type} == MINUS_TOKEN) {
3923     $t = $tt->get_next_token;
3924     $sign = -1;
3925     }
3926    
3927     if ($t->{type} == DIMENSION_TOKEN) {
3928     my $value = $t->{number} * $sign;
3929     my $unit = lc $t->{value}; ## TODO: case
3930     $t = $tt->get_next_token;
3931     if ($length_unit->{$unit} and $value >= 0) {
3932     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
3933     } else {
3934     $onerror->(type => 'syntax error:'.$prop_name,
3935     level => $self->{must_level},
3936     token => $t);
3937     return ($t, undef);
3938     }
3939     } elsif ($t->{type} == NUMBER_TOKEN and
3940     ($self->{unitless_px} or $t->{number} == 0)) {
3941     my $value = $t->{number} * $sign;
3942     $t = $tt->get_next_token;
3943     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
3944     unless ($value >= 0) {
3945     $onerror->(type => 'syntax error:'.$prop_name,
3946     level => $self->{must_level},
3947     token => $t);
3948     return ($t, undef);
3949     }
3950     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3951     my $prop_value = lc $t->{value}; ## TODO: case folding
3952     $t = $tt->get_next_token;
3953     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3954     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
3955     } elsif ($prop_value eq 'inherit') {
3956     $prop_value{'border-top-width'} = ['INHERIT'];
3957     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3958     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3959     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3960     return ($t, \%prop_value);
3961     } else {
3962     $onerror->(type => 'syntax error:'.$prop_name,
3963     level => $self->{must_level},
3964     token => $t);
3965     return ($t, undef);
3966     }
3967     } else {
3968     $onerror->(type => 'syntax error:'.$prop_name,
3969     level => $self->{must_level},
3970     token => $t);
3971     return ($t, undef);
3972     }
3973     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3974     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3975     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3976    
3977     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3978     $sign = 1;
3979     if ($t->{type} == MINUS_TOKEN) {
3980     $t = $tt->get_next_token;
3981     $sign = -1;
3982     }
3983    
3984     if ($t->{type} == DIMENSION_TOKEN) {
3985     my $value = $t->{number} * $sign;
3986     my $unit = lc $t->{value}; ## TODO: case
3987     $t = $tt->get_next_token;
3988     if ($length_unit->{$unit} and $value >= 0) {
3989     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
3990     } else {
3991     $onerror->(type => 'syntax error:'.$prop_name,
3992     level => $self->{must_level},
3993     token => $t);
3994     return ($t, undef);
3995     }
3996     } elsif ($t->{type} == NUMBER_TOKEN and
3997     ($self->{unitless_px} or $t->{number} == 0)) {
3998     my $value = $t->{number} * $sign;
3999     $t = $tt->get_next_token;
4000     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
4001     unless ($value >= 0) {
4002     $onerror->(type => 'syntax error:'.$prop_name,
4003     level => $self->{must_level},
4004     token => $t);
4005     return ($t, undef);
4006     }
4007     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4008     my $prop_value = lc $t->{value}; ## TODO: case
4009     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4010     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
4011     $t = $tt->get_next_token;
4012     }
4013     } else {
4014 wakaba 1.24 if ($sign < 0) {
4015     $onerror->(type => 'syntax error:'.$prop_name,
4016     level => $self->{must_level},
4017     token => $t);
4018     return ($t, undef);
4019     }
4020 wakaba 1.20 return ($t, \%prop_value);
4021     }
4022     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
4023    
4024     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4025     $sign = 1;
4026     if ($t->{type} == MINUS_TOKEN) {
4027     $t = $tt->get_next_token;
4028     $sign = -1;
4029     }
4030    
4031     if ($t->{type} == DIMENSION_TOKEN) {
4032     my $value = $t->{number} * $sign;
4033     my $unit = lc $t->{value}; ## TODO: case
4034     $t = $tt->get_next_token;
4035     if ($length_unit->{$unit} and $value >= 0) {
4036     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
4037     } else {
4038     $onerror->(type => 'syntax error:'.$prop_name,
4039     level => $self->{must_level},
4040     token => $t);
4041     return ($t, undef);
4042     }
4043     } elsif ($t->{type} == NUMBER_TOKEN and
4044     ($self->{unitless_px} or $t->{number} == 0)) {
4045     my $value = $t->{number} * $sign;
4046     $t = $tt->get_next_token;
4047     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
4048     unless ($value >= 0) {
4049     $onerror->(type => 'syntax error:'.$prop_name,
4050     level => $self->{must_level},
4051     token => $t);
4052     return ($t, undef);
4053     }
4054     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4055     my $prop_value = lc $t->{value}; ## TODO: case
4056     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4057     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
4058     $t = $tt->get_next_token;
4059     }
4060     } else {
4061 wakaba 1.24 if ($sign < 0) {
4062     $onerror->(type => 'syntax error:'.$prop_name,
4063     level => $self->{must_level},
4064     token => $t);
4065     return ($t, undef);
4066     }
4067 wakaba 1.20 return ($t, \%prop_value);
4068     }
4069    
4070     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4071     $sign = 1;
4072     if ($t->{type} == MINUS_TOKEN) {
4073     $t = $tt->get_next_token;
4074     $sign = -1;
4075     }
4076    
4077     if ($t->{type} == DIMENSION_TOKEN) {
4078     my $value = $t->{number} * $sign;
4079     my $unit = lc $t->{value}; ## TODO: case
4080     $t = $tt->get_next_token;
4081     if ($length_unit->{$unit} and $value >= 0) {
4082     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
4083     } else {
4084     $onerror->(type => 'syntax error:'.$prop_name,
4085     level => $self->{must_level},
4086     token => $t);
4087     return ($t, undef);
4088     }
4089     } elsif ($t->{type} == NUMBER_TOKEN and
4090     ($self->{unitless_px} or $t->{number} == 0)) {
4091     my $value = $t->{number} * $sign;
4092     $t = $tt->get_next_token;
4093     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
4094     unless ($value >= 0) {
4095     $onerror->(type => 'syntax error:'.$prop_name,
4096     level => $self->{must_level},
4097     token => $t);
4098     return ($t, undef);
4099     }
4100     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4101     my $prop_value = lc $t->{value}; ## TODO: case
4102     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4103     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
4104     $t = $tt->get_next_token;
4105     }
4106     } else {
4107 wakaba 1.24 if ($sign < 0) {
4108     $onerror->(type => 'syntax error:'.$prop_name,
4109     level => $self->{must_level},
4110     token => $t);
4111     return ($t, undef);
4112     }
4113 wakaba 1.20 return ($t, \%prop_value);
4114     }
4115    
4116     return ($t, \%prop_value);
4117     },
4118     serialize => sub {
4119     my ($self, $prop_name, $value) = @_;
4120    
4121     local $Error::Depth = $Error::Depth + 1;
4122     my @v;
4123 wakaba 1.24 push @v, $self->border_top_width;
4124 wakaba 1.20 return undef unless defined $v[-1];
4125 wakaba 1.24 push @v, $self->border_right_width;
4126 wakaba 1.20 return undef unless defined $v[-1];
4127 wakaba 1.24 push @v, $self->border_bottom_width;
4128 wakaba 1.20 return undef unless defined $v[-1];
4129 wakaba 1.24 push @v, $self->border_left_width;
4130 wakaba 1.20 return undef unless defined $v[-1];
4131    
4132     pop @v if $v[1] eq $v[3];
4133     pop @v if $v[0] eq $v[2];
4134     pop @v if $v[0] eq $v[1];
4135     return join ' ', @v;
4136     },
4137     };
4138 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
4139 wakaba 1.20
4140 wakaba 1.12 $Prop->{'list-style'} = {
4141     css => 'list-style',
4142     dom => 'list_style',
4143     parse => sub {
4144     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4145    
4146     my %prop_value;
4147     my $none = 0;
4148    
4149     F: for my $f (1..3) {
4150     if ($t->{type} == IDENT_TOKEN) {
4151     my $prop_value = lc $t->{value}; ## TODO: case folding
4152     $t = $tt->get_next_token;
4153    
4154     if ($prop_value eq 'none') {
4155     $none++;
4156     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
4157     if (exists $prop_value{'list-style-type'}) {
4158     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4159     $prop_name,
4160     level => $self->{must_level},
4161     token => $t);
4162     return ($t, undef);
4163     } else {
4164     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
4165     }
4166     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
4167     if (exists $prop_value{'list-style-position'}) {
4168     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
4169     $prop_name,
4170     level => $self->{must_level},
4171     token => $t);
4172     return ($t, undef);
4173     }
4174    
4175     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
4176     } elsif ($f == 1 and $prop_value eq 'inherit') {
4177     $prop_value{'list-style-type'} = ["INHERIT"];
4178     $prop_value{'list-style-position'} = ["INHERIT"];
4179     $prop_value{'list-style-image'} = ["INHERIT"];
4180     last F;
4181     } else {
4182     if ($f == 1) {
4183     $onerror->(type => 'syntax error:'.$prop_name,
4184     level => $self->{must_level},
4185     token => $t);
4186     return ($t, undef);
4187     } else {
4188     last F;
4189     }
4190     }
4191     } elsif ($t->{type} == URI_TOKEN) {
4192     if (exists $prop_value{'list-style-image'}) {
4193     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4194     $prop_name,
4195     level => $self->{must_level},
4196     token => $t);
4197     return ($t, undef);
4198     }
4199    
4200     $prop_value{'list-style-image'}
4201 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
4202 wakaba 1.12 $t = $tt->get_next_token;
4203     } else {
4204     if ($f == 1) {
4205     $onerror->(type => 'syntax error:keyword:'.$prop_name,
4206     level => $self->{must_level},
4207     token => $t);
4208     return ($t, undef);
4209     } else {
4210     last F;
4211     }
4212     }
4213    
4214     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4215     } # F
4216     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
4217    
4218     if ($none == 1) {
4219     if (exists $prop_value{'list-style-type'}) {
4220     if (exists $prop_value{'list-style-image'}) {
4221     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4222     $prop_name,
4223     level => $self->{must_level},
4224     token => $t);
4225     return ($t, undef);
4226     } else {
4227     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
4228     }
4229     } else {
4230     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
4231     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
4232     unless exists $prop_value{'list-style-image'};
4233     }
4234     } elsif ($none == 2) {
4235     if (exists $prop_value{'list-style-type'}) {
4236     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4237     $prop_name,
4238     level => $self->{must_level},
4239     token => $t);
4240     return ($t, undef);
4241     }
4242     if (exists $prop_value{'list-style-image'}) {
4243     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4244     $prop_name,
4245     level => $self->{must_level},
4246     token => $t);
4247     return ($t, undef);
4248     }
4249    
4250     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
4251     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
4252     } elsif ($none == 3) {
4253     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4254     $prop_name,
4255     level => $self->{must_level},
4256     token => $t);
4257     return ($t, undef);
4258     }
4259    
4260     for (qw/list-style-type list-style-position list-style-image/) {
4261     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
4262     }
4263    
4264     return ($t, \%prop_value);
4265     },
4266     serialize => sub {
4267     my ($self, $prop_name, $value) = @_;
4268    
4269     local $Error::Depth = $Error::Depth + 1;
4270     return $self->list_style_type . ' ' . $self->list_style_position .
4271     ' ' . $self->list_style_image;
4272     },
4273     };
4274     $Attr->{list_style} = $Prop->{'list-style'};
4275    
4276 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
4277     ## store the parsed value to support CSS 3 properties.
4278     $Prop->{'text-decoration'} = {
4279     css => 'text-decoration',
4280     dom => 'text_decoration',
4281     key => 'text_decoration',
4282     parse => sub {
4283     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4284    
4285     my $value = ['DECORATION']; # , underline, overline, line-through, blink
4286    
4287     if ($t->{type} == IDENT_TOKEN) {
4288     my $v = lc $t->{value}; ## TODO: case
4289     $t = $tt->get_next_token;
4290     if ($v eq 'inherit') {
4291     return ($t, {$prop_name => ['INHERIT']});
4292     } elsif ($v eq 'none') {
4293     return ($t, {$prop_name => $value});
4294     } elsif ($v eq 'underline' and
4295     $self->{prop_value}->{$prop_name}->{$v}) {
4296     $value->[1] = 1;
4297     } elsif ($v eq 'overline' and
4298     $self->{prop_value}->{$prop_name}->{$v}) {
4299     $value->[2] = 1;
4300     } elsif ($v eq 'line-through' and
4301     $self->{prop_value}->{$prop_name}->{$v}) {
4302     $value->[3] = 1;
4303     } elsif ($v eq 'blink' and
4304     $self->{prop_value}->{$prop_name}->{$v}) {
4305     $value->[4] = 1;
4306     } else {
4307     $onerror->(type => 'syntax error:'.$prop_name,
4308     level => $self->{must_level},
4309     token => $t);
4310     return ($t, undef);
4311     }
4312     }
4313    
4314     F: {
4315     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4316     last F unless $t->{type} == IDENT_TOKEN;
4317    
4318     my $v = lc $t->{value}; ## TODO: case
4319     $t = $tt->get_next_token;
4320     if ($v eq 'underline' and
4321     $self->{prop_value}->{$prop_name}->{$v}) {
4322     $value->[1] = 1;
4323     } elsif ($v eq 'overline' and
4324     $self->{prop_value}->{$prop_name}->{$v}) {
4325     $value->[1] = 2;
4326     } elsif ($v eq 'line-through' and
4327     $self->{prop_value}->{$prop_name}->{$v}) {
4328     $value->[1] = 3;
4329     } elsif ($v eq 'blink' and
4330     $self->{prop_value}->{$prop_name}->{$v}) {
4331     $value->[1] = 4;
4332     } else {
4333     last F;
4334     }
4335    
4336     redo F;
4337     } # F
4338    
4339     return ($t, {$prop_name => $value});
4340     },
4341     serialize => $default_serializer,
4342     initial => ["KEYWORD", "none"],
4343     #inherited => 0,
4344     compute => $compute_as_specified,
4345     };
4346     $Attr->{text_decoration} = $Prop->{'text-decoration'};
4347     $Key->{text_decoration} = $Prop->{'text-decoration'};
4348    
4349 wakaba 1.1 1;
4350 wakaba 1.28 ## $Date: 2008/01/06 10:33:01 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24