/[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.51 - (hide annotations) (download)
Sun Jan 27 06:42:05 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.50: +95 -46 lines
++ whatpm/t/ChangeLog	27 Jan 2008 06:42:00 -0000
2008-01-27  Wakaba  <wakaba@suika.fam.cx>

	* css-visual.dat: New test data for 'border-top-width', 'border-width',
	'padding-top', 'top', 'right', 'bottom', 'left', 'z-index',
	'width', 'min-width', 'max-width', 'height', 'min-height',
	'max-height', 'line-height', and 'vertical-align' are added.

++ whatpm/Whatpm/CSS/ChangeLog	27 Jan 2008 06:40:08 -0000
2008-01-27  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm ('z-index', 'line-height', 'border-width' parse): Support
	for '+'.
	('margin-top' parse): Fixed to report more accureate error
	position.
	('right'): Negative values and 'auto' were not supported.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24