/[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.54 - (hide annotations) (download)
Sun Jan 27 08:22:40 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.53: +8 -3 lines
++ whatpm/t/ChangeLog	27 Jan 2008 08:22:34 -0000
	* css-visual.dat: New test data for 'opacity' and '-moz-opacity'
	are added.

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

++ whatpm/Whatpm/CSS/ChangeLog	27 Jan 2008 08:22:07 -0000
	* Parser.pm ('opacity' parse): Support for '+'.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24