/[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.43 - (hide annotations) (download)
Fri Jan 25 16:04:39 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.42: +307 -160 lines
++ whatpm/t/ChangeLog	25 Jan 2008 16:01:06 -0000
2008-01-26  Wakaba  <wakaba@suika.fam.cx>

	* css-visual.dat: Test data for 'padding', 'margin',
	and 'border' related shorthands are added.

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

	* CSS-Parser-1.t: |css-generated.dat| is added.
	(get_computed_style): The subject of |query_selector|
	should have been the document.

	* css-generated.dat: New test data.

	* css-visual.dat: New test data for 'display' are added.

++ whatpm/Whatpm/CSS/ChangeLog	25 Jan 2008 15:59:41 -0000
2008-01-26  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Return value of |serialize_multiple| methods
	are changed.
	('margin' serialize_multiple, 'padding' serialize_multiple):
	Implemented.
	('border-style' serialize_shorthand, 'border-color'
	serialize_shorthand, 'border-width' serialize_shorthand,
	'border-left' serialize_shorthand, 'border-top' serialize_shorthand,
	'border-bottom' serialize_shorthand, 'border-right'
	serialize_shorthand): New.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24