/[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.45 - (hide annotations) (download)
Sat Jan 26 05:11:01 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.44: +42 -62 lines
++ whatpm/t/ChangeLog	26 Jan 2008 05:10:58 -0000
	* css-1.dat: Some test results were incorrect.

	* css-font.dat: New test data on 'font' are added.

	* css-visual.dat: Some test results were incorrect.  New
	test data on 'margin' are added.

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

++ whatpm/Whatpm/CSS/ChangeLog	26 Jan 2008 05:09:37 -0000
	* Parser.pm (serialize_shorthand, serialize_multiple): The
	leading | ! |s in the priority part were removed (now they
	are added by |css_text| attribute implementation).
	(border-width, border-style, border-color serialize_shorthand): Typo
	fixed.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24