/[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.58 - (hide annotations) (download)
Tue Jan 29 22:15:01 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.57: +135 -4 lines
++ whatpm/t/ChangeLog	29 Jan 2008 22:14:49 -0000
2008-01-30  Wakaba  <wakaba@suika.fam.cx>

	* css-generated.dat: New test data for 'counter-increment', 'content',
	and 'counter-reset' are added.

	* CSS-Parser-1.t: 'counter-reset' and 'counter-increment'
	are added.

++ whatpm/Whatpm/CSS/ChangeLog	29 Jan 2008 22:13:54 -0000
2008-01-30  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm ($default_serializer): New 'ADDCOUNTER'
	and 'SETCOUNTER' types supported.
	('content'): 'counter()' and 'counters()' are supported iff
	the property 'counter-reset' is supported.
	('counter-reset', 'counter-increment'): Implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24