/[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.39 - (hide annotations) (download)
Sun Jan 20 09:59:25 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.38: +115 -114 lines
++ whatpm/Whatpm/CSS/ChangeLog	20 Jan 2008 09:59:08 -0000
	* Parser.pm, SelectorsParser.pm: Error type strings are revised.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24