/[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.68 - (hide annotations) (download)
Mon Feb 11 09:53:37 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.67: +84 -1 lines
++ whatpm/t/ChangeLog	11 Feb 2008 09:53:28 -0000
	* css-text.dat: Test data for 'writing-mode' and 'text-anchor'
	are added.

	* CSS-Parser-1.t: New properties are added.

2008-02-11  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/CSS/ChangeLog	11 Feb 2008 09:52:47 -0000
	* Parser.pm: 'writing-mode', 'text-anchor', 'dominant-baseline',
	and 'alignment-baseline' are implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24