/[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.38 - (hide annotations) (download)
Sun Jan 20 06:15:20 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.37: +131 -34 lines
++ whatpm/Whatpm/CSS/ChangeLog	20 Jan 2008 06:15:14 -0000
	* Parser.pm, SelectorsParser.pm: |{href}| parameter added
	to all the onerror invocations.  The |{onerror}| function
	is no longer called with |{line}| and |{column}| parameters.

	* Tokenizer.pm: All token are now given |{line}| and |{column}|
	values.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24