/[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.55 - (hide annotations) (download)
Sun Jan 27 08:58:05 2008 UTC (18 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.54: +69 -6 lines
++ whatpm/t/ChangeLog	27 Jan 2008 08:57:57 -0000
	* CSS-Parser-1.t: 'overflow-x' and 'overflow-y' are added.

	* css-visual.dat: New test data for 'overflow', 'overflow-x',
	and 'overflow-y' are added.

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

++ whatpm/Whatpm/CSS/ChangeLog	27 Jan 2008 08:56:42 -0000
	* Parser.pm ($one_keyword_parser): More accurate error location
	reporting.
	('overflow-x', 'overflow-y'): Implemented.
	('overflow'): Reimplemented as a shorthand.

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     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
1340     $self->{prop_value}->{$prop_name}->{$prop_value}) {
1341 wakaba 1.55 $t = $tt->get_next_token;
1342 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
1343 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
1344 wakaba 1.55 $t = $tt->get_next_token;
1345 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
1346 wakaba 1.6 }
1347     }
1348    
1349 wakaba 1.39 $onerror->(type => "syntax error:'".$prop_name."'",
1350 wakaba 1.6 level => $self->{must_level},
1351 wakaba 1.38 uri => \$self->{href},
1352 wakaba 1.6 token => $t);
1353     return ($t, undef);
1354     };
1355    
1356     $Prop->{display} = {
1357     css => 'display',
1358     dom => 'display',
1359     key => 'display',
1360     parse => $one_keyword_parser,
1361 wakaba 1.11 serialize => $default_serializer,
1362 wakaba 1.6 keyword => {
1363     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
1364     'list-item' => 1, none => 1,
1365     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
1366     'table-column-group' => 1, 'table-header-group' => 1,
1367     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
1368     },
1369 wakaba 1.9 initial => ["KEYWORD", "inline"],
1370     #inherited => 0,
1371     compute => sub {
1372     my ($self, $element, $prop_name, $specified_value) = @_;
1373     ## NOTE: CSS 2.1 Section 9.7.
1374    
1375     ## WARNING: |compute| for 'float' property invoke this CODE
1376     ## in some case. Careless modification might cause a infinite loop.
1377    
1378 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1379 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1380     ## Case 1 [CSS 2.1]
1381     return $specified_value;
1382     } else {
1383     my $position = $self->get_computed_value ($element, 'position');
1384     if ($position->[0] eq 'KEYWORD' and
1385     ($position->[1] eq 'absolute' or
1386     $position->[1] eq 'fixed')) {
1387     ## Case 2 [CSS 2.1]
1388     #
1389     } else {
1390     my $float = $self->get_computed_value ($element, 'float');
1391     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
1392     ## Caes 3 [CSS 2.1]
1393     #
1394     } elsif (not defined $element->manakai_parent_element) {
1395     ## Case 4 [CSS 2.1]
1396     #
1397     } else {
1398     ## Case 5 [CSS 2.1]
1399     return $specified_value;
1400     }
1401     }
1402    
1403     return ["KEYWORD",
1404     {
1405     'inline-table' => 'table',
1406     inline => 'block',
1407     'run-in' => 'block',
1408     'table-row-group' => 'block',
1409     'table-column' => 'block',
1410     'table-column-group' => 'block',
1411     'table-header-group' => 'block',
1412     'table-footer-group' => 'block',
1413     'table-row' => 'block',
1414     'table-cell' => 'block',
1415     'table-caption' => 'block',
1416     'inline-block' => 'block',
1417     }->{$specified_value->[1]} || $specified_value->[1]];
1418     }
1419     } else {
1420     return $specified_value; ## Maybe an error of the implementation.
1421     }
1422     },
1423 wakaba 1.6 };
1424     $Attr->{display} = $Prop->{display};
1425     $Key->{display} = $Prop->{display};
1426    
1427     $Prop->{position} = {
1428     css => 'position',
1429     dom => 'position',
1430     key => 'position',
1431     parse => $one_keyword_parser,
1432 wakaba 1.11 serialize => $default_serializer,
1433 wakaba 1.6 keyword => {
1434     static => 1, relative => 1, absolute => 1, fixed => 1,
1435     },
1436 wakaba 1.10 initial => ["KEYWORD", "static"],
1437 wakaba 1.9 #inherited => 0,
1438     compute => $compute_as_specified,
1439 wakaba 1.6 };
1440     $Attr->{position} = $Prop->{position};
1441     $Key->{position} = $Prop->{position};
1442    
1443     $Prop->{float} = {
1444     css => 'float',
1445     dom => 'css_float',
1446     key => 'float',
1447     parse => $one_keyword_parser,
1448 wakaba 1.11 serialize => $default_serializer,
1449 wakaba 1.6 keyword => {
1450     left => 1, right => 1, none => 1,
1451     },
1452 wakaba 1.9 initial => ["KEYWORD", "none"],
1453     #inherited => 0,
1454     compute => sub {
1455     my ($self, $element, $prop_name, $specified_value) = @_;
1456     ## NOTE: CSS 2.1 Section 9.7.
1457    
1458     ## WARNING: |compute| for 'display' property invoke this CODE
1459     ## in some case. Careless modification might cause a infinite loop.
1460    
1461 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1462 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1463     ## Case 1 [CSS 2.1]
1464     return $specified_value;
1465     } else {
1466     my $position = $self->get_computed_value ($element, 'position');
1467     if ($position->[0] eq 'KEYWORD' and
1468     ($position->[1] eq 'absolute' or
1469     $position->[1] eq 'fixed')) {
1470     ## Case 2 [CSS 2.1]
1471     return ["KEYWORD", "none"];
1472     }
1473     }
1474     }
1475    
1476     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
1477     ## on computed value of 'float' property.
1478    
1479     ## Case 3, 4, and 5 [CSS 2.1]
1480     return $specified_value;
1481     },
1482 wakaba 1.6 };
1483     $Attr->{css_float} = $Prop->{float};
1484     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
1485     $Key->{float} = $Prop->{float};
1486    
1487     $Prop->{clear} = {
1488     css => 'clear',
1489     dom => 'clear',
1490     key => 'clear',
1491     parse => $one_keyword_parser,
1492 wakaba 1.11 serialize => $default_serializer,
1493 wakaba 1.6 keyword => {
1494     left => 1, right => 1, none => 1, both => 1,
1495     },
1496 wakaba 1.9 initial => ["KEYWORD", "none"],
1497     #inherited => 0,
1498     compute => $compute_as_specified,
1499 wakaba 1.6 };
1500     $Attr->{clear} = $Prop->{clear};
1501     $Key->{clear} = $Prop->{clear};
1502    
1503     $Prop->{direction} = {
1504     css => 'direction',
1505     dom => 'direction',
1506     key => 'direction',
1507     parse => $one_keyword_parser,
1508 wakaba 1.11 serialize => $default_serializer,
1509 wakaba 1.6 keyword => {
1510     ltr => 1, rtl => 1,
1511     },
1512 wakaba 1.9 initial => ["KEYWORD", "ltr"],
1513     inherited => 1,
1514     compute => $compute_as_specified,
1515 wakaba 1.6 };
1516     $Attr->{direction} = $Prop->{direction};
1517     $Key->{direction} = $Prop->{direction};
1518    
1519     $Prop->{'unicode-bidi'} = {
1520     css => 'unicode-bidi',
1521     dom => 'unicode_bidi',
1522     key => 'unicode_bidi',
1523     parse => $one_keyword_parser,
1524 wakaba 1.11 serialize => $default_serializer,
1525 wakaba 1.6 keyword => {
1526     normal => 1, embed => 1, 'bidi-override' => 1,
1527     },
1528 wakaba 1.9 initial => ["KEYWORD", "normal"],
1529     #inherited => 0,
1530     compute => $compute_as_specified,
1531 wakaba 1.6 };
1532     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
1533     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
1534    
1535 wakaba 1.55 $Prop->{'overflow-x'} = {
1536     css => 'overflow-x',
1537     dom => 'overflow_x',
1538     key => 'overflow_x',
1539 wakaba 1.11 parse => $one_keyword_parser,
1540     serialize => $default_serializer,
1541 wakaba 1.55 serialize_multiple => sub {
1542     my $self = shift;
1543    
1544     my $x = $self->overflow_x;
1545     my $xi = $self->get_property_priority ('overflow-x');
1546     my $y = $self->overflow_y;
1547     my $yi = $self->get_property_priority ('overflow-y');
1548    
1549     if (length $x) {
1550     if (length $y) {
1551     if ($x eq $y and $xi eq $yi) {
1552     return {overflow => [$x, $xi]};
1553     } else {
1554     return {'overflow-x' => [$x, $xi], 'overflow-y' => [$y, $yi]};
1555     }
1556     } else {
1557     return {'overflow-x' => [$x, $xi]};
1558     }
1559     } else {
1560     if (length $y) {
1561     return {'overflow-y' => [$y, $yi]};
1562     } else {
1563     return {};
1564     }
1565     }
1566     },
1567 wakaba 1.11 keyword => {
1568     visible => 1, hidden => 1, scroll => 1, auto => 1,
1569 wakaba 1.55 '-moz-hidden-unscrollable' => 1, '-webkit-marquee' => 1,
1570 wakaba 1.11 },
1571     initial => ["KEYWORD", "visible"],
1572     #inherited => 0,
1573     compute => $compute_as_specified,
1574     };
1575 wakaba 1.55 $Attr->{overflow_x} = $Prop->{'overflow-x'};
1576     $Key->{overflow_x} = $Prop->{'overflow-x'};
1577    
1578     $Prop->{'overflow-y'} = {
1579     css => 'overflow-y',
1580     dom => 'overflow_y',
1581     key => 'overflow_y',
1582     parse => $one_keyword_parser,
1583     serialize => $default_serializer,
1584     serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1585     keyword => $Prop->{'overflow-x'}->{keyword},
1586     initial => ["KEYWORD", "visible"],
1587     #inherited => 0,
1588     compute => $compute_as_specified,
1589     };
1590     $Attr->{overflow_y} = $Prop->{'overflow-y'};
1591     $Key->{overflow_y} = $Prop->{'overflow-y'};
1592    
1593     $Prop->{overflow} = {
1594     css => 'overflow',
1595     dom => 'overflow',
1596     key => 'overflow',
1597     parse => sub {
1598     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1599     my ($t, $pv) = $one_keyword_parser->($self, $prop_name, $tt, $t, $onerror);
1600     if (defined $pv) {
1601     return ($t, {'overflow-x' => $pv->{overflow},
1602     'overflow-y' => $pv->{overflow}});
1603     } else {
1604     return ($t, $pv);
1605     }
1606     },
1607     keyword => $Prop->{'overflow-x'}->{keyword},
1608     serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1609     };
1610 wakaba 1.11 $Attr->{overflow} = $Prop->{overflow};
1611     $Key->{overflow} = $Prop->{overflow};
1612    
1613     $Prop->{visibility} = {
1614     css => 'visibility',
1615     dom => 'visibility',
1616     key => 'visibility',
1617     parse => $one_keyword_parser,
1618     serialize => $default_serializer,
1619     keyword => {
1620     visible => 1, hidden => 1, collapse => 1,
1621     },
1622     initial => ["KEYWORD", "visible"],
1623     #inherited => 0,
1624     compute => $compute_as_specified,
1625     };
1626     $Attr->{visibility} = $Prop->{visibility};
1627     $Key->{visibility} = $Prop->{visibility};
1628    
1629     $Prop->{'list-style-type'} = {
1630     css => 'list-style-type',
1631     dom => 'list_style_type',
1632     key => 'list_style_type',
1633     parse => $one_keyword_parser,
1634     serialize => $default_serializer,
1635     keyword => {
1636     qw/
1637     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
1638     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
1639     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
1640     none 1
1641     /,
1642     },
1643     initial => ["KEYWORD", 'disc'],
1644     inherited => 1,
1645     compute => $compute_as_specified,
1646     };
1647     $Attr->{list_style_type} = $Prop->{'list-style-type'};
1648     $Key->{list_style_type} = $Prop->{'list-style-type'};
1649    
1650     $Prop->{'list-style-position'} = {
1651     css => 'list-style-position',
1652     dom => 'list_style_position',
1653     key => 'list_style_position',
1654     parse => $one_keyword_parser,
1655     serialize => $default_serializer,
1656     keyword => {
1657     inside => 1, outside => 1,
1658     },
1659     initial => ["KEYWORD", 'outside'],
1660     inherited => 1,
1661     compute => $compute_as_specified,
1662     };
1663     $Attr->{list_style_position} = $Prop->{'list-style-position'};
1664     $Key->{list_style_position} = $Prop->{'list-style-position'};
1665    
1666 wakaba 1.12 $Prop->{'page-break-before'} = {
1667     css => 'page-break-before',
1668     dom => 'page_break_before',
1669     key => 'page_break_before',
1670     parse => $one_keyword_parser,
1671     serialize => $default_serializer,
1672     keyword => {
1673     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1674     },
1675     initial => ["KEYWORD", 'auto'],
1676 wakaba 1.15 #inherited => 0,
1677 wakaba 1.12 compute => $compute_as_specified,
1678     };
1679     $Attr->{page_break_before} = $Prop->{'page-break-before'};
1680     $Key->{page_break_before} = $Prop->{'page-break-before'};
1681    
1682     $Prop->{'page-break-after'} = {
1683     css => 'page-break-after',
1684     dom => 'page_break_after',
1685     key => 'page_break_after',
1686     parse => $one_keyword_parser,
1687     serialize => $default_serializer,
1688     keyword => {
1689     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1690     },
1691     initial => ["KEYWORD", 'auto'],
1692 wakaba 1.15 #inherited => 0,
1693 wakaba 1.12 compute => $compute_as_specified,
1694     };
1695     $Attr->{page_break_after} = $Prop->{'page-break-after'};
1696     $Key->{page_break_after} = $Prop->{'page-break-after'};
1697    
1698     $Prop->{'page-break-inside'} = {
1699     css => 'page-break-inside',
1700     dom => 'page_break_inside',
1701     key => 'page_break_inside',
1702     parse => $one_keyword_parser,
1703     serialize => $default_serializer,
1704     keyword => {
1705     auto => 1, avoid => 1,
1706     },
1707     initial => ["KEYWORD", 'auto'],
1708     inherited => 1,
1709     compute => $compute_as_specified,
1710     };
1711     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
1712     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
1713    
1714 wakaba 1.15 $Prop->{'background-repeat'} = {
1715     css => 'background-repeat',
1716     dom => 'background_repeat',
1717     key => 'background_repeat',
1718     parse => $one_keyword_parser,
1719     serialize => $default_serializer,
1720 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
1721 wakaba 1.15 keyword => {
1722     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
1723     },
1724     initial => ["KEYWORD", 'repeat'],
1725     #inherited => 0,
1726     compute => $compute_as_specified,
1727     };
1728     $Attr->{background_repeat} = $Prop->{'background-repeat'};
1729     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
1730    
1731     $Prop->{'background-attachment'} = {
1732     css => 'background-attachment',
1733     dom => 'background_attachment',
1734     key => 'background_attachment',
1735     parse => $one_keyword_parser,
1736     serialize => $default_serializer,
1737 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
1738 wakaba 1.15 keyword => {
1739     scroll => 1, fixed => 1,
1740     },
1741     initial => ["KEYWORD", 'scroll'],
1742     #inherited => 0,
1743     compute => $compute_as_specified,
1744     };
1745     $Attr->{background_attachment} = $Prop->{'background-attachment'};
1746     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
1747    
1748     $Prop->{'font-style'} = {
1749     css => 'font-style',
1750 wakaba 1.18 dom => 'font_style',
1751     key => 'font_style',
1752 wakaba 1.15 parse => $one_keyword_parser,
1753     serialize => $default_serializer,
1754     keyword => {
1755     normal => 1, italic => 1, oblique => 1,
1756     },
1757     initial => ["KEYWORD", 'normal'],
1758     inherited => 1,
1759     compute => $compute_as_specified,
1760     };
1761     $Attr->{font_style} = $Prop->{'font-style'};
1762     $Key->{font_style} = $Prop->{'font-style'};
1763    
1764     $Prop->{'font-variant'} = {
1765     css => 'font-variant',
1766     dom => 'font_variant',
1767     key => 'font_variant',
1768     parse => $one_keyword_parser,
1769     serialize => $default_serializer,
1770     keyword => {
1771     normal => 1, 'small-caps' => 1,
1772     },
1773     initial => ["KEYWORD", 'normal'],
1774     inherited => 1,
1775     compute => $compute_as_specified,
1776     };
1777     $Attr->{font_variant} = $Prop->{'font-variant'};
1778     $Key->{font_variant} = $Prop->{'font-variant'};
1779    
1780 wakaba 1.16 $Prop->{'text-align'} = {
1781     css => 'text-align',
1782     dom => 'text_align',
1783     key => 'text_align',
1784     parse => $one_keyword_parser,
1785     serialize => $default_serializer,
1786     keyword => {
1787     left => 1, right => 1, center => 1, justify => 1, ## CSS 2
1788     begin => 1, end => 1, ## CSS 3
1789     },
1790     initial => ["KEYWORD", 'begin'],
1791     inherited => 1,
1792     compute => $compute_as_specified,
1793     };
1794     $Attr->{text_align} = $Prop->{'text-align'};
1795     $Key->{text_align} = $Prop->{'text-align'};
1796    
1797     $Prop->{'text-transform'} = {
1798     css => 'text-transform',
1799     dom => 'text_transform',
1800     key => 'text_transform',
1801     parse => $one_keyword_parser,
1802     serialize => $default_serializer,
1803     keyword => {
1804     capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
1805     },
1806     initial => ["KEYWORD", 'none'],
1807     inherited => 1,
1808     compute => $compute_as_specified,
1809     };
1810     $Attr->{text_transform} = $Prop->{'text-transform'};
1811     $Key->{text_transform} = $Prop->{'text-transform'};
1812    
1813     $Prop->{'white-space'} = {
1814     css => 'white-space',
1815     dom => 'white_space',
1816     key => 'white_space',
1817     parse => $one_keyword_parser,
1818     serialize => $default_serializer,
1819     keyword => {
1820     normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
1821     },
1822     initial => ["KEYWORD", 'normal'],
1823     inherited => 1,
1824     compute => $compute_as_specified,
1825     };
1826     $Attr->{white_space} = $Prop->{'white-space'};
1827     $Key->{white_space} = $Prop->{'white-space'};
1828    
1829     $Prop->{'caption-side'} = {
1830     css => 'caption-side',
1831     dom => 'caption_side',
1832     key => 'caption_side',
1833     parse => $one_keyword_parser,
1834     serialize => $default_serializer,
1835     keyword => {
1836     top => 1, bottom => 1,
1837     },
1838     initial => ['KEYWORD', 'top'],
1839     inherited => 1,
1840     compute => $compute_as_specified,
1841     };
1842     $Attr->{caption_side} = $Prop->{'caption-side'};
1843     $Key->{caption_side} = $Prop->{'caption-side'};
1844    
1845     $Prop->{'table-layout'} = {
1846     css => 'table-layout',
1847     dom => 'table_layout',
1848     key => 'table_layout',
1849     parse => $one_keyword_parser,
1850     serialize => $default_serializer,
1851     keyword => {
1852     auto => 1, fixed => 1,
1853     },
1854     initial => ['KEYWORD', 'auto'],
1855     #inherited => 0,
1856     compute => $compute_as_specified,
1857     };
1858     $Attr->{table_layout} = $Prop->{'table-layout'};
1859     $Key->{table_layout} = $Prop->{'table-layout'};
1860    
1861     $Prop->{'border-collapse'} = {
1862     css => 'border-collapse',
1863     dom => 'border_collapse',
1864     key => 'border_collapse',
1865     parse => $one_keyword_parser,
1866     serialize => $default_serializer,
1867     keyword => {
1868     collapse => 1, separate => 1,
1869     },
1870     initial => ['KEYWORD', 'separate'],
1871     inherited => 1,
1872     compute => $compute_as_specified,
1873     };
1874     $Attr->{border_collapse} = $Prop->{'border-collapse'};
1875     $Key->{border_collapse} = $Prop->{'border-collapse'};
1876    
1877     $Prop->{'empty-cells'} = {
1878     css => 'empty-cells',
1879     dom => 'empty_cells',
1880     key => 'empty_cells',
1881     parse => $one_keyword_parser,
1882     serialize => $default_serializer,
1883     keyword => {
1884     show => 1, hide => 1,
1885     },
1886     initial => ['KEYWORD', 'show'],
1887     inherited => 1,
1888     compute => $compute_as_specified,
1889     };
1890     $Attr->{empty_cells} = $Prop->{'empty-cells'};
1891     $Key->{empty_cells} = $Prop->{'empty-cells'};
1892    
1893 wakaba 1.11 $Prop->{'z-index'} = {
1894     css => 'z-index',
1895     dom => 'z_index',
1896     key => 'z_index',
1897     parse => sub {
1898     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1899    
1900 wakaba 1.51 my $has_sign;
1901 wakaba 1.12 my $sign = 1;
1902     if ($t->{type} == MINUS_TOKEN) {
1903     $sign = -1;
1904 wakaba 1.51 $has_sign = 1;
1905     $t = $tt->get_next_token;
1906     } elsif ($t->{type} == PLUS_TOKEN) {
1907     $has_sign = 1;
1908 wakaba 1.12 $t = $tt->get_next_token;
1909     }
1910    
1911 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
1912     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1913     ## browser compatibility issue.
1914     my $value = $t->{number};
1915     $t = $tt->get_next_token;
1916 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1917 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
1918 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
1919     if ($value eq 'auto') {
1920     ## NOTE: |z-index| is the default value and therefore it must be
1921     ## supported anyway.
1922 wakaba 1.51 $t = $tt->get_next_token;
1923 wakaba 1.11 return ($t, {$prop_name => ["KEYWORD", 'auto']});
1924     } elsif ($value eq 'inherit') {
1925 wakaba 1.51 $t = $tt->get_next_token;
1926 wakaba 1.11 return ($t, {$prop_name => ['INHERIT']});
1927     }
1928     }
1929    
1930 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
1931 wakaba 1.11 level => $self->{must_level},
1932 wakaba 1.38 uri => \$self->{href},
1933 wakaba 1.11 token => $t);
1934     return ($t, undef);
1935     },
1936     serialize => $default_serializer,
1937     initial => ['KEYWORD', 'auto'],
1938     #inherited => 0,
1939     compute => $compute_as_specified,
1940     };
1941     $Attr->{z_index} = $Prop->{'z-index'};
1942     $Key->{z_index} = $Prop->{'z-index'};
1943    
1944 wakaba 1.12 $Prop->{orphans} = {
1945     css => 'orphans',
1946     dom => 'orphans',
1947     key => 'orphans',
1948     parse => sub {
1949     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1950    
1951 wakaba 1.52 my $has_sign;
1952 wakaba 1.12 my $sign = 1;
1953     if ($t->{type} == MINUS_TOKEN) {
1954     $t = $tt->get_next_token;
1955 wakaba 1.52 $has_sign = 1;
1956 wakaba 1.12 $sign = -1;
1957 wakaba 1.52 } elsif ($t->{type} == PLUS_TOKEN) {
1958     $t = $tt->get_next_token;
1959     $has_sign = 1;
1960 wakaba 1.12 }
1961    
1962     if ($t->{type} == NUMBER_TOKEN) {
1963     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1964     ## <http://suika.fam.cx/gate/2005/sw/widows> for
1965     ## browser compatibility issue.
1966     my $value = $t->{number};
1967     $t = $tt->get_next_token;
1968     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1969 wakaba 1.52 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
1970 wakaba 1.12 my $value = lc $t->{value}; ## TODO: case
1971     if ($value eq 'inherit') {
1972 wakaba 1.52 $t = $tt->get_next_token;
1973 wakaba 1.12 return ($t, {$prop_name => ['INHERIT']});
1974     }
1975     }
1976    
1977 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
1978 wakaba 1.12 level => $self->{must_level},
1979 wakaba 1.38 uri => \$self->{href},
1980 wakaba 1.12 token => $t);
1981     return ($t, undef);
1982     },
1983     serialize => $default_serializer,
1984     initial => ['NUMBER', 2],
1985     inherited => 1,
1986     compute => $compute_as_specified,
1987     };
1988     $Attr->{orphans} = $Prop->{orphans};
1989     $Key->{orphans} = $Prop->{orphans};
1990    
1991     $Prop->{widows} = {
1992     css => 'widows',
1993     dom => 'widows',
1994     key => 'widows',
1995     parse => $Prop->{orphans}->{parse},
1996     serialize => $default_serializer,
1997     initial => ['NUMBER', 2],
1998     inherited => 1,
1999     compute => $compute_as_specified,
2000     };
2001     $Attr->{widows} = $Prop->{widows};
2002     $Key->{widows} = $Prop->{widows};
2003    
2004 wakaba 1.32 $Prop->{opacity} = {
2005     css => 'opacity',
2006     dom => 'opacity',
2007     key => 'opacity',
2008     parse => sub {
2009     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2010    
2011 wakaba 1.54 my $has_sign;
2012 wakaba 1.32 my $sign = 1;
2013     if ($t->{type} == MINUS_TOKEN) {
2014     $t = $tt->get_next_token;
2015 wakaba 1.54 $has_sign = 1;
2016 wakaba 1.32 $sign = -1;
2017 wakaba 1.54 } elsif ($t->{type} == PLUS_TOKEN) {
2018     $t = $tt->get_next_token;
2019     $has_sign = 1;
2020 wakaba 1.32 }
2021    
2022     if ($t->{type} == NUMBER_TOKEN) {
2023     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/opacity> for
2024     ## browser compatibility issue.
2025     my $value = $t->{number};
2026     $t = $tt->get_next_token;
2027     return ($t, {$prop_name => ["NUMBER", $sign * $value]});
2028 wakaba 1.54 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2029 wakaba 1.32 my $value = lc $t->{value}; ## TODO: case
2030     if ($value eq 'inherit') {
2031 wakaba 1.54 $t = $tt->get_next_token;
2032 wakaba 1.32 return ($t, {$prop_name => ['INHERIT']});
2033     }
2034     }
2035    
2036 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
2037 wakaba 1.32 level => $self->{must_level},
2038 wakaba 1.38 uri => \$self->{href},
2039 wakaba 1.32 token => $t);
2040     return ($t, undef);
2041     },
2042     serialize => $default_serializer,
2043     initial => ['NUMBER', 2],
2044     inherited => 1,
2045     compute => sub {
2046     my ($self, $element, $prop_name, $specified_value) = @_;
2047    
2048     if (defined $specified_value) {
2049     if ($specified_value->[0] eq 'NUMBER') {
2050     if ($specified_value->[1] < 0) {
2051     return ['NUMBER', 0];
2052     } elsif ($specified_value->[1] > 1) {
2053     return ['NUMBER', 1];
2054     }
2055     }
2056     }
2057    
2058     return $specified_value;
2059     },
2060     serialize_multiple => sub {
2061     ## NOTE: This CODE is necessary to avoid two 'opacity' properties
2062     ## are outputed in |cssText| (for 'opacity' and for '-moz-opacity').
2063 wakaba 1.43 return {opacity => [shift->opacity]},
2064 wakaba 1.32 },
2065     };
2066     $Attr->{opacity} = $Prop->{opacity};
2067     $Key->{opacity} = $Prop->{opacity};
2068    
2069     $Prop->{'-moz-opacity'} = $Prop->{opacity};
2070 wakaba 1.36 $Attr->{_moz_opacity} = $Attr->{opacity};
2071 wakaba 1.32
2072 wakaba 1.19 my $length_unit = {
2073     em => 1, ex => 1, px => 1,
2074     in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
2075     };
2076    
2077 wakaba 1.18 $Prop->{'font-size'} = {
2078     css => 'font-size',
2079     dom => 'font_size',
2080     key => 'font_size',
2081     parse => sub {
2082     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2083    
2084 wakaba 1.49 my $has_sign;
2085 wakaba 1.18 my $sign = 1;
2086     if ($t->{type} == MINUS_TOKEN) {
2087     $t = $tt->get_next_token;
2088     $sign = -1;
2089 wakaba 1.49 $has_sign = 1;
2090     } elsif ($t->{type} == PLUS_TOKEN) {
2091     $t = $tt->get_next_token;
2092     $has_sign = 1;
2093 wakaba 1.18 }
2094    
2095     if ($t->{type} == DIMENSION_TOKEN) {
2096     my $value = $t->{number} * $sign;
2097     my $unit = lc $t->{value}; ## TODO: case
2098 wakaba 1.19 if ($length_unit->{$unit} and $value >= 0) {
2099 wakaba 1.49 $t = $tt->get_next_token;
2100 wakaba 1.18 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2101     }
2102     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2103     my $value = $t->{number} * $sign;
2104 wakaba 1.49 if ($value >= 0) {
2105     $t = $tt->get_next_token;
2106     return ($t, {$prop_name => ['PERCENTAGE', $value]});
2107     }
2108 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2109 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2110 wakaba 1.18 my $value = $t->{number} * $sign;
2111 wakaba 1.49 if ($value >= 0) {
2112     $t = $tt->get_next_token;
2113     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2114     }
2115     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2116 wakaba 1.18 my $value = lc $t->{value}; ## TODO: case
2117     if ({
2118     'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
2119     large => 1, 'x-large' => 1, 'xx-large' => 1,
2120 wakaba 1.49 '-manakai-xxx-large' => 1, '-webkit-xxx-large' => 1,
2121 wakaba 1.18 larger => 1, smaller => 1,
2122     }->{$value}) {
2123 wakaba 1.49 $t = $tt->get_next_token;
2124 wakaba 1.18 return ($t, {$prop_name => ['KEYWORD', $value]});
2125     } elsif ($value eq 'inherit') {
2126 wakaba 1.49 $t = $tt->get_next_token;
2127 wakaba 1.18 return ($t, {$prop_name => ['INHERIT']});
2128     }
2129     }
2130    
2131 wakaba 1.49 $onerror->(type => "syntax error:'$prop_name'",
2132 wakaba 1.18 level => $self->{must_level},
2133 wakaba 1.38 uri => \$self->{href},
2134 wakaba 1.18 token => $t);
2135     return ($t, undef);
2136     },
2137     serialize => $default_serializer,
2138     initial => ['KEYWORD', 'medium'],
2139     inherited => 1,
2140     compute => sub {
2141     my ($self, $element, $prop_name, $specified_value) = @_;
2142    
2143     if (defined $specified_value) {
2144     if ($specified_value->[0] eq 'DIMENSION') {
2145     my $unit = $specified_value->[2];
2146     my $value = $specified_value->[1];
2147    
2148     if ($unit eq 'em' or $unit eq 'ex') {
2149     $value *= 0.5 if $unit eq 'ex';
2150     ## TODO: Preferred way to determine the |ex| size is defined
2151     ## in CSS 2.1.
2152    
2153     my $parent_element = $element->manakai_parent_element;
2154     if (defined $parent_element) {
2155     $value *= $self->get_computed_value ($parent_element, $prop_name)
2156     ->[1];
2157     } else {
2158     $value *= $self->{font_size}->[3]; # medium
2159     }
2160     $unit = 'px';
2161     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2162     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2163     ($value /= 72, $unit = 'in') if $unit eq 'pt';
2164     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2165     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2166     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2167     }
2168 wakaba 1.19 ## else: consistency error
2169 wakaba 1.18
2170     return ['DIMENSION', $value, $unit];
2171     } elsif ($specified_value->[0] eq 'PERCENTAGE') {
2172     my $parent_element = $element->manakai_parent_element;
2173     my $parent_cv;
2174     if (defined $parent_element) {
2175     $parent_cv = $self->get_computed_value
2176     ($parent_element, $prop_name);
2177     } else {
2178     $parent_cv = [undef, $self->{font_size}->[3]];
2179     }
2180     return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
2181     'px'];
2182     } elsif ($specified_value->[0] eq 'KEYWORD') {
2183     if ($specified_value->[1] eq 'larger') {
2184     my $parent_element = $element->manakai_parent_element;
2185     if (defined $parent_element) {
2186     my $parent_cv = $self->get_computed_value
2187     ($parent_element, $prop_name);
2188     return ['DIMENSION',
2189     $self->{get_larger_font_size}->($self, $parent_cv->[1]),
2190     'px'];
2191     } else { ## 'larger' relative to 'medium', initial of 'font-size'
2192     return ['DIMENSION', $self->{font_size}->[4], 'px'];
2193     }
2194     } elsif ($specified_value->[1] eq 'smaller') {
2195     my $parent_element = $element->manakai_parent_element;
2196     if (defined $parent_element) {
2197     my $parent_cv = $self->get_computed_value
2198     ($parent_element, $prop_name);
2199     return ['DIMENSION',
2200     $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
2201     'px'];
2202     } else { ## 'smaller' relative to 'medium', initial of 'font-size'
2203     return ['DIMENSION', $self->{font_size}->[2], 'px'];
2204     }
2205     } else {
2206 wakaba 1.49 ## TODO: different computation in quirks mode?
2207 wakaba 1.18 return ['DIMENSION', $self->{font_size}->[{
2208     'xx-small' => 0,
2209     'x-small' => 1,
2210     small => 2,
2211     medium => 3,
2212     large => 4,
2213     'x-large' => 5,
2214     'xx-large' => 6,
2215     '-manakai-xxx-large' => 7,
2216 wakaba 1.49 '-webkit-xxx-large' => 7,
2217 wakaba 1.18 }->{$specified_value->[1]}], 'px'];
2218     }
2219     }
2220     }
2221    
2222     return $specified_value;
2223     },
2224     };
2225     $Attr->{font_size} = $Prop->{'font-size'};
2226     $Key->{font_size} = $Prop->{'font-size'};
2227    
2228 wakaba 1.19 my $compute_length = sub {
2229     my ($self, $element, $prop_name, $specified_value) = @_;
2230    
2231     if (defined $specified_value) {
2232     if ($specified_value->[0] eq 'DIMENSION') {
2233     my $unit = $specified_value->[2];
2234     my $value = $specified_value->[1];
2235    
2236     if ($unit eq 'em' or $unit eq 'ex') {
2237     $value *= 0.5 if $unit eq 'ex';
2238     ## TODO: Preferred way to determine the |ex| size is defined
2239     ## in CSS 2.1.
2240    
2241     $value *= $self->get_computed_value ($element, 'font-size')->[1];
2242     $unit = 'px';
2243     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2244     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2245     ($value /= 72, $unit = 'in') if $unit eq 'pt';
2246     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2247     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2248     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2249     }
2250    
2251     return ['DIMENSION', $value, $unit];
2252     }
2253     }
2254    
2255     return $specified_value;
2256     }; # $compute_length
2257    
2258 wakaba 1.23 $Prop->{'letter-spacing'} = {
2259     css => 'letter-spacing',
2260     dom => 'letter_spacing',
2261     key => 'letter_spacing',
2262     parse => sub {
2263     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2264    
2265 wakaba 1.24 ## NOTE: Used also for 'word-spacing', '-manakai-border-spacing-x',
2266     ## and '-manakai-border-spacing-y'.
2267 wakaba 1.23
2268 wakaba 1.53 my $has_sign;
2269 wakaba 1.23 my $sign = 1;
2270     if ($t->{type} == MINUS_TOKEN) {
2271     $t = $tt->get_next_token;
2272 wakaba 1.53 $has_sign = 1;
2273 wakaba 1.23 $sign = -1;
2274 wakaba 1.53 } elsif ($t->{type} == PLUS_TOKEN) {
2275     $t = $tt->get_next_token;
2276     $has_sign = 1;
2277 wakaba 1.23 }
2278     my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2279    
2280     if ($t->{type} == DIMENSION_TOKEN) {
2281     my $value = $t->{number} * $sign;
2282     my $unit = lc $t->{value}; ## TODO: case
2283 wakaba 1.24 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2284 wakaba 1.53 $t = $tt->get_next_token;
2285 wakaba 1.23 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2286     }
2287     } elsif ($t->{type} == NUMBER_TOKEN and
2288     ($self->{unitless_px} or $t->{number} == 0)) {
2289     my $value = $t->{number} * $sign;
2290 wakaba 1.53 if ($allow_negative or $value >= 0) {
2291     $t = $tt->get_next_token;
2292     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2293     }
2294     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2295 wakaba 1.23 my $value = lc $t->{value}; ## TODO: case
2296 wakaba 1.24 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2297 wakaba 1.53 $t = $tt->get_next_token;
2298 wakaba 1.23 return ($t, {$prop_name => ['KEYWORD', $value]});
2299     } elsif ($value eq 'inherit') {
2300 wakaba 1.53 $t = $tt->get_next_token;
2301 wakaba 1.23 return ($t, {$prop_name => ['INHERIT']});
2302     }
2303     }
2304    
2305 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
2306 wakaba 1.23 level => $self->{must_level},
2307 wakaba 1.38 uri => \$self->{href},
2308 wakaba 1.23 token => $t);
2309     return ($t, undef);
2310     },
2311 wakaba 1.24 allow_negative => 1,
2312     keyword => {normal => 1},
2313 wakaba 1.23 serialize => $default_serializer,
2314     initial => ['KEYWORD', 'normal'],
2315     inherited => 1,
2316     compute => $compute_length,
2317     };
2318     $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
2319     $Key->{letter_spacing} = $Prop->{'letter-spacing'};
2320    
2321     $Prop->{'word-spacing'} = {
2322     css => 'word-spacing',
2323     dom => 'word_spacing',
2324     key => 'word_spacing',
2325     parse => $Prop->{'letter-spacing'}->{parse},
2326 wakaba 1.24 allow_negative => 1,
2327     keyword => {normal => 1},
2328 wakaba 1.23 serialize => $default_serializer,
2329     initial => ['KEYWORD', 'normal'],
2330     inherited => 1,
2331     compute => $compute_length,
2332     };
2333     $Attr->{word_spacing} = $Prop->{'word-spacing'};
2334     $Key->{word_spacing} = $Prop->{'word-spacing'};
2335    
2336 wakaba 1.24 $Prop->{'-manakai-border-spacing-x'} = {
2337     css => '-manakai-border-spacing-x',
2338     dom => '_manakai_border_spacing_x',
2339     key => 'border_spacing_x',
2340     parse => $Prop->{'letter-spacing'}->{parse},
2341     #allow_negative => 0,
2342     #keyword => {},
2343     serialize => $default_serializer,
2344 wakaba 1.25 serialize_multiple => sub {
2345     my $self = shift;
2346    
2347     my $x = $self->_manakai_border_spacing_x;
2348     my $y = $self->_manakai_border_spacing_y;
2349     my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
2350     my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
2351 wakaba 1.34 if (length $x) {
2352     if (length $y) {
2353 wakaba 1.26 if ($xi eq $yi) {
2354 wakaba 1.25 if ($x eq $y) {
2355 wakaba 1.43 return {'border-spacing' => [$x, $xi]};
2356 wakaba 1.25 } else {
2357 wakaba 1.53 if ($x eq 'inherit' or $y eq 'inherit') {
2358     return {'-manakai-border-spacing-x' => [$x, $xi],
2359     '-manakai-border-spacing-y' => [$y, $yi]};
2360     } else {
2361     return {'border-spacing' => [$x . ' ' . $y, $xi]};
2362     }
2363 wakaba 1.25 }
2364     } else {
2365 wakaba 1.43 return {'-manakai-border-spacing-x' => [$x, $xi],
2366     '-manakai-border-spacing-y' => [$y, $yi]};
2367 wakaba 1.25 }
2368     } else {
2369 wakaba 1.43 return {'-manakai-border-spacing-x' => [$x, $xi]};
2370 wakaba 1.25 }
2371     } else {
2372 wakaba 1.34 if (length $y) {
2373 wakaba 1.43 return {'-manakai-border-spacing-y' => [$y, $yi]};
2374 wakaba 1.25 } else {
2375     return {};
2376     }
2377     }
2378     },
2379 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
2380     inherited => 1,
2381     compute => $compute_length,
2382     };
2383     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2384     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2385    
2386     $Prop->{'-manakai-border-spacing-y'} = {
2387     css => '-manakai-border-spacing-y',
2388     dom => '_manakai_border_spacing_y',
2389     key => 'border_spacing_y',
2390     parse => $Prop->{'letter-spacing'}->{parse},
2391     #allow_negative => 0,
2392     #keyword => {},
2393     serialize => $default_serializer,
2394 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
2395     ->{serialize_multiple},
2396 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
2397     inherited => 1,
2398     compute => $compute_length,
2399     };
2400     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2401     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2402    
2403 wakaba 1.19 $Prop->{'margin-top'} = {
2404     css => 'margin-top',
2405     dom => 'margin_top',
2406     key => 'margin_top',
2407     parse => sub {
2408     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2409    
2410 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
2411 wakaba 1.22 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
2412     ## 'padding-right', 'padding-bottom', 'padding-left',
2413     ## 'border-top-width', 'border-right-width', 'border-bottom-width',
2414 wakaba 1.27 ## 'border-left-width', 'text-indent', 'background-position-x',
2415     ## and 'background-position-y'.
2416 wakaba 1.21
2417 wakaba 1.19 my $sign = 1;
2418 wakaba 1.41 my $has_sign;
2419 wakaba 1.19 if ($t->{type} == MINUS_TOKEN) {
2420     $t = $tt->get_next_token;
2421 wakaba 1.41 $has_sign = 1;
2422 wakaba 1.19 $sign = -1;
2423 wakaba 1.41 } elsif ($t->{type} == PLUS_TOKEN) {
2424     $t = $tt->get_next_token;
2425     $has_sign = 1;
2426 wakaba 1.19 }
2427 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2428 wakaba 1.19
2429     if ($t->{type} == DIMENSION_TOKEN) {
2430     my $value = $t->{number} * $sign;
2431     my $unit = lc $t->{value}; ## TODO: case
2432 wakaba 1.22 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2433 wakaba 1.51 $t = $tt->get_next_token;
2434 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2435     }
2436     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2437     my $value = $t->{number} * $sign;
2438 wakaba 1.51 if ($allow_negative or $value >= 0) {
2439     $t = $tt->get_next_token;
2440     return ($t, {$prop_name => ['PERCENTAGE', $value]});
2441     }
2442 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2443 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2444     my $value = $t->{number} * $sign;
2445 wakaba 1.51 if ($allow_negative or $value >=0) {
2446     $t = $tt->get_next_token;
2447     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2448     }
2449 wakaba 1.41 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2450 wakaba 1.19 my $value = lc $t->{value}; ## TODO: case
2451 wakaba 1.22 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2452 wakaba 1.29 $t = $tt->get_next_token;
2453 wakaba 1.19 return ($t, {$prop_name => ['KEYWORD', $value]});
2454     } elsif ($value eq 'inherit') {
2455 wakaba 1.29 $t = $tt->get_next_token;
2456 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2457     }
2458 wakaba 1.29 ## NOTE: In the "else" case, don't procede the |$t| pointer
2459     ## for the support of 'border-top' property (and similar ones).
2460 wakaba 1.19 }
2461    
2462 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
2463 wakaba 1.19 level => $self->{must_level},
2464 wakaba 1.38 uri => \$self->{href},
2465 wakaba 1.19 token => $t);
2466     return ($t, undef);
2467     },
2468 wakaba 1.22 allow_negative => 1,
2469     keyword => {auto => 1},
2470 wakaba 1.19 serialize => $default_serializer,
2471 wakaba 1.42 serialize_multiple => sub {
2472     my $self = shift;
2473    
2474 wakaba 1.43 ## NOTE: Same as |serialize_multiple| of 'padding-top'.
2475    
2476 wakaba 1.42 my $use_shorthand = 1;
2477     my $t = $self->margin_top;
2478     undef $use_shorthand unless length $t;
2479     my $t_i = $self->get_property_priority ('margin-top');
2480     my $r = $self->margin_right;
2481     undef $use_shorthand
2482     if not length $r or
2483     ($r eq 'inherit' and $t ne 'inherit') or
2484     ($t eq 'inherit' and $r ne 'inherit');
2485     my $r_i = $self->get_property_priority ('margin-right');
2486     undef $use_shorthand unless $r_i eq $t_i;
2487     my $b = $self->margin_bottom;
2488     undef $use_shorthand
2489     if not length $b or
2490     ($b eq 'inherit' and $t ne 'inherit') or
2491     ($t eq 'inherit' and $b ne 'inherit');
2492     my $b_i = $self->get_property_priority ('margin-bottom');
2493     undef $use_shorthand unless $b_i eq $t_i;
2494     my $l = $self->margin_left;
2495     undef $use_shorthand
2496     if not length $l or
2497     ($l eq 'inherit' and $t ne 'inherit') or
2498     ($t eq 'inherit' and $l ne 'inherit');
2499     my $l_i = $self->get_property_priority ('margin-left');
2500     undef $use_shorthand unless $l_i eq $t_i;
2501    
2502     if ($use_shorthand) {
2503     $b .= ' ' . $l if $r ne $l;
2504     $r .= ' ' . $b if $t ne $b;
2505     $t .= ' ' . $r if $t ne $r;
2506 wakaba 1.45 return {margin => [$t, $t_i]};
2507 wakaba 1.42 } else {
2508     my $v = {};
2509     if (length $t) {
2510 wakaba 1.45 $v->{'margin-top'} = [$t, $t_i];
2511 wakaba 1.42 }
2512     if (length $r) {
2513 wakaba 1.45 $v->{'margin-right'} = [$r, $r_i];
2514 wakaba 1.42 }
2515     if (length $b) {
2516 wakaba 1.45 $v->{'margin-bottom'} = [$b, $b_i];
2517 wakaba 1.42 }
2518     if (length $l) {
2519 wakaba 1.45 $v->{'margin-left'} = [$l, $l_i];
2520 wakaba 1.42 }
2521     return $v;
2522     }
2523     },
2524 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2525     #inherited => 0,
2526     compute => $compute_length,
2527     };
2528     $Attr->{margin_top} = $Prop->{'margin-top'};
2529     $Key->{margin_top} = $Prop->{'margin-top'};
2530    
2531     $Prop->{'margin-bottom'} = {
2532     css => 'margin-bottom',
2533     dom => 'margin_bottom',
2534     key => 'margin_bottom',
2535     parse => $Prop->{'margin-top'}->{parse},
2536 wakaba 1.22 allow_negative => 1,
2537     keyword => {auto => 1},
2538 wakaba 1.19 serialize => $default_serializer,
2539 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2540 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2541     #inherited => 0,
2542     compute => $compute_length,
2543     };
2544     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
2545     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
2546    
2547     $Prop->{'margin-right'} = {
2548     css => 'margin-right',
2549     dom => 'margin_right',
2550     key => 'margin_right',
2551     parse => $Prop->{'margin-top'}->{parse},
2552 wakaba 1.22 allow_negative => 1,
2553     keyword => {auto => 1},
2554 wakaba 1.19 serialize => $default_serializer,
2555 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2556 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2557     #inherited => 0,
2558     compute => $compute_length,
2559     };
2560     $Attr->{margin_right} = $Prop->{'margin-right'};
2561     $Key->{margin_right} = $Prop->{'margin-right'};
2562    
2563     $Prop->{'margin-left'} = {
2564     css => 'margin-left',
2565     dom => 'margin_left',
2566     key => 'margin_left',
2567     parse => $Prop->{'margin-top'}->{parse},
2568 wakaba 1.22 allow_negative => 1,
2569     keyword => {auto => 1},
2570 wakaba 1.19 serialize => $default_serializer,
2571 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2572 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2573     #inherited => 0,
2574     compute => $compute_length,
2575     };
2576     $Attr->{margin_left} = $Prop->{'margin-left'};
2577     $Key->{margin_left} = $Prop->{'margin-left'};
2578    
2579 wakaba 1.21 $Prop->{top} = {
2580     css => 'top',
2581     dom => 'top',
2582     key => 'top',
2583     parse => $Prop->{'margin-top'}->{parse},
2584 wakaba 1.22 allow_negative => 1,
2585     keyword => {auto => 1},
2586 wakaba 1.21 serialize => $default_serializer,
2587     initial => ['KEYWORD', 'auto'],
2588     #inherited => 0,
2589     compute_multiple => sub {
2590     my ($self, $element, $eid, $prop_name) = @_;
2591    
2592     my $pos_value = $self->get_computed_value ($element, 'position');
2593     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2594     if ($pos_value->[1] eq 'static') {
2595     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2596     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2597     return;
2598     } elsif ($pos_value->[1] eq 'relative') {
2599     my $top_specified = $self->get_specified_value_no_inherit
2600     ($element, 'top');
2601     if (defined $top_specified and
2602     ($top_specified->[0] eq 'DIMENSION' or
2603     $top_specified->[0] eq 'PERCENTAGE')) {
2604     my $tv = $self->{computed_value}->{$eid}->{top}
2605     = $compute_length->($self, $element, 'top', $top_specified);
2606     $self->{computed_value}->{$eid}->{bottom}
2607     = [$tv->[0], -$tv->[1], $tv->[2]];
2608     } else { # top: auto
2609     my $bottom_specified = $self->get_specified_value_no_inherit
2610     ($element, 'bottom');
2611     if (defined $bottom_specified and
2612     ($bottom_specified->[0] eq 'DIMENSION' or
2613     $bottom_specified->[0] eq 'PERCENTAGE')) {
2614     my $tv = $self->{computed_value}->{$eid}->{bottom}
2615     = $compute_length->($self, $element, 'bottom',
2616     $bottom_specified);
2617     $self->{computed_value}->{$eid}->{top}
2618     = [$tv->[0], -$tv->[1], $tv->[2]];
2619     } else { # bottom: auto
2620     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2621     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2622     }
2623     }
2624     return;
2625     }
2626     }
2627    
2628     my $top_specified = $self->get_specified_value_no_inherit
2629     ($element, 'top');
2630     $self->{computed_value}->{$eid}->{top}
2631     = $compute_length->($self, $element, 'top', $top_specified);
2632     my $bottom_specified = $self->get_specified_value_no_inherit
2633     ($element, 'bottom');
2634     $self->{computed_value}->{$eid}->{bottom}
2635     = $compute_length->($self, $element, 'bottom', $bottom_specified);
2636     },
2637     };
2638     $Attr->{top} = $Prop->{top};
2639     $Key->{top} = $Prop->{top};
2640    
2641     $Prop->{bottom} = {
2642     css => 'bottom',
2643     dom => 'bottom',
2644     key => 'bottom',
2645     parse => $Prop->{'margin-top'}->{parse},
2646 wakaba 1.22 allow_negative => 1,
2647     keyword => {auto => 1},
2648 wakaba 1.21 serialize => $default_serializer,
2649     initial => ['KEYWORD', 'auto'],
2650     #inherited => 0,
2651     compute_multiple => $Prop->{top}->{compute_multiple},
2652     };
2653     $Attr->{bottom} = $Prop->{bottom};
2654     $Key->{bottom} = $Prop->{bottom};
2655    
2656     $Prop->{left} = {
2657     css => 'left',
2658     dom => 'left',
2659     key => 'left',
2660     parse => $Prop->{'margin-top'}->{parse},
2661 wakaba 1.22 allow_negative => 1,
2662     keyword => {auto => 1},
2663 wakaba 1.21 serialize => $default_serializer,
2664     initial => ['KEYWORD', 'auto'],
2665     #inherited => 0,
2666     compute_multiple => sub {
2667     my ($self, $element, $eid, $prop_name) = @_;
2668    
2669     my $pos_value = $self->get_computed_value ($element, 'position');
2670     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2671     if ($pos_value->[1] eq 'static') {
2672     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
2673     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
2674     return;
2675     } elsif ($pos_value->[1] eq 'relative') {
2676     my $left_specified = $self->get_specified_value_no_inherit
2677     ($element, 'left');
2678     if (defined $left_specified and
2679     ($left_specified->[0] eq 'DIMENSION' or
2680     $left_specified->[0] eq 'PERCENTAGE')) {
2681     my $right_specified = $self->get_specified_value_no_inherit
2682     ($element, 'right');
2683     if (defined $right_specified and
2684     ($right_specified->[0] eq 'DIMENSION' or
2685     $right_specified->[0] eq 'PERCENTAGE')) {
2686     my $direction = $self->get_computed_value ($element, 'direction');
2687     if (defined $direction and $direction->[0] eq 'KEYWORD' and
2688     $direction->[0] eq 'ltr') {
2689     my $tv = $self->{computed_value}->{$eid}->{left}
2690     = $compute_length->($self, $element, 'left',
2691     $left_specified);
2692     $self->{computed_value}->{$eid}->{right}
2693     = [$tv->[0], -$tv->[1], $tv->[2]];
2694     } else {
2695     my $tv = $self->{computed_value}->{$eid}->{right}
2696     = $compute_length->($self, $element, 'right',
2697     $right_specified);
2698     $self->{computed_value}->{$eid}->{left}
2699     = [$tv->[0], -$tv->[1], $tv->[2]];
2700     }
2701     } else {
2702     my $tv = $self->{computed_value}->{$eid}->{left}
2703     = $compute_length->($self, $element, 'left', $left_specified);
2704     $self->{computed_value}->{$eid}->{right}
2705     = [$tv->[0], -$tv->[1], $tv->[2]];
2706     }
2707     } else { # left: auto
2708     my $right_specified = $self->get_specified_value_no_inherit
2709     ($element, 'right');
2710     if (defined $right_specified and
2711     ($right_specified->[0] eq 'DIMENSION' or
2712     $right_specified->[0] eq 'PERCENTAGE')) {
2713     my $tv = $self->{computed_value}->{$eid}->{right}
2714     = $compute_length->($self, $element, 'right',
2715     $right_specified);
2716     $self->{computed_value}->{$eid}->{left}
2717     = [$tv->[0], -$tv->[1], $tv->[2]];
2718     } else { # right: auto
2719     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
2720     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
2721     }
2722     }
2723     return;
2724     }
2725     }
2726    
2727     my $left_specified = $self->get_specified_value_no_inherit
2728     ($element, 'left');
2729     $self->{computed_value}->{$eid}->{left}
2730     = $compute_length->($self, $element, 'left', $left_specified);
2731     my $right_specified = $self->get_specified_value_no_inherit
2732     ($element, 'right');
2733     $self->{computed_value}->{$eid}->{right}
2734     = $compute_length->($self, $element, 'right', $right_specified);
2735     },
2736     };
2737     $Attr->{left} = $Prop->{left};
2738     $Key->{left} = $Prop->{left};
2739    
2740     $Prop->{right} = {
2741     css => 'right',
2742     dom => 'right',
2743     key => 'right',
2744     parse => $Prop->{'margin-top'}->{parse},
2745 wakaba 1.51 allow_negative => 1,
2746     keyword => {auto => 1},
2747 wakaba 1.21 serialize => $default_serializer,
2748     initial => ['KEYWORD', 'auto'],
2749     #inherited => 0,
2750     compute_multiple => $Prop->{left}->{compute_multiple},
2751     };
2752     $Attr->{right} = $Prop->{right};
2753     $Key->{right} = $Prop->{right};
2754    
2755 wakaba 1.22 $Prop->{width} = {
2756     css => 'width',
2757     dom => 'width',
2758     key => 'width',
2759     parse => $Prop->{'margin-top'}->{parse},
2760     #allow_negative => 0,
2761     keyword => {auto => 1},
2762     serialize => $default_serializer,
2763     initial => ['KEYWORD', 'auto'],
2764     #inherited => 0,
2765     compute => $compute_length,
2766     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
2767     ## browser compatibility issues.
2768     };
2769     $Attr->{width} = $Prop->{width};
2770     $Key->{width} = $Prop->{width};
2771    
2772     $Prop->{'min-width'} = {
2773     css => 'min-width',
2774     dom => 'min_width',
2775     key => 'min_width',
2776     parse => $Prop->{'margin-top'}->{parse},
2777     #allow_negative => 0,
2778     #keyword => {},
2779     serialize => $default_serializer,
2780     initial => ['DIMENSION', 0, 'px'],
2781     #inherited => 0,
2782     compute => $compute_length,
2783     };
2784     $Attr->{min_width} = $Prop->{'min-width'};
2785     $Key->{min_width} = $Prop->{'min-width'};
2786    
2787     $Prop->{'max-width'} = {
2788     css => 'max-width',
2789     dom => 'max_width',
2790     key => 'max_width',
2791     parse => $Prop->{'margin-top'}->{parse},
2792     #allow_negative => 0,
2793     keyword => {none => 1},
2794     serialize => $default_serializer,
2795     initial => ['KEYWORD', 'none'],
2796     #inherited => 0,
2797     compute => $compute_length,
2798     };
2799     $Attr->{max_width} = $Prop->{'max-width'};
2800     $Key->{max_width} = $Prop->{'max-width'};
2801    
2802     $Prop->{height} = {
2803     css => 'height',
2804     dom => 'height',
2805     key => 'height',
2806     parse => $Prop->{'margin-top'}->{parse},
2807     #allow_negative => 0,
2808     keyword => {auto => 1},
2809     serialize => $default_serializer,
2810     initial => ['KEYWORD', 'auto'],
2811     #inherited => 0,
2812     compute => $compute_length,
2813     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
2814     ## browser compatibility issues.
2815     };
2816     $Attr->{height} = $Prop->{height};
2817     $Key->{height} = $Prop->{height};
2818    
2819     $Prop->{'min-height'} = {
2820     css => 'min-height',
2821     dom => 'min_height',
2822     key => 'min_height',
2823     parse => $Prop->{'margin-top'}->{parse},
2824     #allow_negative => 0,
2825     #keyword => {},
2826     serialize => $default_serializer,
2827     initial => ['DIMENSION', 0, 'px'],
2828     #inherited => 0,
2829     compute => $compute_length,
2830     };
2831     $Attr->{min_height} = $Prop->{'min-height'};
2832     $Key->{min_height} = $Prop->{'min-height'};
2833    
2834     $Prop->{'max-height'} = {
2835     css => 'max-height',
2836     dom => 'max_height',
2837     key => 'max_height',
2838     parse => $Prop->{'margin-top'}->{parse},
2839     #allow_negative => 0,
2840     keyword => {none => 1},
2841     serialize => $default_serializer,
2842     initial => ['KEYWORD', 'none'],
2843     #inherited => 0,
2844     compute => $compute_length,
2845     };
2846     $Attr->{max_height} = $Prop->{'max-height'};
2847     $Key->{max_height} = $Prop->{'max-height'};
2848    
2849     $Prop->{'line-height'} = {
2850     css => 'line-height',
2851     dom => 'line_height',
2852     key => 'line_height',
2853 wakaba 1.19 parse => sub {
2854     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2855    
2856 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
2857     ## for unitless numbers.
2858    
2859 wakaba 1.51 my $has_sign;
2860 wakaba 1.19 my $sign = 1;
2861     if ($t->{type} == MINUS_TOKEN) {
2862     $t = $tt->get_next_token;
2863     $sign = -1;
2864 wakaba 1.51 $has_sign = 1;
2865     } elsif ($t->{type} == PLUS_TOKEN) {
2866     $t = $tt->get_next_token;
2867     $has_sign = 1;
2868 wakaba 1.19 }
2869    
2870     if ($t->{type} == DIMENSION_TOKEN) {
2871     my $value = $t->{number} * $sign;
2872     my $unit = lc $t->{value}; ## TODO: case
2873     if ($length_unit->{$unit} and $value >= 0) {
2874 wakaba 1.51 $t = $tt->get_next_token;
2875 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2876     }
2877     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2878     my $value = $t->{number} * $sign;
2879 wakaba 1.51 if ($value >= 0) {
2880     $t = $tt->get_next_token;
2881     return ($t, {$prop_name => ['PERCENTAGE', $value]});
2882     }
2883 wakaba 1.22 } elsif ($t->{type} == NUMBER_TOKEN) {
2884 wakaba 1.19 my $value = $t->{number} * $sign;
2885 wakaba 1.51 if ($value >= 0) {
2886     $t = $tt->get_next_token;
2887     return ($t, {$prop_name => ['NUMBER', $value]});
2888     }
2889     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2890 wakaba 1.19 my $value = lc $t->{value}; ## TODO: case
2891 wakaba 1.22 if ($value eq 'normal') {
2892 wakaba 1.51 $t = $tt->get_next_token;
2893 wakaba 1.22 return ($t, {$prop_name => ['KEYWORD', $value]});
2894     } elsif ($value eq 'inherit') {
2895 wakaba 1.51 $t = $tt->get_next_token;
2896 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2897     }
2898     }
2899    
2900 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
2901 wakaba 1.19 level => $self->{must_level},
2902 wakaba 1.38 uri => \$self->{href},
2903 wakaba 1.19 token => $t);
2904     return ($t, undef);
2905     },
2906     serialize => $default_serializer,
2907 wakaba 1.22 initial => ['KEYWORD', 'normal'],
2908     inherited => 1,
2909     compute => $compute_length,
2910     };
2911     $Attr->{line_height} = $Prop->{'line-height'};
2912     $Key->{line_height} = $Prop->{'line-height'};
2913    
2914     $Prop->{'vertical-align'} = {
2915     css => 'vertical-align',
2916     dom => 'vertical_align',
2917     key => 'vertical_align',
2918     parse => $Prop->{'margin-top'}->{parse},
2919     allow_negative => 1,
2920     keyword => {
2921     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
2922     middle => 1, bottom => 1, 'text-bottom' => 1,
2923     },
2924     ## NOTE: Currently, we don't support option to select subset of keywords
2925     ## supported by application (i.e.
2926     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
2927     ## it?
2928     serialize => $default_serializer,
2929     initial => ['KEYWORD', 'baseline'],
2930     #inherited => 0,
2931     compute => $compute_length,
2932     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
2933     ## browser compatibility issues.
2934     };
2935     $Attr->{vertical_align} = $Prop->{'vertical-align'};
2936     $Key->{vertical_align} = $Prop->{'vertical-align'};
2937    
2938 wakaba 1.23 $Prop->{'text-indent'} = {
2939     css => 'text-indent',
2940     dom => 'text_indent',
2941     key => 'text_indent',
2942     parse => $Prop->{'margin-top'}->{parse},
2943     allow_negative => 1,
2944     keyword => {},
2945     serialize => $default_serializer,
2946     initial => ['DIMENSION', 0, 'px'],
2947     inherited => 1,
2948     compute => $compute_length,
2949     };
2950     $Attr->{text_indent} = $Prop->{'text-indent'};
2951     $Key->{text_indent} = $Prop->{'text-indent'};
2952    
2953 wakaba 1.27 $Prop->{'background-position-x'} = {
2954     css => 'background-position-x',
2955     dom => 'background_position_x',
2956     key => 'background_position_x',
2957     parse => $Prop->{'margin-top'}->{parse},
2958     allow_negative => 1,
2959     keyword => {left => 1, center => 1, right => 1},
2960     serialize => $default_serializer,
2961     initial => ['PERCENTAGE', 0],
2962     #inherited => 0,
2963     compute => sub {
2964     my ($self, $element, $prop_name, $specified_value) = @_;
2965    
2966     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2967     my $v = {
2968     left => 0, center => 50, right => 100, top => 0, bottom => 100,
2969     }->{$specified_value->[1]};
2970     if (defined $v) {
2971     return ['PERCENTAGE', $v];
2972     } else {
2973     return $specified_value;
2974     }
2975     } else {
2976     return $compute_length->(@_);
2977     }
2978     },
2979 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
2980 wakaba 1.27 };
2981     $Attr->{background_position_x} = $Prop->{'background-position-x'};
2982     $Key->{background_position_x} = $Prop->{'background-position-x'};
2983    
2984     $Prop->{'background-position-y'} = {
2985     css => 'background-position-y',
2986     dom => 'background_position_y',
2987     key => 'background_position_y',
2988     parse => $Prop->{'margin-top'}->{parse},
2989     allow_negative => 1,
2990     keyword => {top => 1, center => 1, bottom => 1},
2991     serialize => $default_serializer,
2992 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
2993 wakaba 1.27 initial => ['PERCENTAGE', 0],
2994     #inherited => 0,
2995     compute => $Prop->{'background-position-x'}->{compute},
2996     };
2997     $Attr->{background_position_y} = $Prop->{'background-position-y'};
2998     $Key->{background_position_y} = $Prop->{'background-position-y'};
2999    
3000 wakaba 1.22 $Prop->{'padding-top'} = {
3001     css => 'padding-top',
3002     dom => 'padding_top',
3003     key => 'padding_top',
3004     parse => $Prop->{'margin-top'}->{parse},
3005     #allow_negative => 0,
3006     #keyword => {},
3007     serialize => $default_serializer,
3008 wakaba 1.43 serialize_multiple => sub {
3009     my $self = shift;
3010    
3011     ## NOTE: Same as |serialize_multiple| of 'margin-top'.
3012    
3013     my $use_shorthand = 1;
3014     my $t = $self->padding_top;
3015     undef $use_shorthand unless length $t;
3016     my $t_i = $self->get_property_priority ('padding-top');
3017     my $r = $self->padding_right;
3018     undef $use_shorthand
3019     if not length $r or
3020     ($r eq 'inherit' and $t ne 'inherit') or
3021     ($t eq 'inherit' and $r ne 'inherit');
3022     my $r_i = $self->get_property_priority ('padding-right');
3023     undef $use_shorthand unless $r_i eq $t_i;
3024     my $b = $self->padding_bottom;
3025     undef $use_shorthand
3026     if not length $b or
3027     ($b eq 'inherit' and $t ne 'inherit') or
3028     ($t eq 'inherit' and $b ne 'inherit');
3029     my $b_i = $self->get_property_priority ('padding-bottom');
3030     undef $use_shorthand unless $b_i eq $t_i;
3031     my $l = $self->padding_left;
3032     undef $use_shorthand
3033     if not length $l or
3034     ($l eq 'inherit' and $t ne 'inherit') or
3035     ($t eq 'inherit' and $l ne 'inherit');
3036     my $l_i = $self->get_property_priority ('padding-left');
3037     undef $use_shorthand unless $l_i eq $t_i;
3038    
3039     if ($use_shorthand) {
3040     $b .= ' ' . $l if $r ne $l;
3041     $r .= ' ' . $b if $t ne $b;
3042     $t .= ' ' . $r if $t ne $r;
3043 wakaba 1.45 return {padding => [$t, $t_i]};
3044 wakaba 1.43 } else {
3045     my $v = {};
3046     if (length $t) {
3047 wakaba 1.45 $v->{'padding-top'} = [$t, $t_i];
3048 wakaba 1.43 }
3049     if (length $r) {
3050 wakaba 1.45 $v->{'padding-right'} = [$r, $r_i];
3051 wakaba 1.43 }
3052     if (length $b) {
3053 wakaba 1.45 $v->{'padding-bottom'} = [$b, $b_i];
3054 wakaba 1.43 }
3055     if (length $l) {
3056 wakaba 1.45 $v->{'padding-left'} = [$l, $l_i];
3057 wakaba 1.43 }
3058     return $v;
3059     }
3060     },
3061 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3062     #inherited => 0,
3063     compute => $compute_length,
3064     };
3065     $Attr->{padding_top} = $Prop->{'padding-top'};
3066     $Key->{padding_top} = $Prop->{'padding-top'};
3067    
3068     $Prop->{'padding-bottom'} = {
3069     css => 'padding-bottom',
3070     dom => 'padding_bottom',
3071     key => 'padding_bottom',
3072     parse => $Prop->{'padding-top'}->{parse},
3073 wakaba 1.22 #allow_negative => 0,
3074     #keyword => {},
3075 wakaba 1.19 serialize => $default_serializer,
3076 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3077 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3078     #inherited => 0,
3079     compute => $compute_length,
3080     };
3081     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
3082     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
3083    
3084     $Prop->{'padding-right'} = {
3085     css => 'padding-right',
3086     dom => 'padding_right',
3087     key => 'padding_right',
3088     parse => $Prop->{'padding-top'}->{parse},
3089 wakaba 1.22 #allow_negative => 0,
3090     #keyword => {},
3091 wakaba 1.19 serialize => $default_serializer,
3092 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3093 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3094     #inherited => 0,
3095     compute => $compute_length,
3096     };
3097     $Attr->{padding_right} = $Prop->{'padding-right'};
3098     $Key->{padding_right} = $Prop->{'padding-right'};
3099    
3100     $Prop->{'padding-left'} = {
3101     css => 'padding-left',
3102     dom => 'padding_left',
3103     key => 'padding_left',
3104     parse => $Prop->{'padding-top'}->{parse},
3105 wakaba 1.22 #allow_negative => 0,
3106     #keyword => {},
3107 wakaba 1.19 serialize => $default_serializer,
3108 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3109 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3110     #inherited => 0,
3111     compute => $compute_length,
3112     };
3113     $Attr->{padding_left} = $Prop->{'padding-left'};
3114     $Key->{padding_left} = $Prop->{'padding-left'};
3115    
3116 wakaba 1.20 $Prop->{'border-top-width'} = {
3117     css => 'border-top-width',
3118     dom => 'border_top_width',
3119     key => 'border_top_width',
3120 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
3121     #allow_negative => 0,
3122     keyword => {thin => 1, medium => 1, thick => 1},
3123 wakaba 1.20 serialize => $default_serializer,
3124 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3125 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3126     #inherited => 0,
3127     compute => sub {
3128     my ($self, $element, $prop_name, $specified_value) = @_;
3129    
3130 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
3131     ## 'border-bottom-width', 'border-right-width', and
3132     ## 'outline-width'.
3133    
3134 wakaba 1.20 my $style_prop = $prop_name;
3135     $style_prop =~ s/width/style/;
3136     my $style = $self->get_computed_value ($element, $style_prop);
3137     if (defined $style and $style->[0] eq 'KEYWORD' and
3138     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
3139     return ['DIMENSION', 0, 'px'];
3140     }
3141    
3142     my $value = $compute_length->(@_);
3143     if (defined $value and $value->[0] eq 'KEYWORD') {
3144     if ($value->[1] eq 'thin') {
3145     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
3146     } elsif ($value->[1] eq 'medium') {
3147     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
3148     } elsif ($value->[1] eq 'thick') {
3149     return ['DIMENSION', 5, 'px']; ## Firefox
3150     }
3151     }
3152     return $value;
3153     },
3154 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
3155     ## Opera 9 has already implemented it.
3156 wakaba 1.20 };
3157     $Attr->{border_top_width} = $Prop->{'border-top-width'};
3158     $Key->{border_top_width} = $Prop->{'border-top-width'};
3159    
3160     $Prop->{'border-right-width'} = {
3161     css => 'border-right-width',
3162     dom => 'border_right_width',
3163     key => 'border_right_width',
3164     parse => $Prop->{'border-top-width'}->{parse},
3165 wakaba 1.22 #allow_negative => 0,
3166     keyword => {thin => 1, medium => 1, thick => 1},
3167 wakaba 1.20 serialize => $default_serializer,
3168 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3169 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3170     #inherited => 0,
3171     compute => $Prop->{'border-top-width'}->{compute},
3172     };
3173     $Attr->{border_right_width} = $Prop->{'border-right-width'};
3174     $Key->{border_right_width} = $Prop->{'border-right-width'};
3175    
3176     $Prop->{'border-bottom-width'} = {
3177     css => 'border-bottom-width',
3178     dom => 'border_bottom_width',
3179     key => 'border_bottom_width',
3180     parse => $Prop->{'border-top-width'}->{parse},
3181 wakaba 1.22 #allow_negative => 0,
3182     keyword => {thin => 1, medium => 1, thick => 1},
3183 wakaba 1.20 serialize => $default_serializer,
3184 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3185 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3186     #inherited => 0,
3187     compute => $Prop->{'border-top-width'}->{compute},
3188     };
3189     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
3190     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
3191    
3192     $Prop->{'border-left-width'} = {
3193     css => 'border-left-width',
3194     dom => 'border_left_width',
3195     key => 'border_left_width',
3196     parse => $Prop->{'border-top-width'}->{parse},
3197 wakaba 1.22 #allow_negative => 0,
3198     keyword => {thin => 1, medium => 1, thick => 1},
3199 wakaba 1.20 serialize => $default_serializer,
3200 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3201 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3202     #inherited => 0,
3203     compute => $Prop->{'border-top-width'}->{compute},
3204     };
3205     $Attr->{border_left_width} = $Prop->{'border-left-width'};
3206     $Key->{border_left_width} = $Prop->{'border-left-width'};
3207    
3208 wakaba 1.23 $Prop->{'outline-width'} = {
3209     css => 'outline-width',
3210     dom => 'outline_width',
3211     key => 'outline_width',
3212     parse => $Prop->{'border-top-width'}->{parse},
3213     #allow_negative => 0,
3214     keyword => {thin => 1, medium => 1, thick => 1},
3215     serialize => $default_serializer,
3216 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3217 wakaba 1.23 initial => ['KEYWORD', 'medium'],
3218     #inherited => 0,
3219     compute => $Prop->{'border-top-width'}->{compute},
3220     };
3221     $Attr->{outline_width} = $Prop->{'outline-width'};
3222     $Key->{outline_width} = $Prop->{'outline-width'};
3223    
3224 wakaba 1.15 $Prop->{'font-weight'} = {
3225     css => 'font-weight',
3226     dom => 'font_weight',
3227     key => 'font_weight',
3228     parse => sub {
3229     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3230    
3231 wakaba 1.50 my $has_sign;
3232     if ($t->{type} == PLUS_TOKEN) {
3233     $has_sign = 1;
3234     $t = $tt->get_next_token;
3235     }
3236    
3237 wakaba 1.15 if ($t->{type} == NUMBER_TOKEN) {
3238     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
3239     ## browser compatibility issue.
3240     my $value = $t->{number};
3241     $t = $tt->get_next_token;
3242     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
3243     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
3244     }
3245 wakaba 1.50 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
3246 wakaba 1.15 my $value = lc $t->{value}; ## TODO: case
3247     if ({
3248     normal => 1, bold => 1, bolder => 1, lighter => 1,
3249     }->{$value}) {
3250 wakaba 1.50 $t = $tt->get_next_token;
3251 wakaba 1.15 return ($t, {$prop_name => ['KEYWORD', $value]});
3252     } elsif ($value eq 'inherit') {
3253 wakaba 1.50 $t = $tt->get_next_token;
3254 wakaba 1.15 return ($t, {$prop_name => ['INHERIT']});
3255     }
3256     }
3257    
3258 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3259 wakaba 1.15 level => $self->{must_level},
3260 wakaba 1.38 uri => \$self->{href},
3261 wakaba 1.15 token => $t);
3262     return ($t, undef);
3263     },
3264     serialize => $default_serializer,
3265     initial => ['KEYWORD', 'normal'],
3266     inherited => 1,
3267     compute => sub {
3268     my ($self, $element, $prop_name, $specified_value) = @_;
3269    
3270 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3271 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
3272     return ['WEIGHT', 400, 0];
3273     } elsif ($specified_value->[1] eq 'bold') {
3274     return ['WEIGHT', 700, 0];
3275     } elsif ($specified_value->[1] eq 'bolder') {
3276     my $parent_element = $element->manakai_parent_element;
3277     if (defined $parent_element) {
3278     my $parent_value = $self->get_cascaded_value
3279     ($parent_element, $prop_name); ## NOTE: What Firefox does.
3280     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
3281     } else {
3282     return ['WEIGHT', 400, 1];
3283     }
3284     } elsif ($specified_value->[1] eq 'lighter') {
3285     my $parent_element = $element->manakai_parent_element;
3286     if (defined $parent_element) {
3287     my $parent_value = $self->get_cascaded_value
3288     ($parent_element, $prop_name); ## NOTE: What Firefox does.
3289     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
3290     } else {
3291     return ['WEIGHT', 400, 1];
3292     }
3293     }
3294 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
3295 wakaba 1.15 #
3296     }
3297    
3298     return $specified_value;
3299     },
3300     };
3301     $Attr->{font_weight} = $Prop->{'font-weight'};
3302     $Key->{font_weight} = $Prop->{'font-weight'};
3303    
3304 wakaba 1.13 my $uri_or_none_parser = sub {
3305 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3306    
3307 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
3308 wakaba 1.11 my $value = $t->{value};
3309     $t = $tt->get_next_token;
3310     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3311     } elsif ($t->{type} == IDENT_TOKEN) {
3312     my $value = lc $t->{value}; ## TODO: case
3313     $t = $tt->get_next_token;
3314     if ($value eq 'none') {
3315     ## NOTE: |none| is the default value and therefore it must be
3316     ## supported anyway.
3317     return ($t, {$prop_name => ["KEYWORD", 'none']});
3318     } elsif ($value eq 'inherit') {
3319     return ($t, {$prop_name => ['INHERIT']});
3320     }
3321     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
3322     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
3323     # my $value = $t->{value};
3324     # $t = $tt->get_next_token;
3325     # if ($t->{type} == EOF_TOKEN) {
3326 wakaba 1.39 # $onerror->(type => 'uri not closed',
3327 wakaba 1.11 # level => $self->{must_level},
3328 wakaba 1.38 # uri => \$self->{href},
3329 wakaba 1.11 # token => $t);
3330     #
3331     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3332     # }
3333     }
3334    
3335 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3336 wakaba 1.11 level => $self->{must_level},
3337 wakaba 1.38 uri => \$self->{href},
3338 wakaba 1.11 token => $t);
3339     return ($t, undef);
3340 wakaba 1.13 }; # $uri_or_none_parser
3341    
3342 wakaba 1.14 my $compute_uri_or_none = sub {
3343 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
3344    
3345     if (defined $specified_value and
3346     $specified_value->[0] eq 'URI' and
3347     defined $specified_value->[2]) {
3348     require Message::DOM::DOMImplementation;
3349     return ['URI',
3350     Message::DOM::DOMImplementation->create_uri_reference
3351     ($specified_value->[1])
3352     ->get_absolute_reference (${$specified_value->[2]})
3353     ->get_uri_reference,
3354     $specified_value->[2]];
3355     }
3356    
3357     return $specified_value;
3358 wakaba 1.14 }; # $compute_uri_or_none
3359    
3360     $Prop->{'list-style-image'} = {
3361     css => 'list-style-image',
3362     dom => 'list_style_image',
3363     key => 'list_style_image',
3364     parse => $uri_or_none_parser,
3365     serialize => $default_serializer,
3366     initial => ['KEYWORD', 'none'],
3367     inherited => 1,
3368     compute => $compute_uri_or_none,
3369 wakaba 1.11 };
3370     $Attr->{list_style_image} = $Prop->{'list-style-image'};
3371     $Key->{list_style_image} = $Prop->{'list-style-image'};
3372    
3373 wakaba 1.15 $Prop->{'background-image'} = {
3374     css => 'background-image',
3375     dom => 'background_image',
3376     key => 'background_image',
3377     parse => $uri_or_none_parser,
3378     serialize => $default_serializer,
3379 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3380 wakaba 1.15 initial => ['KEYWORD', 'none'],
3381     #inherited => 0,
3382     compute => $compute_uri_or_none,
3383     };
3384     $Attr->{background_image} = $Prop->{'background-image'};
3385     $Key->{background_image} = $Prop->{'background-image'};
3386    
3387 wakaba 1.7 my $border_style_keyword = {
3388     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
3389     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
3390     };
3391    
3392     $Prop->{'border-top-style'} = {
3393     css => 'border-top-style',
3394     dom => 'border_top_style',
3395     key => 'border_top_style',
3396     parse => $one_keyword_parser,
3397 wakaba 1.11 serialize => $default_serializer,
3398 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3399 wakaba 1.7 keyword => $border_style_keyword,
3400 wakaba 1.9 initial => ["KEYWORD", "none"],
3401     #inherited => 0,
3402     compute => $compute_as_specified,
3403 wakaba 1.7 };
3404     $Attr->{border_top_style} = $Prop->{'border-top-style'};
3405     $Key->{border_top_style} = $Prop->{'border-top-style'};
3406    
3407     $Prop->{'border-right-style'} = {
3408     css => 'border-right-style',
3409     dom => 'border_right_style',
3410     key => 'border_right_style',
3411     parse => $one_keyword_parser,
3412 wakaba 1.11 serialize => $default_serializer,
3413 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3414 wakaba 1.7 keyword => $border_style_keyword,
3415 wakaba 1.9 initial => ["KEYWORD", "none"],
3416     #inherited => 0,
3417     compute => $compute_as_specified,
3418 wakaba 1.7 };
3419     $Attr->{border_right_style} = $Prop->{'border-right-style'};
3420     $Key->{border_right_style} = $Prop->{'border-right-style'};
3421    
3422     $Prop->{'border-bottom-style'} = {
3423     css => 'border-bottom-style',
3424     dom => 'border_bottom_style',
3425     key => 'border_bottom_style',
3426     parse => $one_keyword_parser,
3427 wakaba 1.11 serialize => $default_serializer,
3428 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3429 wakaba 1.7 keyword => $border_style_keyword,
3430 wakaba 1.9 initial => ["KEYWORD", "none"],
3431     #inherited => 0,
3432     compute => $compute_as_specified,
3433 wakaba 1.7 };
3434     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
3435     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
3436    
3437     $Prop->{'border-left-style'} = {
3438     css => 'border-left-style',
3439     dom => 'border_left_style',
3440     key => 'border_left_style',
3441     parse => $one_keyword_parser,
3442 wakaba 1.11 serialize => $default_serializer,
3443 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3444 wakaba 1.7 keyword => $border_style_keyword,
3445 wakaba 1.9 initial => ["KEYWORD", "none"],
3446     #inherited => 0,
3447     compute => $compute_as_specified,
3448 wakaba 1.7 };
3449     $Attr->{border_left_style} = $Prop->{'border-left-style'};
3450     $Key->{border_left_style} = $Prop->{'border-left-style'};
3451    
3452 wakaba 1.16 $Prop->{'outline-style'} = {
3453     css => 'outline-style',
3454     dom => 'outline_style',
3455     key => 'outline_style',
3456     parse => $one_keyword_parser,
3457     serialize => $default_serializer,
3458 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3459 wakaba 1.23 keyword => {%$border_style_keyword},
3460 wakaba 1.16 initial => ['KEYWORD', 'none'],
3461     #inherited => 0,
3462     compute => $compute_as_specified,
3463     };
3464     $Attr->{outline_style} = $Prop->{'outline-style'};
3465     $Key->{outline_style} = $Prop->{'outline-style'};
3466 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
3467 wakaba 1.16
3468 wakaba 1.44 my $generic_font_keywords = {
3469 wakaba 1.31 serif => 1, 'sans-serif' => 1, cursive => 1,
3470     fantasy => 1, monospace => 1, '-manakai-default' => 1,
3471     '-manakai-caption' => 1, '-manakai-icon' => 1,
3472     '-manakai-menu' => 1, '-manakai-message-box' => 1,
3473     '-manakai-small-caption' => 1, '-manakai-status-bar' => 1,
3474     };
3475     ## NOTE: "All five generic font families are defined to exist in all CSS
3476     ## implementations (they need not necessarily map to five distinct actual
3477     ## fonts)." [CSS 2.1].
3478     ## NOTE: "If no font with the indicated characteristics exists on a given
3479     ## platform, the user agent should either intelligently substitute (e.g., a
3480     ## smaller version of the 'caption' font might be used for the 'small-caption'
3481     ## font), or substitute a user agent default font." [CSS 2.1].
3482    
3483 wakaba 1.15 $Prop->{'font-family'} = {
3484     css => 'font-family',
3485     dom => 'font_family',
3486     key => 'font_family',
3487     parse => sub {
3488     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3489    
3490     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
3491     ## how chaotic browsers are!
3492    
3493 wakaba 1.53 ## NOTE: Opera 9 allows NUMBER and DIMENSION as part of
3494     ## <font-family>, while Firefox 2 does not.
3495    
3496 wakaba 1.15 my @prop_value;
3497    
3498     my $font_name = '';
3499     my $may_be_generic = 1;
3500 wakaba 1.31 my $may_be_inherit = ($prop_name ne 'font');
3501 wakaba 1.15 my $has_s = 0;
3502     F: {
3503     if ($t->{type} == IDENT_TOKEN) {
3504     undef $may_be_inherit if $has_s or length $font_name;
3505     undef $may_be_generic if $has_s or length $font_name;
3506     $font_name .= ' ' if $has_s;
3507     $font_name .= $t->{value};
3508     undef $has_s;
3509     $t = $tt->get_next_token;
3510     } elsif ($t->{type} == STRING_TOKEN) {
3511     $font_name .= ' ' if $has_s;
3512     $font_name .= $t->{value};
3513     undef $may_be_inherit;
3514     undef $may_be_generic;
3515     undef $has_s;
3516     $t = $tt->get_next_token;
3517 wakaba 1.31 } elsif ($t->{type} == COMMA_TOKEN) { ## TODO: case
3518     if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3519 wakaba 1.15 push @prop_value, ['KEYWORD', $font_name];
3520     } elsif (not $may_be_generic or length $font_name) {
3521     push @prop_value, ["STRING", $font_name];
3522     }
3523     undef $may_be_inherit;
3524     $may_be_generic = 1;
3525     undef $has_s;
3526     $font_name = '';
3527     $t = $tt->get_next_token;
3528     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3529     } elsif ($t->{type} == S_TOKEN) {
3530     $has_s = 1;
3531     $t = $tt->get_next_token;
3532     } else {
3533 wakaba 1.31 if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3534     push @prop_value, ['KEYWORD', $font_name]; ## TODO: case
3535 wakaba 1.15 } elsif (not $may_be_generic or length $font_name) {
3536     push @prop_value, ['STRING', $font_name];
3537     } else {
3538 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3539 wakaba 1.15 level => $self->{must_level},
3540 wakaba 1.38 uri => \$self->{href},
3541 wakaba 1.15 token => $t);
3542     return ($t, undef);
3543     }
3544     last F;
3545     }
3546     redo F;
3547     } # F
3548    
3549     if ($may_be_inherit and
3550     @prop_value == 1 and
3551     $prop_value[0]->[0] eq 'STRING' and
3552     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
3553     return ($t, {$prop_name => ['INHERIT']});
3554     } else {
3555     unshift @prop_value, 'FONT';
3556     return ($t, {$prop_name => \@prop_value});
3557     }
3558     },
3559     serialize => sub {
3560     my ($self, $prop_name, $value) = @_;
3561    
3562     if ($value->[0] eq 'FONT') {
3563     return join ', ', map {
3564     if ($_->[0] eq 'STRING') {
3565     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
3566     } elsif ($_->[0] eq 'KEYWORD') {
3567     $_->[1]; ## NOTE: This is what Firefox does.
3568     } else {
3569     ## NOTE: This should be an error.
3570     '""';
3571     }
3572     } @$value[1..$#$value];
3573     } elsif ($value->[0] eq 'INHERIT') {
3574     return 'inherit';
3575     } else {
3576 wakaba 1.34 return '';
3577 wakaba 1.15 }
3578     },
3579     initial => ['FONT', ['KEYWORD', '-manakai-default']],
3580     inherited => 1,
3581     compute => $compute_as_specified,
3582     };
3583     $Attr->{font_family} = $Prop->{'font-family'};
3584     $Key->{font_family} = $Prop->{'font-family'};
3585    
3586 wakaba 1.17 $Prop->{cursor} = {
3587     css => 'cursor',
3588     dom => 'cursor',
3589     key => 'cursor',
3590     parse => sub {
3591     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3592    
3593     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
3594     ## compatibility issues.
3595    
3596     my @prop_value = ('CURSOR');
3597    
3598     F: {
3599     if ($t->{type} == IDENT_TOKEN) {
3600     my $v = lc $t->{value}; ## TODO: case
3601     $t = $tt->get_next_token;
3602     if ($Prop->{$prop_name}->{keyword}->{$v}) {
3603     push @prop_value, ['KEYWORD', $v];
3604     last F;
3605     } elsif ($v eq 'inherit' and @prop_value == 1) {
3606     return ($t, {$prop_name => ['INHERIT']});
3607     } else {
3608 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3609 wakaba 1.17 level => $self->{must_level},
3610 wakaba 1.38 uri => \$self->{href},
3611 wakaba 1.17 token => $t);
3612     return ($t, undef);
3613     }
3614     } elsif ($t->{type} == URI_TOKEN) {
3615     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
3616     $t = $tt->get_next_token;
3617     } else {
3618 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3619 wakaba 1.17 level => $self->{must_level},
3620 wakaba 1.38 uri => \$self->{href},
3621 wakaba 1.17 token => $t);
3622     return ($t, undef);
3623     }
3624    
3625     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3626     if ($t->{type} == COMMA_TOKEN) {
3627     $t = $tt->get_next_token;
3628     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3629     redo F;
3630     }
3631     } # F
3632    
3633     return ($t, {$prop_name => \@prop_value});
3634     },
3635     serialize => sub {
3636     my ($self, $prop_name, $value) = @_;
3637    
3638     if ($value->[0] eq 'CURSOR') {
3639     return join ', ', map {
3640     if ($_->[0] eq 'URI') {
3641     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
3642     } elsif ($_->[0] eq 'KEYWORD') {
3643     $_->[1];
3644     } else {
3645     ## NOTE: This should be an error.
3646     '""';
3647     }
3648     } @$value[1..$#$value];
3649     } elsif ($value->[0] eq 'INHERIT') {
3650     return 'inherit';
3651     } else {
3652 wakaba 1.34 return '';
3653 wakaba 1.17 }
3654     },
3655     keyword => {
3656     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
3657     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
3658     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
3659     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
3660     },
3661     initial => ['CURSOR', ['KEYWORD', 'auto']],
3662     inherited => 1,
3663     compute => sub {
3664     my ($self, $element, $prop_name, $specified_value) = @_;
3665    
3666     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
3667     my @new_value = ('CURSOR');
3668     for my $value (@$specified_value[1..$#$specified_value]) {
3669     if ($value->[0] eq 'URI') {
3670     if (defined $value->[2]) {
3671     require Message::DOM::DOMImplementation;
3672     push @new_value, ['URI',
3673     Message::DOM::DOMImplementation
3674     ->create_uri_reference ($value->[1])
3675     ->get_absolute_reference (${$value->[2]})
3676     ->get_uri_reference,
3677     $value->[2]];
3678     } else {
3679     push @new_value, $value;
3680     }
3681     } else {
3682     push @new_value, $value;
3683     }
3684     }
3685     return \@new_value;
3686     }
3687    
3688     return $specified_value;
3689     },
3690     };
3691     $Attr->{cursor} = $Prop->{cursor};
3692     $Key->{cursor} = $Prop->{cursor};
3693    
3694 wakaba 1.7 $Prop->{'border-style'} = {
3695     css => 'border-style',
3696     dom => 'border_style',
3697     parse => sub {
3698     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3699    
3700     my %prop_value;
3701     if ($t->{type} == IDENT_TOKEN) {
3702     my $prop_value = lc $t->{value}; ## TODO: case folding
3703     $t = $tt->get_next_token;
3704     if ($border_style_keyword->{$prop_value} and
3705     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
3706     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
3707     } elsif ($prop_value eq 'inherit') {
3708 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
3709 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3710     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3711     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3712     return ($t, \%prop_value);
3713 wakaba 1.7 } else {
3714 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3715 wakaba 1.7 level => $self->{must_level},
3716 wakaba 1.38 uri => \$self->{href},
3717 wakaba 1.7 token => $t);
3718     return ($t, undef);
3719     }
3720     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3721     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3722     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3723     } else {
3724 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3725 wakaba 1.7 level => $self->{must_level},
3726 wakaba 1.38 uri => \$self->{href},
3727 wakaba 1.7 token => $t);
3728     return ($t, undef);
3729     }
3730    
3731     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3732     if ($t->{type} == IDENT_TOKEN) {
3733     my $prop_value = lc $t->{value}; ## TODO: case folding
3734     $t = $tt->get_next_token;
3735 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
3736 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
3737     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
3738     } else {
3739 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3740 wakaba 1.7 level => $self->{must_level},
3741 wakaba 1.38 uri => \$self->{href},
3742 wakaba 1.7 token => $t);
3743     return ($t, undef);
3744     }
3745     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3746    
3747     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3748     if ($t->{type} == IDENT_TOKEN) {
3749     my $prop_value = lc $t->{value}; ## TODO: case folding
3750     $t = $tt->get_next_token;
3751     if ($border_style_keyword->{$prop_value} and
3752     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
3753     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
3754     } else {
3755 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3756 wakaba 1.7 level => $self->{must_level},
3757 wakaba 1.38 uri => \$self->{href},
3758 wakaba 1.7 token => $t);
3759     return ($t, undef);
3760     }
3761    
3762     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3763     if ($t->{type} == IDENT_TOKEN) {
3764     my $prop_value = lc $t->{value}; ## TODO: case folding
3765     $t = $tt->get_next_token;
3766     if ($border_style_keyword->{$prop_value} and
3767     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
3768     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
3769     } else {
3770 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3771 wakaba 1.7 level => $self->{must_level},
3772 wakaba 1.38 uri => \$self->{href},
3773 wakaba 1.7 token => $t);
3774     return ($t, undef);
3775     }
3776     }
3777     }
3778     }
3779    
3780     return ($t, \%prop_value);
3781     },
3782 wakaba 1.43 serialize_shorthand => sub {
3783     my $self = shift;
3784    
3785 wakaba 1.7 my @v;
3786     push @v, $self->border_top_style;
3787 wakaba 1.43 my $i = $self->get_property_priority ('border-top-style');
3788 wakaba 1.45 return {} unless length $v[-1];
3789 wakaba 1.7 push @v, $self->border_right_style;
3790 wakaba 1.45 return {} unless length $v[-1];
3791     return {} unless $i eq $self->get_property_priority ('border-right-style');
3792 wakaba 1.7 push @v, $self->border_bottom_style;
3793 wakaba 1.45 return {} unless length $v[-1];
3794     return {} unless $i eq $self->get_property_priority ('border-bottom-style');
3795 wakaba 1.19 push @v, $self->border_left_style;
3796 wakaba 1.45 return {} unless length $v[-1];
3797     return {} unless $i eq $self->get_property_priority ('border-left-style');
3798 wakaba 1.43
3799     my $v = 0;
3800     for (0..3) {
3801     $v++ if $v[$_] eq 'inherit';
3802     }
3803     if ($v == 4) {
3804 wakaba 1.45 return {'border-style' => ['inherit', $i]};
3805 wakaba 1.43 } elsif ($v) {
3806     return {};
3807     }
3808 wakaba 1.7
3809     pop @v if $v[1] eq $v[3];
3810     pop @v if $v[0] eq $v[2];
3811     pop @v if $v[0] eq $v[1];
3812 wakaba 1.45 return {'border-style' => [(join ' ', @v), $i]};
3813 wakaba 1.7 },
3814 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3815 wakaba 1.7 };
3816     $Attr->{border_style} = $Prop->{'border-style'};
3817    
3818 wakaba 1.29 $Prop->{'border-color'} = {
3819     css => 'border-color',
3820     dom => 'border_color',
3821     parse => sub {
3822     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3823    
3824     my %prop_value;
3825     ($t, my $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3826     if (not defined $pv) {
3827     return ($t, undef);
3828     }
3829     $prop_value{'border-top-color'} = $pv->{'border-color'};
3830     $prop_value{'border-bottom-color'} = $prop_value{'border-top-color'};
3831     $prop_value{'border-right-color'} = $prop_value{'border-top-color'};
3832     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
3833     if ($prop_value{'border-top-color'}->[0] eq 'INHERIT') {
3834     return ($t, \%prop_value);
3835     }
3836    
3837     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3838     if ({
3839     IDENT_TOKEN, 1,
3840     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3841     FUNCTION_TOKEN, 1,
3842     }->{$t->{type}}) {
3843     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3844     if (not defined $pv) {
3845     return ($t, undef);
3846     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3847 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3848 wakaba 1.29 level => $self->{must_level},
3849 wakaba 1.38 uri => \$self->{href},
3850 wakaba 1.29 token => $t);
3851     return ($t, undef);
3852     }
3853     $prop_value{'border-right-color'} = $pv->{'border-color'};
3854     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
3855    
3856     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3857     if ({
3858     IDENT_TOKEN, 1,
3859     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3860     FUNCTION_TOKEN, 1,
3861     }->{$t->{type}}) {
3862     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3863     if (not defined $pv) {
3864     return ($t, undef);
3865     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3866 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3867 wakaba 1.29 level => $self->{must_level},
3868 wakaba 1.38 uri => \$self->{href},
3869 wakaba 1.29 token => $t);
3870     return ($t, undef);
3871     }
3872     $prop_value{'border-bottom-color'} = $pv->{'border-color'};
3873    
3874     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3875     if ({
3876     IDENT_TOKEN, 1,
3877     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3878     FUNCTION_TOKEN, 1,
3879     }->{$t->{type}}) {
3880     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3881     if (not defined $pv) {
3882     return ($t, undef);
3883     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3884 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3885 wakaba 1.29 level => $self->{must_level},
3886 wakaba 1.38 uri => \$self->{href},
3887 wakaba 1.29 token => $t);
3888     return ($t, undef);
3889     }
3890     $prop_value{'border-left-color'} = $pv->{'border-color'};
3891     }
3892     }
3893     }
3894    
3895     return ($t, \%prop_value);
3896     },
3897 wakaba 1.43 serialize_shorthand => sub {
3898     my $self = shift;
3899    
3900 wakaba 1.29 my @v;
3901     push @v, $self->border_top_color;
3902 wakaba 1.43 my $i = $self->get_property_priority ('border-top-color');
3903 wakaba 1.45 return {} unless length $v[-1];
3904 wakaba 1.29 push @v, $self->border_right_color;
3905 wakaba 1.45 return {} unless length $v[-1];
3906     return {} unless $i eq $self->get_property_priority ('border-right-color');
3907 wakaba 1.29 push @v, $self->border_bottom_color;
3908 wakaba 1.45 return {} unless length $v[-1];
3909     return {} unless $i eq $self->get_property_priority ('border-bottom-color');
3910 wakaba 1.29 push @v, $self->border_left_color;
3911 wakaba 1.45 return {} unless length $v[-1];
3912     return {} unless $i eq $self->get_property_priority ('border-left-color');
3913 wakaba 1.43
3914     my $v = 0;
3915     for (0..3) {
3916     $v++ if $v[$_] eq 'inherit';
3917     }
3918     if ($v == 4) {
3919 wakaba 1.45 return {'border-color' => ['inherit', $i]};
3920 wakaba 1.43 } elsif ($v) {
3921     return {};
3922     }
3923 wakaba 1.29
3924     pop @v if $v[1] eq $v[3];
3925     pop @v if $v[0] eq $v[2];
3926     pop @v if $v[0] eq $v[1];
3927 wakaba 1.45 return {'border-color' => [(join ' ', @v), $i]};
3928 wakaba 1.29 },
3929     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3930     };
3931     $Attr->{border_color} = $Prop->{'border-color'};
3932    
3933     $Prop->{'border-top'} = {
3934     css => 'border-top',
3935     dom => 'border_top',
3936     parse => sub {
3937     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3938    
3939     my %prop_value;
3940     my $pv;
3941     ## NOTE: Since $onerror is disabled for three invocations below,
3942     ## some informative warning messages (if they are added someday) will not
3943     ## be reported.
3944     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, sub {});
3945     if (defined $pv) {
3946     if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
3947     return ($t, {$prop_name.'-color' => ['INHERIT'],
3948     $prop_name.'-style' => ['INHERIT'],
3949     $prop_name.'-width' => ['INHERIT']});
3950     } else {
3951     $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
3952     }
3953     } else {
3954     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
3955     ->($self, $prop_name.'-width', $tt, $t, sub {});
3956     if (defined $pv) {
3957     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
3958     } else {
3959     ($t, $pv) = $Prop->{'border-top-style'}->{parse}
3960     ->($self, $prop_name.'-style', $tt, $t, sub {});
3961     if (defined $pv) {
3962     $prop_value{$prop_name.'-style'} = $pv->{$prop_name.'-style'};
3963     } else {
3964 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
3965 wakaba 1.29 level => $self->{must_level},
3966 wakaba 1.38 uri => \$self->{href},
3967 wakaba 1.29 token => $t);
3968     return ($t, undef);
3969     }
3970     }
3971     }
3972    
3973     for (1..2) {
3974     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3975     if ($t->{type} == IDENT_TOKEN) {
3976     my $prop_value = lc $t->{value}; ## TODO: case
3977     if ($border_style_keyword->{$prop_value} and
3978     $self->{prop_value}->{'border-top-style'}->{$prop_value} and
3979     not defined $prop_value{$prop_name.'-style'}) {
3980     $prop_value{$prop_name.'-style'} = ['KEYWORD', $prop_value];
3981     $t = $tt->get_next_token;
3982     next;
3983     } elsif ({thin => 1, medium => 1, thick => 1}->{$prop_value} and
3984     not defined $prop_value{$prop_name.'-width'}) {
3985     $prop_value{$prop_name.'-width'} = ['KEYWORD', $prop_value];
3986     $t = $tt->get_next_token;
3987     next;
3988     }
3989     }
3990    
3991     undef $pv;
3992     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, $onerror)
3993     if not defined $prop_value{$prop_name.'-color'} and
3994     {
3995     IDENT_TOKEN, 1,
3996     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3997     FUNCTION_TOKEN, 1,
3998     }->{$t->{type}};
3999     if (defined $pv) {
4000     if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4001 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4002 wakaba 1.29 level => $self->{must_level},
4003 wakaba 1.38 uri => \$self->{href},
4004 wakaba 1.29 token => $t);
4005     } else {
4006     $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4007     }
4008     } else {
4009     undef $pv;
4010     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4011     ->($self, $prop_name.'-width',
4012     $tt, $t, $onerror)
4013     if not defined $prop_value{$prop_name.'-width'} and
4014     {
4015     DIMENSION_TOKEN, 1,
4016     NUMBER_TOKEN, 1,
4017     IDENT_TOKEN, 1,
4018     MINUS_TOKEN, 1,
4019     }->{$t->{type}};
4020     if (defined $pv) {
4021     if ($pv->{$prop_name.'-width'}->[0] eq 'INHERIT') {
4022 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4023 wakaba 1.29 level => $self->{must_level},
4024 wakaba 1.38 uri => \$self->{href},
4025 wakaba 1.29 token => $t);
4026     } else {
4027     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4028     }
4029     } else {
4030     last;
4031     }
4032     }
4033     }
4034    
4035     $prop_value{$prop_name.'-color'}
4036     ||= $Prop->{$prop_name.'-color'}->{initial};
4037     $prop_value{$prop_name.'-width'}
4038     ||= $Prop->{$prop_name.'-width'}->{initial};
4039     $prop_value{$prop_name.'-style'}
4040     ||= $Prop->{$prop_name.'-style'}->{initial};
4041    
4042     return ($t, \%prop_value);
4043     },
4044 wakaba 1.43 serialize_shorthand => sub {
4045     my $self = shift;
4046    
4047     my $w = $self->border_top_width;
4048     return {} unless length $w;
4049     my $i = $self->get_property_priority ('border-top-width');
4050     my $s = $self->border_top_style;
4051     return {} unless length $s;
4052     return {} unless $i eq $self->get_property_priority ('border-top-style');
4053     my $c = $self->border_top_color;
4054     return {} unless length $c;
4055     return {} unless $i eq $self->get_property_priority ('border-top-color');
4056    
4057     my $v = 0;
4058     $v++ if $w eq 'inherit';
4059     $v++ if $s eq 'inherit';
4060     $v++ if $c eq 'inherit';
4061     if ($v == 3) {
4062     return {'border-top' => ['inherit', $i]};
4063     } elsif ($v) {
4064     return {};
4065     }
4066 wakaba 1.29
4067 wakaba 1.43 return {'border-top' => [$w . ' ' . $s . ' ' . $c, $i]};
4068 wakaba 1.29 },
4069     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4070     };
4071     $Attr->{border_top} = $Prop->{'border-top'};
4072    
4073     $Prop->{'border-right'} = {
4074     css => 'border-right',
4075     dom => 'border_right',
4076     parse => $Prop->{'border-top'}->{parse},
4077 wakaba 1.43 serialize_shorthand => sub {
4078     my $self = shift;
4079    
4080     my $w = $self->border_right_width;
4081     return {} unless length $w;
4082     my $i = $self->get_property_priority ('border-right-width');
4083     my $s = $self->border_right_style;
4084     return {} unless length $s;
4085     return {} unless $i eq $self->get_property_priority ('border-right-style');
4086     my $c = $self->border_right_color;
4087     return {} unless length $c;
4088     return {} unless $i eq $self->get_property_priority ('border-right-color');
4089    
4090     my $v = 0;
4091     $v++ if $w eq 'inherit';
4092     $v++ if $s eq 'inherit';
4093     $v++ if $c eq 'inherit';
4094     if ($v == 3) {
4095     return {'border-right' => ['inherit', $i]};
4096     } elsif ($v) {
4097     return {};
4098     }
4099    
4100     return {'border-right' => [$w . ' ' . $s . ' ' . $c, $i]};
4101     },
4102 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4103     };
4104     $Attr->{border_right} = $Prop->{'border-right'};
4105    
4106     $Prop->{'border-bottom'} = {
4107     css => 'border-bottom',
4108     dom => 'border_bottom',
4109     parse => $Prop->{'border-top'}->{parse},
4110 wakaba 1.43 serialize_shorthand => sub {
4111     my $self = shift;
4112    
4113     my $w = $self->border_bottom_width;
4114     return {} unless length $w;
4115     my $i = $self->get_property_priority ('border-bottom-width');
4116     my $s = $self->border_bottom_style;
4117     return {} unless length $s;
4118     return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4119     my $c = $self->border_bottom_color;
4120     return {} unless length $c;
4121     return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4122    
4123     my $v = 0;
4124     $v++ if $w eq 'inherit';
4125     $v++ if $s eq 'inherit';
4126     $v++ if $c eq 'inherit';
4127     if ($v == 3) {
4128     return {'border-bottom' => ['inherit', $i]};
4129     } elsif ($v) {
4130     return {};
4131     }
4132    
4133     return {'border-bottom' => [$w . ' ' . $s . ' ' . $c, $i]};
4134     },
4135 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4136     };
4137     $Attr->{border_bottom} = $Prop->{'border-bottom'};
4138    
4139     $Prop->{'border-left'} = {
4140     css => 'border-left',
4141     dom => 'border_left',
4142     parse => $Prop->{'border-top'}->{parse},
4143 wakaba 1.43 serialize_shorthand => sub {
4144     my $self = shift;
4145    
4146     my $w = $self->border_left_width;
4147     return {} unless length $w;
4148     my $i = $self->get_property_priority ('border-left-width');
4149     my $s = $self->border_left_style;
4150     return {} unless length $s;
4151     return {} unless $i eq $self->get_property_priority ('border-left-style');
4152     my $c = $self->border_left_color;
4153     return {} unless length $c;
4154     return {} unless $i eq $self->get_property_priority ('border-left-color');
4155    
4156     my $v = 0;
4157     $v++ if $w eq 'inherit';
4158     $v++ if $s eq 'inherit';
4159     $v++ if $c eq 'inherit';
4160     if ($v == 3) {
4161     return {'border-left' => ['inherit', $i]};
4162     } elsif ($v) {
4163     return {};
4164     }
4165    
4166     return {'border-left' => [$w . ' ' . $s . ' ' . $c, $i]};
4167     },
4168 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4169     };
4170     $Attr->{border_left} = $Prop->{'border-left'};
4171    
4172     $Prop->{outline} = {
4173     css => 'outline',
4174     dom => 'outline',
4175     parse => $Prop->{'border-top'}->{parse},
4176     serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
4177     };
4178     $Attr->{outline} = $Prop->{outline};
4179    
4180     $Prop->{border} = {
4181     css => 'border',
4182     dom => 'border',
4183     parse => sub {
4184     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4185     my $prop_value;
4186     ($t, $prop_value) = $Prop->{'border-top'}->{parse}
4187     ->($self, 'border-top', $tt, $t, $onerror);
4188     return ($t, undef) unless defined $prop_value;
4189    
4190     for (qw/border-right border-bottom border-left/) {
4191     $prop_value->{$_.'-color'} = $prop_value->{'border-top-color'}
4192     if defined $prop_value->{'border-top-color'};
4193     $prop_value->{$_.'-style'} = $prop_value->{'border-top-style'}
4194     if defined $prop_value->{'border-top-style'};
4195     $prop_value->{$_.'-width'} = $prop_value->{'border-top-width'}
4196     if defined $prop_value->{'border-top-width'};
4197     }
4198     return ($t, $prop_value);
4199     },
4200     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4201     };
4202     $Attr->{border} = $Prop->{border};
4203    
4204 wakaba 1.19 $Prop->{margin} = {
4205     css => 'margin',
4206     dom => 'margin',
4207     parse => sub {
4208     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4209    
4210     my %prop_value;
4211    
4212     my $sign = 1;
4213 wakaba 1.42 my $has_sign;
4214 wakaba 1.19 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-top'} = ['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-top'} = ['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-top'} = ['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-top'} = ['KEYWORD', $prop_value];
4250     } elsif ($prop_value eq 'inherit') {
4251     $prop_value{'margin-top'} = ['INHERIT'];
4252     $prop_value{'margin-right'} = $prop_value{'margin-top'};
4253     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4254     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4255     return ($t, \%prop_value);
4256     } else {
4257 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4258 wakaba 1.19 level => $self->{must_level},
4259 wakaba 1.38 uri => \$self->{href},
4260 wakaba 1.19 token => $t);
4261     return ($t, undef);
4262     }
4263     } else {
4264 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4265 wakaba 1.19 level => $self->{must_level},
4266 wakaba 1.38 uri => \$self->{href},
4267 wakaba 1.19 token => $t);
4268     return ($t, undef);
4269     }
4270     $prop_value{'margin-right'} = $prop_value{'margin-top'};
4271     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4272     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4273    
4274     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4275 wakaba 1.42 undef $has_sign;
4276 wakaba 1.19 $sign = 1;
4277     if ($t->{type} == MINUS_TOKEN) {
4278     $t = $tt->get_next_token;
4279 wakaba 1.42 $has_sign = 1;
4280 wakaba 1.19 $sign = -1;
4281 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4282     $t = $tt->get_next_token;
4283     $has_sign = 1;
4284 wakaba 1.19 }
4285    
4286     if ($t->{type} == DIMENSION_TOKEN) {
4287     my $value = $t->{number} * $sign;
4288     my $unit = lc $t->{value}; ## TODO: case
4289     $t = $tt->get_next_token;
4290     if ($length_unit->{$unit}) {
4291     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
4292     } else {
4293 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4294 wakaba 1.19 level => $self->{must_level},
4295 wakaba 1.38 uri => \$self->{href},
4296 wakaba 1.19 token => $t);
4297     return ($t, undef);
4298     }
4299     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4300     my $value = $t->{number} * $sign;
4301     $t = $tt->get_next_token;
4302     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
4303 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4304 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4305     my $value = $t->{number} * $sign;
4306     $t = $tt->get_next_token;
4307     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
4308 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4309 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4310     $t = $tt->get_next_token;
4311     if ($prop_value eq 'auto') {
4312     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
4313     } else {
4314 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4315 wakaba 1.19 level => $self->{must_level},
4316 wakaba 1.38 uri => \$self->{href},
4317 wakaba 1.19 token => $t);
4318     return ($t, undef);
4319     }
4320     } else {
4321 wakaba 1.42 if ($has_sign) {
4322 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4323 wakaba 1.24 level => $self->{must_level},
4324 wakaba 1.38 uri => \$self->{href},
4325 wakaba 1.24 token => $t);
4326     return ($t, undef);
4327     }
4328 wakaba 1.19 return ($t, \%prop_value);
4329     }
4330     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4331    
4332     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4333 wakaba 1.42 undef $has_sign;
4334 wakaba 1.19 $sign = 1;
4335     if ($t->{type} == MINUS_TOKEN) {
4336     $t = $tt->get_next_token;
4337 wakaba 1.42 $has_sign = 1;
4338 wakaba 1.19 $sign = -1;
4339 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4340     $t = $tt->get_next_token;
4341     $has_sign = 1;
4342 wakaba 1.19 }
4343    
4344     if ($t->{type} == DIMENSION_TOKEN) {
4345     my $value = $t->{number} * $sign;
4346     my $unit = lc $t->{value}; ## TODO: case
4347     $t = $tt->get_next_token;
4348     if ($length_unit->{$unit}) {
4349     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
4350     } else {
4351 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4352 wakaba 1.19 level => $self->{must_level},
4353 wakaba 1.38 uri => \$self->{href},
4354 wakaba 1.19 token => $t);
4355     return ($t, undef);
4356     }
4357     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4358     my $value = $t->{number} * $sign;
4359     $t = $tt->get_next_token;
4360     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
4361 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4362 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4363     my $value = $t->{number} * $sign;
4364     $t = $tt->get_next_token;
4365     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
4366 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4367 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4368     $t = $tt->get_next_token;
4369     if ($prop_value eq 'auto') {
4370     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
4371     } else {
4372 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4373 wakaba 1.19 level => $self->{must_level},
4374 wakaba 1.38 uri => \$self->{href},
4375 wakaba 1.19 token => $t);
4376     return ($t, undef);
4377     }
4378     } else {
4379 wakaba 1.42 if ($has_sign) {
4380 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4381 wakaba 1.24 level => $self->{must_level},
4382 wakaba 1.38 uri => \$self->{href},
4383 wakaba 1.24 token => $t);
4384     return ($t, undef);
4385     }
4386 wakaba 1.19 return ($t, \%prop_value);
4387     }
4388    
4389     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4390 wakaba 1.42 undef $has_sign;
4391 wakaba 1.19 $sign = 1;
4392     if ($t->{type} == MINUS_TOKEN) {
4393     $t = $tt->get_next_token;
4394 wakaba 1.42 $has_sign = 1;
4395 wakaba 1.19 $sign = -1;
4396 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4397     $t = $tt->get_next_token;
4398     $has_sign = 1;
4399 wakaba 1.19 }
4400    
4401     if ($t->{type} == DIMENSION_TOKEN) {
4402     my $value = $t->{number} * $sign;
4403     my $unit = lc $t->{value}; ## TODO: case
4404     $t = $tt->get_next_token;
4405     if ($length_unit->{$unit}) {
4406     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
4407     } else {
4408 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4409 wakaba 1.19 level => $self->{must_level},
4410 wakaba 1.38 uri => \$self->{href},
4411 wakaba 1.19 token => $t);
4412     return ($t, undef);
4413     }
4414     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4415     my $value = $t->{number} * $sign;
4416     $t = $tt->get_next_token;
4417     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
4418 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4419 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4420     my $value = $t->{number} * $sign;
4421     $t = $tt->get_next_token;
4422     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
4423 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4424 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4425     $t = $tt->get_next_token;
4426     if ($prop_value eq 'auto') {
4427     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
4428     } else {
4429 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4430 wakaba 1.19 level => $self->{must_level},
4431 wakaba 1.38 uri => \$self->{href},
4432 wakaba 1.19 token => $t);
4433     return ($t, undef);
4434     }
4435     } else {
4436 wakaba 1.42 if ($has_sign) {
4437 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4438 wakaba 1.24 level => $self->{must_level},
4439 wakaba 1.38 uri => \$self->{href},
4440 wakaba 1.24 token => $t);
4441     return ($t, undef);
4442     }
4443 wakaba 1.19 return ($t, \%prop_value);
4444     }
4445    
4446     return ($t, \%prop_value);
4447     },
4448 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
4449 wakaba 1.19 };
4450     $Attr->{margin} = $Prop->{margin};
4451    
4452     $Prop->{padding} = {
4453     css => 'padding',
4454     dom => 'padding',
4455     parse => sub {
4456     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4457    
4458     my %prop_value;
4459    
4460     my $sign = 1;
4461     if ($t->{type} == MINUS_TOKEN) {
4462     $t = $tt->get_next_token;
4463     $sign = -1;
4464     }
4465    
4466     if ($t->{type} == DIMENSION_TOKEN) {
4467     my $value = $t->{number} * $sign;
4468     my $unit = lc $t->{value}; ## TODO: case
4469     $t = $tt->get_next_token;
4470     if ($length_unit->{$unit} and $value >= 0) {
4471     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
4472     } else {
4473 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4474 wakaba 1.19 level => $self->{must_level},
4475 wakaba 1.38 uri => \$self->{href},
4476 wakaba 1.19 token => $t);
4477     return ($t, undef);
4478     }
4479     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4480     my $value = $t->{number} * $sign;
4481     $t = $tt->get_next_token;
4482     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
4483     unless ($value >= 0) {
4484 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4485 wakaba 1.19 level => $self->{must_level},
4486 wakaba 1.38 uri => \$self->{href},
4487 wakaba 1.19 token => $t);
4488     return ($t, undef);
4489     }
4490 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4491 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4492     my $value = $t->{number} * $sign;
4493     $t = $tt->get_next_token;
4494     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
4495     unless ($value >= 0) {
4496 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4497 wakaba 1.19 level => $self->{must_level},
4498 wakaba 1.38 uri => \$self->{href},
4499 wakaba 1.19 token => $t);
4500     return ($t, undef);
4501     }
4502     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4503     my $prop_value = lc $t->{value}; ## TODO: case folding
4504     $t = $tt->get_next_token;
4505     if ($prop_value eq 'inherit') {
4506     $prop_value{'padding-top'} = ['INHERIT'];
4507     $prop_value{'padding-right'} = $prop_value{'padding-top'};
4508     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4509     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4510     return ($t, \%prop_value);
4511     } else {
4512 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4513 wakaba 1.19 level => $self->{must_level},
4514 wakaba 1.38 uri => \$self->{href},
4515 wakaba 1.19 token => $t);
4516     return ($t, undef);
4517     }
4518     } else {
4519 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4520 wakaba 1.19 level => $self->{must_level},
4521 wakaba 1.38 uri => \$self->{href},
4522 wakaba 1.19 token => $t);
4523     return ($t, undef);
4524     }
4525     $prop_value{'padding-right'} = $prop_value{'padding-top'};
4526     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4527     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4528    
4529     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4530     $sign = 1;
4531     if ($t->{type} == MINUS_TOKEN) {
4532     $t = $tt->get_next_token;
4533     $sign = -1;
4534     }
4535    
4536     if ($t->{type} == DIMENSION_TOKEN) {
4537     my $value = $t->{number} * $sign;
4538     my $unit = lc $t->{value}; ## TODO: case
4539     $t = $tt->get_next_token;
4540     if ($length_unit->{$unit} and $value >= 0) {
4541     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
4542     } else {
4543 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4544 wakaba 1.19 level => $self->{must_level},
4545 wakaba 1.38 uri => \$self->{href},
4546 wakaba 1.19 token => $t);
4547     return ($t, undef);
4548     }
4549     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4550     my $value = $t->{number} * $sign;
4551     $t = $tt->get_next_token;
4552     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
4553     unless ($value >= 0) {
4554 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4555 wakaba 1.19 level => $self->{must_level},
4556 wakaba 1.38 uri => \$self->{href},
4557 wakaba 1.19 token => $t);
4558     return ($t, undef);
4559     }
4560 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4561 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4562     my $value = $t->{number} * $sign;
4563     $t = $tt->get_next_token;
4564     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
4565     unless ($value >= 0) {
4566 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4567 wakaba 1.19 level => $self->{must_level},
4568 wakaba 1.38 uri => \$self->{href},
4569 wakaba 1.19 token => $t);
4570     return ($t, undef);
4571     }
4572     } else {
4573 wakaba 1.24 if ($sign < 0) {
4574 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4575 wakaba 1.24 level => $self->{must_level},
4576 wakaba 1.38 uri => \$self->{href},
4577 wakaba 1.24 token => $t);
4578     return ($t, undef);
4579     }
4580 wakaba 1.19 return ($t, \%prop_value);
4581     }
4582     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4583    
4584     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4585     $sign = 1;
4586     if ($t->{type} == MINUS_TOKEN) {
4587     $t = $tt->get_next_token;
4588     $sign = -1;
4589     }
4590    
4591     if ($t->{type} == DIMENSION_TOKEN) {
4592     my $value = $t->{number} * $sign;
4593     my $unit = lc $t->{value}; ## TODO: case
4594     $t = $tt->get_next_token;
4595     if ($length_unit->{$unit} and $value >= 0) {
4596     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
4597     } else {
4598 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4599 wakaba 1.19 level => $self->{must_level},
4600 wakaba 1.38 uri => \$self->{href},
4601 wakaba 1.19 token => $t);
4602     return ($t, undef);
4603     }
4604     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4605     my $value = $t->{number} * $sign;
4606     $t = $tt->get_next_token;
4607     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
4608     unless ($value >= 0) {
4609 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4610 wakaba 1.19 level => $self->{must_level},
4611 wakaba 1.38 uri => \$self->{href},
4612 wakaba 1.19 token => $t);
4613     return ($t, undef);
4614     }
4615 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4616 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4617     my $value = $t->{number} * $sign;
4618     $t = $tt->get_next_token;
4619     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
4620     unless ($value >= 0) {
4621 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4622 wakaba 1.19 level => $self->{must_level},
4623 wakaba 1.38 uri => \$self->{href},
4624 wakaba 1.19 token => $t);
4625     return ($t, undef);
4626     }
4627     } else {
4628 wakaba 1.24 if ($sign < 0) {
4629 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4630 wakaba 1.24 level => $self->{must_level},
4631 wakaba 1.38 uri => \$self->{href},
4632 wakaba 1.24 token => $t);
4633     return ($t, undef);
4634     }
4635 wakaba 1.19 return ($t, \%prop_value);
4636     }
4637    
4638     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4639     $sign = 1;
4640     if ($t->{type} == MINUS_TOKEN) {
4641     $t = $tt->get_next_token;
4642     $sign = -1;
4643     }
4644    
4645     if ($t->{type} == DIMENSION_TOKEN) {
4646     my $value = $t->{number} * $sign;
4647     my $unit = lc $t->{value}; ## TODO: case
4648     $t = $tt->get_next_token;
4649     if ($length_unit->{$unit} and $value >= 0) {
4650     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
4651     } else {
4652 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4653 wakaba 1.19 level => $self->{must_level},
4654 wakaba 1.38 uri => \$self->{href},
4655 wakaba 1.19 token => $t);
4656     return ($t, undef);
4657     }
4658     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4659     my $value = $t->{number} * $sign;
4660     $t = $tt->get_next_token;
4661     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
4662     unless ($value >= 0) {
4663 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4664 wakaba 1.19 level => $self->{must_level},
4665 wakaba 1.38 uri => \$self->{href},
4666 wakaba 1.19 token => $t);
4667     return ($t, undef);
4668     }
4669 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4670 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4671     my $value = $t->{number} * $sign;
4672     $t = $tt->get_next_token;
4673     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
4674     unless ($value >= 0) {
4675 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4676 wakaba 1.19 level => $self->{must_level},
4677 wakaba 1.38 uri => \$self->{href},
4678 wakaba 1.19 token => $t);
4679     return ($t, undef);
4680     }
4681     } else {
4682 wakaba 1.24 if ($sign < 0) {
4683 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4684 wakaba 1.24 level => $self->{must_level},
4685 wakaba 1.38 uri => \$self->{href},
4686 wakaba 1.24 token => $t);
4687     return ($t, undef);
4688     }
4689 wakaba 1.19 return ($t, \%prop_value);
4690     }
4691    
4692     return ($t, \%prop_value);
4693     },
4694 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
4695 wakaba 1.19 };
4696     $Attr->{padding} = $Prop->{padding};
4697    
4698 wakaba 1.24 $Prop->{'border-spacing'} = {
4699     css => 'border-spacing',
4700     dom => 'border_spacing',
4701     parse => sub {
4702     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4703    
4704     my %prop_value;
4705 wakaba 1.53 my $has_sign;
4706 wakaba 1.24 my $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-x'} = ['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-x'} = ['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 wakaba 1.53 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4743 wakaba 1.24 my $prop_value = lc $t->{value}; ## TODO: case folding
4744     if ($prop_value eq 'inherit') {
4745 wakaba 1.53 $t = $tt->get_next_token;
4746 wakaba 1.24 $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
4747     $prop_value{'-manakai-border-spacing-y'}
4748     = $prop_value{'-manakai-border-spacing-x'};
4749     return ($t, \%prop_value);
4750     } else {
4751 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4752 wakaba 1.24 level => $self->{must_level},
4753 wakaba 1.38 uri => \$self->{href},
4754 wakaba 1.24 token => $t);
4755     return ($t, undef);
4756     }
4757     } else {
4758 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4759 wakaba 1.24 level => $self->{must_level},
4760 wakaba 1.38 uri => \$self->{href},
4761 wakaba 1.24 token => $t);
4762     return ($t, undef);
4763     }
4764     $prop_value{'-manakai-border-spacing-y'}
4765     = $prop_value{'-manakai-border-spacing-x'};
4766    
4767     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4768 wakaba 1.53 undef $has_sign;
4769 wakaba 1.24 $sign = 1;
4770     if ($t->{type} == MINUS_TOKEN) {
4771     $t = $tt->get_next_token;
4772 wakaba 1.53 $has_sign = 1;
4773 wakaba 1.24 $sign = -1;
4774 wakaba 1.53 } elsif ($t->{type} == PLUS_TOKEN) {
4775     $t = $tt->get_next_token;
4776     $has_sign = 1;
4777 wakaba 1.24 }
4778    
4779     if ($t->{type} == DIMENSION_TOKEN) {
4780     my $value = $t->{number} * $sign;
4781     my $unit = lc $t->{value}; ## TODO: case
4782     if ($length_unit->{$unit} and $value >= 0) {
4783 wakaba 1.53 $t = $tt->get_next_token;
4784 wakaba 1.24 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
4785     } else {
4786 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4787 wakaba 1.24 level => $self->{must_level},
4788 wakaba 1.38 uri => \$self->{href},
4789 wakaba 1.24 token => $t);
4790     return ($t, undef);
4791     }
4792     } elsif ($t->{type} == NUMBER_TOKEN and
4793     ($self->{unitless_px} or $t->{number} == 0)) {
4794     my $value = $t->{number} * $sign;
4795     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
4796 wakaba 1.53 if ($value >= 0) {
4797     $t = $tt->get_next_token;
4798     } else {
4799 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4800 wakaba 1.24 level => $self->{must_level},
4801 wakaba 1.38 uri => \$self->{href},
4802 wakaba 1.24 token => $t);
4803     return ($t, undef);
4804     }
4805     } else {
4806 wakaba 1.53 if ($has_sign) {
4807 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4808 wakaba 1.24 level => $self->{must_level},
4809 wakaba 1.38 uri => \$self->{href},
4810 wakaba 1.24 token => $t);
4811     return ($t, undef);
4812     }
4813     return ($t, \%prop_value);
4814     }
4815    
4816     return ($t, \%prop_value);
4817     },
4818 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
4819     ->{serialize_multiple},
4820 wakaba 1.24 };
4821     $Attr->{border_spacing} = $Prop->{'border-spacing'};
4822    
4823 wakaba 1.27 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
4824     ## browser compatibility problems.
4825     $Prop->{'background-position'} = {
4826     css => 'background-position',
4827     dom => 'background_position',
4828     parse => sub {
4829     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4830    
4831     my %prop_value;
4832    
4833     my $sign = 1;
4834 wakaba 1.52 my $has_sign;
4835 wakaba 1.27 if ($t->{type} == MINUS_TOKEN) {
4836     $t = $tt->get_next_token;
4837 wakaba 1.52 $has_sign = 1;
4838 wakaba 1.27 $sign = -1;
4839 wakaba 1.52 } elsif ($t->{type} == PLUS_TOKEN) {
4840     $t = $tt->get_next_token;
4841     $has_sign = 1;
4842 wakaba 1.27 }
4843    
4844     if ($t->{type} == DIMENSION_TOKEN) {
4845     my $value = $t->{number} * $sign;
4846     my $unit = lc $t->{value}; ## TODO: case
4847     if ($length_unit->{$unit}) {
4848 wakaba 1.52 $t = $tt->get_next_token;
4849 wakaba 1.27 $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
4850     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4851     } else {
4852 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4853 wakaba 1.27 level => $self->{must_level},
4854 wakaba 1.38 uri => \$self->{href},
4855 wakaba 1.27 token => $t);
4856     return ($t, undef);
4857     }
4858     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4859     my $value = $t->{number} * $sign;
4860     $t = $tt->get_next_token;
4861     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
4862     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4863     } elsif ($t->{type} == NUMBER_TOKEN and
4864     ($self->{unitless_px} or $t->{number} == 0)) {
4865     my $value = $t->{number} * $sign;
4866     $t = $tt->get_next_token;
4867     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
4868     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4869 wakaba 1.52 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4870 wakaba 1.27 my $prop_value = lc $t->{value}; ## TODO: case folding
4871     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
4872 wakaba 1.52 $t = $tt->get_next_token;
4873 wakaba 1.27 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
4874     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
4875     } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
4876 wakaba 1.52 $t = $tt->get_next_token;
4877 wakaba 1.27 $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
4878    
4879     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4880     if ($t->{type} == IDENT_TOKEN) {
4881     my $prop_value = lc $t->{value}; ## TODO: case folding
4882     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
4883     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
4884     $t = $tt->get_next_token;
4885     return ($t, \%prop_value);
4886     }
4887     }
4888     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
4889     return ($t, \%prop_value);
4890     } elsif ($prop_value eq 'inherit') {
4891 wakaba 1.52 $t = $tt->get_next_token;
4892 wakaba 1.27 $prop_value{'background-position-x'} = ['INHERIT'];
4893     $prop_value{'background-position-y'} = ['INHERIT'];
4894     return ($t, \%prop_value);
4895     } else {
4896 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4897 wakaba 1.27 level => $self->{must_level},
4898 wakaba 1.38 uri => \$self->{href},
4899 wakaba 1.27 token => $t);
4900     return ($t, undef);
4901     }
4902     } else {
4903 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4904 wakaba 1.27 level => $self->{must_level},
4905 wakaba 1.38 uri => \$self->{href},
4906 wakaba 1.27 token => $t);
4907     return ($t, undef);
4908     }
4909    
4910     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4911 wakaba 1.52 undef $has_sign;
4912 wakaba 1.27 $sign = 1;
4913     if ($t->{type} == MINUS_TOKEN) {
4914     $t = $tt->get_next_token;
4915 wakaba 1.52 $has_sign = 1;
4916 wakaba 1.27 $sign = -1;
4917 wakaba 1.52 } elsif ($t->{type} == PLUS_TOKEN) {
4918     $t = $tt->get_next_token;
4919     $has_sign = 1;
4920 wakaba 1.27 }
4921    
4922     if ($t->{type} == DIMENSION_TOKEN) {
4923     my $value = $t->{number} * $sign;
4924     my $unit = lc $t->{value}; ## TODO: case
4925     if ($length_unit->{$unit}) {
4926 wakaba 1.52 $t = $tt->get_next_token;
4927 wakaba 1.27 $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
4928     } else {
4929 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4930 wakaba 1.27 level => $self->{must_level},
4931 wakaba 1.38 uri => \$self->{href},
4932 wakaba 1.27 token => $t);
4933     return ($t, undef);
4934     }
4935     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4936     my $value = $t->{number} * $sign;
4937     $t = $tt->get_next_token;
4938     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
4939 wakaba 1.30 } elsif ($t->{type} == NUMBER_TOKEN and
4940     ($self->{unitless_px} or $t->{number} == 0)) {
4941 wakaba 1.27 my $value = $t->{number} * $sign;
4942     $t = $tt->get_next_token;
4943     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
4944 wakaba 1.52 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4945 wakaba 1.27 my $value = lc $t->{value}; ## TODO: case
4946     if ({top => 1, center => 1, bottom => 1}->{$value}) {
4947     $prop_value{'background-position-y'} = ['KEYWORD', $value];
4948     $t = $tt->get_next_token;
4949     }
4950     } else {
4951 wakaba 1.52 if ($has_sign) {
4952 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
4953 wakaba 1.27 level => $self->{must_level},
4954 wakaba 1.38 uri => \$self->{href},
4955 wakaba 1.27 token => $t);
4956     return ($t, undef);
4957     }
4958     return ($t, \%prop_value);
4959     }
4960    
4961     return ($t, \%prop_value);
4962     },
4963 wakaba 1.48 serialize_shorthand => sub {
4964     my $self = shift;
4965    
4966     my $r = {};
4967    
4968 wakaba 1.27 my $x = $self->background_position_x;
4969     my $y = $self->background_position_y;
4970 wakaba 1.48 my $xi = $self->get_property_priority ('background-position-x');
4971     my $yi = $self->get_property_priority ('background-position-y');
4972     if (length $x) {
4973     if (length $y) {
4974     if ($xi eq $yi) {
4975     if ($x eq 'inherit') {
4976     if ($y eq 'inherit') {
4977     $r->{'background-position'} = ['inherit', $xi];
4978     } else {
4979     $r->{'background-position-x'} = [$x, $xi];
4980     $r->{'background-position-y'} = [$y, $yi];
4981     }
4982     } elsif ($y eq 'inherit') {
4983     $r->{'background-position-x'} = [$x, $xi];
4984     $r->{'background-position-y'} = [$y, $yi];
4985     } else {
4986     $r->{'background-position'} = [$x . ' ' . $y, $xi];
4987     }
4988     } else {
4989     $r->{'background-position-x'} = [$x, $xi];
4990     $r->{'background-position-y'} = [$y, $yi];
4991     }
4992     } else {
4993     $r->{'background-position-x'} = [$x, $xi];
4994     }
4995     } else {
4996     if (length $y) {
4997     $r->{'background-position-y'} = [$y, $yi];
4998     } else {
4999     #
5000     }
5001     }
5002    
5003     return $r;
5004 wakaba 1.27 },
5005 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5006 wakaba 1.27 };
5007     $Attr->{background_position} = $Prop->{'background-position'};
5008    
5009 wakaba 1.30 $Prop->{background} = {
5010     css => 'background',
5011     dom => 'background',
5012     parse => sub {
5013     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5014     my %prop_value;
5015     B: for (1..5) {
5016 wakaba 1.48 my $has_sign;
5017 wakaba 1.30 my $sign = 1;
5018     if ($t->{type} == MINUS_TOKEN) {
5019     $sign = -1;
5020 wakaba 1.48 $has_sign = 1;
5021     $t = $tt->get_next_token;
5022     } elsif ($t->{type} == PLUS_TOKEN) {
5023     $has_sign = 1;
5024 wakaba 1.30 $t = $tt->get_next_token;
5025     }
5026    
5027 wakaba 1.48 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5028 wakaba 1.30 my $value = lc $t->{value}; ## TODO: case
5029     if ($Prop->{'background-repeat'}->{keyword}->{$value} and
5030     $self->{prop_value}->{'background-repeat'}->{$value} and
5031     not defined $prop_value{'background-repeat'}) {
5032     $prop_value{'background-repeat'} = ['KEYWORD', $value];
5033     $t = $tt->get_next_token;
5034     } elsif ($Prop->{'background-attachment'}->{keyword}->{$value} and
5035     $self->{prop_value}->{'background-attachment'}->{$value} and
5036     not defined $prop_value{'background-attachment'}) {
5037     $prop_value{'background-attachment'} = ['KEYWORD', $value];
5038     $t = $tt->get_next_token;
5039     } elsif ($value eq 'none' and
5040     not defined $prop_value{'background-image'}) {
5041     $prop_value{'background-image'} = ['KEYWORD', $value];
5042     $t = $tt->get_next_token;
5043     } elsif ({left => 1, center => 1, right => 1}->{$value} and
5044     not defined $prop_value{'background-position-x'}) {
5045     $prop_value{'background-position-x'} = ['KEYWORD', $value];
5046     $t = $tt->get_next_token;
5047     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5048     my $sign = 1;
5049 wakaba 1.48 my $has_sign;
5050 wakaba 1.30 if ($t->{type} == MINUS_TOKEN) {
5051     $sign = -1;
5052 wakaba 1.48 $has_sign = 1;
5053     $t = $tt->get_next_token;
5054     } elsif ($t->{type} == PLUS_TOKEN) {
5055     $has_sign = 1;
5056 wakaba 1.30 $t = $tt->get_next_token;
5057     }
5058 wakaba 1.48 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5059 wakaba 1.30 my $value = lc $t->{value}; ## TODO: case
5060     if ({top => 1, bottom => 1, center => 1}->{$value}) {
5061     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5062     $t = $tt->get_next_token;
5063     } elsif ($prop_value{'background-position-x'}->[1] eq 'center' and
5064     $value eq 'left' or $value eq 'right') {
5065     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5066     $prop_value{'background-position-x'} = ['KEYWORD', $value];
5067     $t = $tt->get_next_token;
5068     } else {
5069     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5070     }
5071     } elsif ($t->{type} == DIMENSION_TOKEN) {
5072     my $value = $t->{number} * $sign;
5073     my $unit = lc $t->{value}; ## TODO: case
5074     $t = $tt->get_next_token;
5075     if ($length_unit->{$unit}) {
5076     $prop_value{'background-position-y'}
5077     = ['DIMENSION', $value, $unit];
5078     } else {
5079 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5080 wakaba 1.30 level => $self->{must_level},
5081 wakaba 1.38 uri => \$self->{href},
5082 wakaba 1.30 token => $t);
5083 wakaba 1.48 return ($t, undef);
5084 wakaba 1.30 }
5085     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5086     my $value = $t->{number} * $sign;
5087     $t = $tt->get_next_token;
5088     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5089     } elsif ($t->{type} == NUMBER_TOKEN and
5090     ($self->{unitless_px} or $t->{number} == 0)) {
5091     my $value = $t->{number} * $sign;
5092     $t = $tt->get_next_token;
5093     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5094 wakaba 1.48 } elsif ($has_sign) {
5095 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5096 wakaba 1.30 level => $self->{must_level},
5097 wakaba 1.38 uri => \$self->{href},
5098 wakaba 1.30 token => $t);
5099 wakaba 1.48 return ($t, undef);
5100     } else {
5101     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5102 wakaba 1.30 }
5103     } elsif (($value eq 'top' or $value eq 'bottom') and
5104     not defined $prop_value{'background-position-y'}) {
5105     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5106     $t = $tt->get_next_token;
5107     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5108     if ($t->{type} == IDENT_TOKEN and ## TODO: case
5109 wakaba 1.48 {
5110     left => 1, center => 1, right => 1,
5111     }->{my $value = lc $t->{value}}) {
5112 wakaba 1.30 $prop_value{'background-position-x'} = ['KEYWORD', $value];
5113     $t = $tt->get_next_token;
5114     } else {
5115     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5116     }
5117     } elsif ($value eq 'inherit' and not keys %prop_value) {
5118     $prop_value{'background-color'} =
5119     $prop_value{'background-image'} =
5120     $prop_value{'background-repeat'} =
5121     $prop_value{'background-attachment'} =
5122     $prop_value{'background-position-x'} =
5123     $prop_value{'background-position-y'} = ['INHERIT'];
5124     $t = $tt->get_next_token;
5125     return ($t, \%prop_value);
5126     } elsif (not defined $prop_value{'background-color'} or
5127     not keys %prop_value) {
5128     ($t, my $pv) = $parse_color->($self, 'background', $tt, $t,
5129     $onerror);
5130     if (defined $pv) {
5131     $prop_value{'background-color'} = $pv->{background};
5132     } else {
5133     ## NOTE: An error should already be raiased.
5134     return ($t, undef);
5135     }
5136     }
5137     } elsif (($t->{type} == DIMENSION_TOKEN or
5138     $t->{type} == PERCENTAGE_TOKEN or
5139     ($t->{type} == NUMBER_TOKEN and
5140 wakaba 1.48 ($t->{unitless_px} or $t->{number} == 0))) and
5141 wakaba 1.30 not defined $prop_value{'background-position-x'}) {
5142     if ($t->{type} == DIMENSION_TOKEN) {
5143     my $value = $t->{number} * $sign;
5144     my $unit = lc $t->{value}; ## TODO: case
5145     $t = $tt->get_next_token;
5146     if ($length_unit->{$unit}) {
5147     $prop_value{'background-position-x'}
5148     = ['DIMENSION', $value, $unit];
5149     } else {
5150 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5151 wakaba 1.30 level => $self->{must_level},
5152 wakaba 1.38 uri => \$self->{href},
5153 wakaba 1.30 token => $t);
5154 wakaba 1.48 return ($t, undef);
5155 wakaba 1.30 }
5156     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5157     my $value = $t->{number} * $sign;
5158     $t = $tt->get_next_token;
5159     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5160     } elsif ($t->{type} == NUMBER_TOKEN and
5161     ($self->{unitless_px} or $t->{number} == 0)) {
5162     my $value = $t->{number} * $sign;
5163     $t = $tt->get_next_token;
5164     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5165     } else {
5166     ## NOTE: Should not be happened.
5167     last B;
5168     }
5169    
5170     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5171     if ($t->{type} == MINUS_TOKEN) {
5172     $sign = -1;
5173 wakaba 1.48 $has_sign = 1;
5174     $t = $tt->get_next_token;
5175     } elsif ($t->{type} == PLUS_TOKEN) {
5176     $has_sign = 1;
5177 wakaba 1.30 $t = $tt->get_next_token;
5178     } else {
5179 wakaba 1.48 undef $has_sign;
5180 wakaba 1.30 $sign = 1;
5181     }
5182    
5183     if ($t->{type} == DIMENSION_TOKEN) {
5184     my $value = $t->{number} * $sign;
5185     my $unit = lc $t->{value}; ## TODO: case
5186     $t = $tt->get_next_token;
5187     if ($length_unit->{$unit}) {
5188     $prop_value{'background-position-y'}
5189     = ['DIMENSION', $value, $unit];
5190     } else {
5191 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5192 wakaba 1.30 level => $self->{must_level},
5193 wakaba 1.38 uri => \$self->{href},
5194 wakaba 1.30 token => $t);
5195 wakaba 1.48 return ($t, undef);
5196 wakaba 1.30 }
5197     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5198     my $value = $t->{number} * $sign;
5199     $t = $tt->get_next_token;
5200     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5201     } elsif ($t->{type} == NUMBER_TOKEN and
5202     ($self->{unitless_px} or $t->{number} == 0)) {
5203     my $value = $t->{number} * $sign;
5204     $t = $tt->get_next_token;
5205     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5206     } elsif ($t->{type} == IDENT_TOKEN) {
5207     my $value = lc $t->{value}; ## TODO: case
5208     if ({top => 1, center => 1, bottom => 1}->{$value}) {
5209     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5210     $t = $tt->get_next_token;
5211     } else {
5212     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5213     }
5214     } else {
5215 wakaba 1.48 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5216     if ($has_sign) {
5217 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5218 wakaba 1.30 level => $self->{must_level},
5219 wakaba 1.38 uri => \$self->{href},
5220 wakaba 1.30 token => $t);
5221 wakaba 1.48 return ($t, undef);
5222 wakaba 1.30 }
5223     }
5224 wakaba 1.48 } elsif (not $has_sign and
5225     $t->{type} == URI_TOKEN and
5226 wakaba 1.30 not defined $prop_value{'background-image'}) {
5227 wakaba 1.48 $prop_value{'background-image'}
5228     = ['URI', $t->{value}, \($self->{base_uri})];
5229 wakaba 1.30 $t = $tt->get_next_token;
5230     } else {
5231 wakaba 1.48 if (keys %prop_value and not $has_sign) {
5232 wakaba 1.30 last B;
5233     } else {
5234 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5235 wakaba 1.30 level => $self->{must_level},
5236 wakaba 1.38 uri => \$self->{href},
5237 wakaba 1.30 token => $t);
5238 wakaba 1.48 return ($t, undef);
5239 wakaba 1.30 }
5240     }
5241    
5242     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5243     } # B
5244    
5245     $prop_value{$_} ||= $Prop->{$_}->{initial}
5246     for qw/background-image background-attachment background-repeat
5247     background-color background-position-x background-position-y/;
5248    
5249     return ($t, \%prop_value);
5250     },
5251     serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5252     };
5253     $Attr->{background} = $Prop->{background};
5254    
5255 wakaba 1.31 $Prop->{font} = {
5256     css => 'font',
5257     dom => 'font',
5258     parse => sub {
5259     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5260    
5261     my %prop_value;
5262    
5263     A: for (1..3) {
5264     if ($t->{type} == IDENT_TOKEN) {
5265     my $value = lc $t->{value}; ## TODO: case
5266     if ($value eq 'normal') {
5267     $t = $tt->get_next_token;
5268     } elsif ($Prop->{'font-style'}->{keyword}->{$value} and
5269     $self->{prop_value}->{'font-style'}->{$value} and
5270     not defined $prop_value{'font-style'}) {
5271     $prop_value{'font-style'} = ['KEYWORD', $value];
5272     $t = $tt->get_next_token;
5273     } elsif ($Prop->{'font-variant'}->{keyword}->{$value} and
5274     $self->{prop_value}->{'font-variant'}->{$value} and
5275     not defined $prop_value{'font-variant'}) {
5276     $prop_value{'font-variant'} = ['KEYWORD', $value];
5277     $t = $tt->get_next_token;
5278     } elsif ({normal => 1, bold => 1,
5279     bolder => 1, lighter => 1}->{$value} and
5280     not defined $prop_value{'font-weight'}) {
5281     $prop_value{'font-weight'} = ['KEYWORD', $value];
5282     $t = $tt->get_next_token;
5283     } elsif ($value eq 'inherit' and 0 == keys %prop_value) {
5284     $t = $tt->get_next_token;
5285     return ($t, {'font-style' => ['INHERIT'],
5286     'font-variant' => ['INHERIT'],
5287     'font-weight' => ['INHERIT'],
5288     'font-size' => ['INHERIT'],
5289     'line-height' => ['INHERIT'],
5290     'font-family' => ['INHERIT']});
5291     } elsif ({
5292     caption => 1, icon => 1, menu => 1,
5293     'message-box' => 1, 'small-caption' => 1, 'status-bar' => 1,
5294     }->{$value} and 0 == keys %prop_value) {
5295     $t = $tt->get_next_token;
5296     return ($t, $self->{get_system_font}->($self, $value, {
5297     'font-style' => $Prop->{'font-style'}->{initial},
5298     'font-variant' => $Prop->{'font-variant'}->{initial},
5299     'font-weight' => $Prop->{'font-weight'}->{initial},
5300     'font-size' => $Prop->{'font-size'}->{initial},
5301     'line-height' => $Prop->{'line-height'}->{initial},
5302     'font-family' => ['FONT', ['KEYWORD', '-manakai-'.$value]],
5303     }));
5304     } else {
5305     if (keys %prop_value) {
5306     last A;
5307     } else {
5308 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5309 wakaba 1.31 level => $self->{must_level},
5310 wakaba 1.38 uri => \$self->{href},
5311 wakaba 1.31 token => $t);
5312     return ($t, undef);
5313     }
5314     }
5315     } elsif ($t->{type} == NUMBER_TOKEN) {
5316     if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5317 wakaba 1.49 600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5318     $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5319 wakaba 1.31 $t = $tt->get_next_token;
5320     } else {
5321     last A;
5322     }
5323 wakaba 1.49 } elsif ($t->{type} == PLUS_TOKEN) {
5324     $t = $tt->get_next_token;
5325     if ($t->{type} == NUMBER_TOKEN) {
5326     if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5327     600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5328     $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5329     $t = $tt->get_next_token;
5330     } else {
5331     ## NOTE: <'font-size'> or invalid
5332     last A;
5333     }
5334     } elsif ($t->{type} == DIMENSION_TOKEN or
5335     $t->{type} == PERCENTAGE_TOKEN) {
5336     ## NOTE: <'font-size'> or invalid
5337     last A;
5338     } else {
5339     $onerror->(type => "syntax error:'$prop_name'",
5340     level => $self->{must_level},
5341     uri => \$self->{href},
5342     token => $t);
5343     return ($t, undef);
5344     }
5345     } else {
5346     last A;
5347 wakaba 1.31 }
5348    
5349     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5350     } # A
5351    
5352     for (qw/font-style font-variant font-weight/) {
5353     $prop_value{$_} = $Prop->{$_}->{initial} unless defined $prop_value{$_};
5354     }
5355    
5356     ($t, my $pv) = $Prop->{'font-size'}->{parse}
5357     ->($self, 'font', $tt, $t, $onerror);
5358     return ($t, undef) unless defined $pv;
5359     if ($pv->{font}->[0] eq 'INHERIT') {
5360 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5361 wakaba 1.31 level => $self->{must_level},
5362 wakaba 1.38 uri => \$self->{href},
5363 wakaba 1.31 token => $t);
5364     return ($t, undef);
5365     }
5366     $prop_value{'font-size'} = $pv->{font};
5367    
5368     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5369     if ($t->{type} == DELIM_TOKEN and $t->{value} eq '/') {
5370     $t = $tt->get_next_token;
5371     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5372     ($t, my $pv) = $Prop->{'line-height'}->{parse}
5373     ->($self, 'font', $tt, $t, $onerror);
5374     return ($t, undef) unless defined $pv;
5375     if ($pv->{font}->[0] eq 'INHERIT') {
5376 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5377 wakaba 1.31 level => $self->{must_level},
5378 wakaba 1.38 uri => \$self->{href},
5379 wakaba 1.31 token => $t);
5380     return ($t, undef);
5381     }
5382     $prop_value{'line-height'} = $pv->{font};
5383     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5384     } else {
5385     $prop_value{'line-height'} = $Prop->{'line-height'}->{initial};
5386     }
5387    
5388     undef $pv;
5389     ($t, $pv) = $Prop->{'font-family'}->{parse}
5390     ->($self, 'font', $tt, $t, $onerror);
5391     return ($t, undef) unless defined $pv;
5392     $prop_value{'font-family'} = $pv->{font};
5393    
5394     return ($t, \%prop_value);
5395     },
5396 wakaba 1.44 serialize_shorthand => sub {
5397     my $self = shift;
5398 wakaba 1.31
5399     local $Error::Depth = $Error::Depth + 1;
5400     my $style = $self->font_style;
5401     my $i = $self->get_property_priority ('font-style');
5402 wakaba 1.44 return {} unless length $style;
5403 wakaba 1.31 my $variant = $self->font_variant;
5404 wakaba 1.44 return {} unless length $variant;
5405     return {} if $i ne $self->get_property_priority ('font-variant');
5406 wakaba 1.31 my $weight = $self->font_weight;
5407 wakaba 1.44 return {} unless length $weight;
5408     return {} if $i ne $self->get_property_priority ('font-weight');
5409 wakaba 1.31 my $size = $self->font_size;
5410 wakaba 1.44 return {} unless length $size;
5411     return {} if $i ne $self->get_property_priority ('font-size');
5412 wakaba 1.31 my $height = $self->line_height;
5413 wakaba 1.44 return {} unless length $height;
5414     return {} if $i ne $self->get_property_priority ('line-height');
5415 wakaba 1.31 my $family = $self->font_family;
5416 wakaba 1.44 return {} unless length $family;
5417     return {} if $i ne $self->get_property_priority ('font-family');
5418    
5419     my $v = 0;
5420     for ($style, $variant, $weight, $size, $height, $family) {
5421     $v++ if $_ eq 'inherit';
5422     }
5423     if ($v == 6) {
5424 wakaba 1.45 return {font => ['inherit', $i]};
5425 wakaba 1.44 } elsif ($v) {
5426     return {};
5427     }
5428 wakaba 1.31
5429     my @v;
5430     push @v, $style unless $style eq 'normal';
5431     push @v, $variant unless $variant eq 'normal';
5432     push @v, $weight unless $weight eq 'normal';
5433     push @v, $size.($height eq 'normal' ? '' : '/'.$height);
5434     push @v, $family;
5435 wakaba 1.45 return {font => [(join ' ', @v), $i]};
5436 wakaba 1.31 },
5437     };
5438     $Attr->{font} = $Prop->{font};
5439    
5440 wakaba 1.20 $Prop->{'border-width'} = {
5441     css => 'border-width',
5442     dom => 'border_width',
5443     parse => sub {
5444     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5445    
5446     my %prop_value;
5447    
5448 wakaba 1.51 my $has_sign;
5449 wakaba 1.20 my $sign = 1;
5450     if ($t->{type} == MINUS_TOKEN) {
5451     $t = $tt->get_next_token;
5452 wakaba 1.51 $has_sign = 1;
5453 wakaba 1.20 $sign = -1;
5454 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5455     $t = $tt->get_next_token;
5456     $has_sign = 1;
5457 wakaba 1.20 }
5458    
5459     if ($t->{type} == DIMENSION_TOKEN) {
5460     my $value = $t->{number} * $sign;
5461     my $unit = lc $t->{value}; ## TODO: case
5462     if ($length_unit->{$unit} and $value >= 0) {
5463     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
5464 wakaba 1.51 $t = $tt->get_next_token;
5465 wakaba 1.20 } else {
5466 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5467 wakaba 1.20 level => $self->{must_level},
5468 wakaba 1.38 uri => \$self->{href},
5469 wakaba 1.20 token => $t);
5470     return ($t, undef);
5471     }
5472     } elsif ($t->{type} == NUMBER_TOKEN and
5473     ($self->{unitless_px} or $t->{number} == 0)) {
5474     my $value = $t->{number} * $sign;
5475 wakaba 1.51 if ($value >= 0) {
5476     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
5477     $t = $tt->get_next_token;
5478     } else {
5479 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5480 wakaba 1.20 level => $self->{must_level},
5481 wakaba 1.38 uri => \$self->{href},
5482 wakaba 1.20 token => $t);
5483     return ($t, undef);
5484     }
5485 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5486 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case folding
5487     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5488 wakaba 1.51 $t = $tt->get_next_token;
5489 wakaba 1.20 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
5490     } elsif ($prop_value eq 'inherit') {
5491 wakaba 1.51 $t = $tt->get_next_token;
5492 wakaba 1.20 $prop_value{'border-top-width'} = ['INHERIT'];
5493     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5494     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5495     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5496     return ($t, \%prop_value);
5497     } else {
5498 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5499 wakaba 1.20 level => $self->{must_level},
5500 wakaba 1.38 uri => \$self->{href},
5501 wakaba 1.20 token => $t);
5502     return ($t, undef);
5503     }
5504     } else {
5505 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5506 wakaba 1.20 level => $self->{must_level},
5507 wakaba 1.38 uri => \$self->{href},
5508 wakaba 1.20 token => $t);
5509     return ($t, undef);
5510     }
5511     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5512     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5513     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5514    
5515     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5516     if ($t->{type} == MINUS_TOKEN) {
5517     $t = $tt->get_next_token;
5518 wakaba 1.51 $has_sign = 1;
5519 wakaba 1.20 $sign = -1;
5520 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5521     $t = $tt->get_next_token;
5522     $has_sign = 1;
5523     $sign = 1;
5524     } else {
5525     undef $has_sign;
5526     $sign = 1;
5527 wakaba 1.20 }
5528    
5529     if ($t->{type} == DIMENSION_TOKEN) {
5530     my $value = $t->{number} * $sign;
5531     my $unit = lc $t->{value}; ## TODO: case
5532     if ($length_unit->{$unit} and $value >= 0) {
5533 wakaba 1.51 $t = $tt->get_next_token;
5534 wakaba 1.20 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
5535     } else {
5536 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5537 wakaba 1.20 level => $self->{must_level},
5538 wakaba 1.38 uri => \$self->{href},
5539 wakaba 1.20 token => $t);
5540     return ($t, undef);
5541     }
5542     } elsif ($t->{type} == NUMBER_TOKEN and
5543     ($self->{unitless_px} or $t->{number} == 0)) {
5544     my $value = $t->{number} * $sign;
5545 wakaba 1.51 if ($value >= 0) {
5546     $t = $tt->get_next_token;
5547     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
5548     } else {
5549 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5550 wakaba 1.20 level => $self->{must_level},
5551 wakaba 1.38 uri => \$self->{href},
5552 wakaba 1.20 token => $t);
5553     return ($t, undef);
5554     }
5555 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5556 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5557     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5558     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
5559     $t = $tt->get_next_token;
5560     }
5561     } else {
5562 wakaba 1.51 if ($has_sign) {
5563 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5564 wakaba 1.24 level => $self->{must_level},
5565 wakaba 1.38 uri => \$self->{href},
5566 wakaba 1.24 token => $t);
5567     return ($t, undef);
5568     }
5569 wakaba 1.20 return ($t, \%prop_value);
5570     }
5571     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5572    
5573     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5574     if ($t->{type} == MINUS_TOKEN) {
5575     $t = $tt->get_next_token;
5576 wakaba 1.51 $has_sign = 1;
5577 wakaba 1.20 $sign = -1;
5578 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5579     $t = $tt->get_next_token;
5580     $has_sign = 1;
5581     $sign = 1;
5582     } else {
5583     undef $has_sign;
5584     $sign = 1;
5585 wakaba 1.20 }
5586    
5587     if ($t->{type} == DIMENSION_TOKEN) {
5588     my $value = $t->{number} * $sign;
5589     my $unit = lc $t->{value}; ## TODO: case
5590     if ($length_unit->{$unit} and $value >= 0) {
5591 wakaba 1.51 $t = $tt->get_next_token;
5592 wakaba 1.20 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
5593     } else {
5594 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5595 wakaba 1.20 level => $self->{must_level},
5596 wakaba 1.38 uri => \$self->{href},
5597 wakaba 1.20 token => $t);
5598     return ($t, undef);
5599     }
5600     } elsif ($t->{type} == NUMBER_TOKEN and
5601     ($self->{unitless_px} or $t->{number} == 0)) {
5602     my $value = $t->{number} * $sign;
5603 wakaba 1.51 if ($value >= 0) {
5604     $t = $tt->get_next_token;
5605     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
5606     } else {
5607 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5608 wakaba 1.20 level => $self->{must_level},
5609 wakaba 1.38 uri => \$self->{href},
5610 wakaba 1.20 token => $t);
5611     return ($t, undef);
5612     }
5613 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5614 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5615     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5616     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
5617     $t = $tt->get_next_token;
5618     }
5619     } else {
5620 wakaba 1.51 if ($has_sign) {
5621 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5622 wakaba 1.24 level => $self->{must_level},
5623 wakaba 1.38 uri => \$self->{href},
5624 wakaba 1.24 token => $t);
5625     return ($t, undef);
5626     }
5627 wakaba 1.20 return ($t, \%prop_value);
5628     }
5629    
5630     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5631     if ($t->{type} == MINUS_TOKEN) {
5632     $t = $tt->get_next_token;
5633 wakaba 1.51 $has_sign = 1;
5634 wakaba 1.20 $sign = -1;
5635 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5636     $t = $tt->get_next_token;
5637     $has_sign = 1;
5638     $sign = 1;
5639     } else {
5640     undef $has_sign;
5641     $sign = 1;
5642 wakaba 1.20 }
5643    
5644     if ($t->{type} == DIMENSION_TOKEN) {
5645     my $value = $t->{number} * $sign;
5646     my $unit = lc $t->{value}; ## TODO: case
5647     if ($length_unit->{$unit} and $value >= 0) {
5648 wakaba 1.51 $t = $tt->get_next_token;
5649 wakaba 1.20 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
5650     } else {
5651 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5652 wakaba 1.20 level => $self->{must_level},
5653 wakaba 1.38 uri => \$self->{href},
5654 wakaba 1.20 token => $t);
5655     return ($t, undef);
5656     }
5657     } elsif ($t->{type} == NUMBER_TOKEN and
5658     ($self->{unitless_px} or $t->{number} == 0)) {
5659     my $value = $t->{number} * $sign;
5660 wakaba 1.51 if ($value >= 0) {
5661     $t = $tt->get_next_token;
5662     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
5663     } else {
5664 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5665 wakaba 1.20 level => $self->{must_level},
5666 wakaba 1.38 uri => \$self->{href},
5667 wakaba 1.20 token => $t);
5668     return ($t, undef);
5669     }
5670 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5671 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5672     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5673     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
5674     $t = $tt->get_next_token;
5675     }
5676     } else {
5677 wakaba 1.51 if ($has_sign) {
5678 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5679 wakaba 1.24 level => $self->{must_level},
5680 wakaba 1.38 uri => \$self->{href},
5681 wakaba 1.24 token => $t);
5682     return ($t, undef);
5683     }
5684 wakaba 1.20 return ($t, \%prop_value);
5685     }
5686    
5687     return ($t, \%prop_value);
5688     },
5689 wakaba 1.43 serialize_shorthand => sub {
5690     my $self = shift;
5691    
5692 wakaba 1.20 my @v;
5693 wakaba 1.24 push @v, $self->border_top_width;
5694 wakaba 1.43 my $i = $self->get_property_priority ('border-top-width');
5695 wakaba 1.45 return {} unless length $v[-1];
5696 wakaba 1.24 push @v, $self->border_right_width;
5697 wakaba 1.45 return {} unless length $v[-1];
5698     return {} unless $i eq $self->get_property_priority ('border-right-width');
5699 wakaba 1.24 push @v, $self->border_bottom_width;
5700 wakaba 1.45 return {} unless length $v[-1];
5701     return {} unless $i eq $self->get_property_priority ('border-bottom-width');
5702 wakaba 1.24 push @v, $self->border_left_width;
5703 wakaba 1.45 return {} unless length $v[-1];
5704     return {} unless $i eq $self->get_property_priority ('border-left-width');
5705 wakaba 1.43
5706     my $v = 0;
5707     for (0..3) {
5708     $v++ if $v[$_] eq 'inherit';
5709     }
5710     if ($v == 4) {
5711 wakaba 1.45 return {'border-width' => ['inherit', $i]};
5712 wakaba 1.43 } elsif ($v) {
5713     return {};
5714     }
5715 wakaba 1.20
5716     pop @v if $v[1] eq $v[3];
5717     pop @v if $v[0] eq $v[2];
5718     pop @v if $v[0] eq $v[1];
5719 wakaba 1.45 return {'border-width' => [(join ' ', @v), $i]};
5720 wakaba 1.20 },
5721 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
5722 wakaba 1.20 };
5723 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
5724 wakaba 1.20
5725 wakaba 1.12 $Prop->{'list-style'} = {
5726     css => 'list-style',
5727     dom => 'list_style',
5728     parse => sub {
5729     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5730    
5731     my %prop_value;
5732     my $none = 0;
5733    
5734     F: for my $f (1..3) {
5735     if ($t->{type} == IDENT_TOKEN) {
5736     my $prop_value = lc $t->{value}; ## TODO: case folding
5737     $t = $tt->get_next_token;
5738    
5739     if ($prop_value eq 'none') {
5740     $none++;
5741     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
5742     if (exists $prop_value{'list-style-type'}) {
5743 wakaba 1.39 $onerror->(type => "duplication:'list-style-type'",
5744 wakaba 1.12 level => $self->{must_level},
5745 wakaba 1.38 uri => \$self->{href},
5746 wakaba 1.12 token => $t);
5747     return ($t, undef);
5748     } else {
5749     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
5750     }
5751     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
5752     if (exists $prop_value{'list-style-position'}) {
5753 wakaba 1.39 $onerror->(type => "duplication:'list-style-position'",
5754 wakaba 1.12 level => $self->{must_level},
5755 wakaba 1.38 uri => \$self->{href},
5756 wakaba 1.12 token => $t);
5757     return ($t, undef);
5758     }
5759    
5760     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
5761     } elsif ($f == 1 and $prop_value eq 'inherit') {
5762     $prop_value{'list-style-type'} = ["INHERIT"];
5763     $prop_value{'list-style-position'} = ["INHERIT"];
5764     $prop_value{'list-style-image'} = ["INHERIT"];
5765     last F;
5766     } else {
5767     if ($f == 1) {
5768 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5769 wakaba 1.12 level => $self->{must_level},
5770 wakaba 1.38 uri => \$self->{href},
5771 wakaba 1.12 token => $t);
5772     return ($t, undef);
5773     } else {
5774     last F;
5775     }
5776     }
5777     } elsif ($t->{type} == URI_TOKEN) {
5778     if (exists $prop_value{'list-style-image'}) {
5779 wakaba 1.39 $onerror->(type => "duplication:'list-style-image'",
5780 wakaba 1.38 uri => \$self->{href},
5781 wakaba 1.12 level => $self->{must_level},
5782     token => $t);
5783     return ($t, undef);
5784     }
5785    
5786     $prop_value{'list-style-image'}
5787 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
5788 wakaba 1.12 $t = $tt->get_next_token;
5789     } else {
5790     if ($f == 1) {
5791 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5792 wakaba 1.12 level => $self->{must_level},
5793 wakaba 1.38 uri => \$self->{href},
5794 wakaba 1.12 token => $t);
5795     return ($t, undef);
5796     } else {
5797     last F;
5798     }
5799     }
5800    
5801     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5802     } # F
5803     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
5804    
5805     if ($none == 1) {
5806     if (exists $prop_value{'list-style-type'}) {
5807     if (exists $prop_value{'list-style-image'}) {
5808 wakaba 1.39 $onerror->(type => "duplication:'list-style-image'",
5809 wakaba 1.38 uri => \$self->{href},
5810 wakaba 1.12 level => $self->{must_level},
5811     token => $t);
5812     return ($t, undef);
5813     } else {
5814     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
5815     }
5816     } else {
5817     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
5818     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
5819     unless exists $prop_value{'list-style-image'};
5820     }
5821     } elsif ($none == 2) {
5822     if (exists $prop_value{'list-style-type'}) {
5823 wakaba 1.39 $onerror->(type => "duplication:'list-style-type'",
5824 wakaba 1.38 uri => \$self->{href},
5825 wakaba 1.12 level => $self->{must_level},
5826     token => $t);
5827     return ($t, undef);
5828     }
5829     if (exists $prop_value{'list-style-image'}) {
5830 wakaba 1.39 $onerror->(type => "duplication:'list-style-image'",
5831 wakaba 1.38 uri => \$self->{href},
5832 wakaba 1.12 level => $self->{must_level},
5833     token => $t);
5834     return ($t, undef);
5835     }
5836    
5837     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
5838     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
5839     } elsif ($none == 3) {
5840 wakaba 1.39 $onerror->(type => "duplication:'list-style-type'",
5841 wakaba 1.38 uri => \$self->{href},
5842 wakaba 1.12 level => $self->{must_level},
5843     token => $t);
5844     return ($t, undef);
5845     }
5846    
5847     for (qw/list-style-type list-style-position list-style-image/) {
5848     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
5849     }
5850    
5851     return ($t, \%prop_value);
5852     },
5853 wakaba 1.43 ## NOTE: We don't merge longhands in |css_text| serialization,
5854     ## since no browser does.
5855     serialize_shorthand => sub {
5856     my $self = shift;
5857    
5858     ## NOTE: Don't omit any value even if it is the initial value,
5859     ## since WinIE is buggy.
5860 wakaba 1.12
5861 wakaba 1.43 my $type = $self->list_style_type;
5862     return {} unless length $type;
5863     my $type_i = $self->get_property_priority ('list-style-type');
5864     my $image = $self->list_style_image;
5865     return {} unless length $image;
5866     my $image_i = $self->get_property_priority ('list-style-image');
5867     return {} unless $type_i eq $image_i;
5868     my $position = $self->list_style_position;
5869     return {} unless length $position;
5870     my $position_i = $self->get_property_priority ('list-style-position');
5871     return {} unless $type_i eq $position_i;
5872    
5873 wakaba 1.45 return {'list-style' => [$type . ' ' . $image . ' ' . $position, $type_i]};
5874 wakaba 1.12 },
5875     };
5876     $Attr->{list_style} = $Prop->{'list-style'};
5877    
5878 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
5879     ## store the parsed value to support CSS 3 properties.
5880     $Prop->{'text-decoration'} = {
5881     css => 'text-decoration',
5882     dom => 'text_decoration',
5883     key => 'text_decoration',
5884     parse => sub {
5885     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5886    
5887     my $value = ['DECORATION']; # , underline, overline, line-through, blink
5888    
5889     if ($t->{type} == IDENT_TOKEN) {
5890     my $v = lc $t->{value}; ## TODO: case
5891     $t = $tt->get_next_token;
5892     if ($v eq 'inherit') {
5893     return ($t, {$prop_name => ['INHERIT']});
5894     } elsif ($v eq 'none') {
5895     return ($t, {$prop_name => $value});
5896     } elsif ($v eq 'underline' and
5897     $self->{prop_value}->{$prop_name}->{$v}) {
5898     $value->[1] = 1;
5899     } elsif ($v eq 'overline' and
5900     $self->{prop_value}->{$prop_name}->{$v}) {
5901     $value->[2] = 1;
5902     } elsif ($v eq 'line-through' and
5903     $self->{prop_value}->{$prop_name}->{$v}) {
5904     $value->[3] = 1;
5905     } elsif ($v eq 'blink' and
5906     $self->{prop_value}->{$prop_name}->{$v}) {
5907     $value->[4] = 1;
5908     } else {
5909 wakaba 1.39 $onerror->(type => "syntax error:'$prop_name'",
5910 wakaba 1.16 level => $self->{must_level},
5911 wakaba 1.38 uri => \$self->{href},
5912 wakaba 1.16 token => $t);
5913     return ($t, undef);
5914     }
5915     }
5916    
5917     F: {
5918     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5919     last F unless $t->{type} == IDENT_TOKEN;
5920    
5921     my $v = lc $t->{value}; ## TODO: case
5922     $t = $tt->get_next_token;
5923     if ($v eq 'underline' and
5924     $self->{prop_value}->{$prop_name}->{$v}) {
5925     $value->[1] = 1;
5926     } elsif ($v eq 'overline' and
5927     $self->{prop_value}->{$prop_name}->{$v}) {
5928     $value->[1] = 2;
5929     } elsif ($v eq 'line-through' and
5930     $self->{prop_value}->{$prop_name}->{$v}) {
5931     $value->[1] = 3;
5932     } elsif ($v eq 'blink' and
5933     $self->{prop_value}->{$prop_name}->{$v}) {
5934     $value->[1] = 4;
5935     } else {
5936     last F;
5937     }
5938    
5939     redo F;
5940     } # F
5941    
5942     return ($t, {$prop_name => $value});
5943     },
5944     serialize => $default_serializer,
5945     initial => ["KEYWORD", "none"],
5946     #inherited => 0,
5947     compute => $compute_as_specified,
5948     };
5949     $Attr->{text_decoration} = $Prop->{'text-decoration'};
5950     $Key->{text_decoration} = $Prop->{'text-decoration'};
5951    
5952 wakaba 1.1 1;
5953 wakaba 1.55 ## $Date: 2008/01/27 08:22:40 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24