/[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.46 - (hide annotations) (download)
Sat Jan 26 09:05:07 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.45: +93 -24 lines
++ whatpm/t/ChangeLog	26 Jan 2008 09:05:05 -0000
	* css-visual.dat: New test data for 'border' are added.

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

++ whatpm/Whatpm/CSS/ChangeLog	26 Jan 2008 09:04:26 -0000
	* Parser.pm ('border' serialize_multiple): Reimplement
	to take 'inherit' and 'important' into account.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24