/[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.56 - (hide annotations) (download)
Sun Jan 27 10:14:52 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.55: +64 -1 lines
++ whatpm/t/ChangeLog	27 Jan 2008 10:14:45 -0000
	* CSS-Parser-1.t: 'quotes' added.

	* css-generated.dat: Test data for 'quotes' are added.

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

++ whatpm/Whatpm/CSS/ChangeLog	27 Jan 2008 10:14:15 -0000
	* Parser.pm ($default_serializer): 'QUOTES' type supported.
	('quotes'): Implemented.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24