File Coverage

blib/lib/CSS/Prepare/Property/Expansions.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package CSS::Prepare::Property::Expansions;
2              
3 58     58   48144 use Modern::Perl;
  58         106  
  58         325  
4 58     58   721148 use CSS::Prepare::Property::Values;
  58         164  
  58         53991  
5 58     58   1023 use Exporter;
  58         118  
  58         571137  
6              
7             our @ISA = qw( Exporter );
8             our @EXPORT = qw(
9             expand_trbl_shorthand
10             collapse_trbl_shorthand
11             expand_clip
12             shorten_colour_value
13             shorten_length_value
14             shorten_url_value
15             validate_any_order_shorthand
16             get_corner_values
17             expand_corner_values
18             collapse_corner_values
19             );
20              
21              
22              
23              
24             sub expand_trbl_shorthand {
25             my $pattern = shift;
26             my $value = shift;
27            
28             my @values = split( m{\s+}, $value );
29             my %values;
30            
31             given ( $#values ) {
32             when ( 0 ) {
33             # top/bottom/left/right shorthand
34             foreach my $subproperty qw( top bottom left right ) {
35             my $key = sprintf $pattern, $subproperty;
36             $values{ $key } = shorten_length_value( $values[0] );
37             }
38             }
39             when ( 1 ) {
40             # top/bottom and left/right shorthand
41             foreach my $subproperty qw ( top bottom ) {
42             my $key = sprintf $pattern, $subproperty;
43             $values{ $key } = shorten_length_value( $values[0] );
44             }
45             foreach my $subproperty qw ( left right ) {
46             my $key = sprintf $pattern, $subproperty;
47             $values{ $key } = shorten_length_value( $values[1] );
48             }
49             }
50             when ( 2 ) {
51             # top, left/right and bottom shorthand
52             my $key = sprintf $pattern, 'top';
53             $values{ $key } = shorten_length_value( $values[0] );
54             foreach my $subproperty qw ( left right ) {
55             $key = sprintf $pattern, $subproperty;
56             $values{ $key } = shorten_length_value( $values[1] );
57             }
58             $key = sprintf $pattern, 'bottom';
59             $values{ $key } = shorten_length_value( $values[2] );
60             }
61             when ( 3 ) {
62             # top, right, bottom and left shorthand
63             my $key = sprintf $pattern, 'top';
64             $values{ $key } = shorten_length_value( $values[0] );
65             $key = sprintf $pattern, 'right';
66             $values{ $key } = shorten_length_value( $values[1] );
67             $key = sprintf $pattern, 'bottom';
68             $values{ $key } = shorten_length_value( $values[2] );
69             $key = sprintf $pattern, 'left';
70             $values{ $key } = shorten_length_value( $values[3] );
71             }
72             }
73            
74             return %values;
75             }
76             sub collapse_trbl_shorthand {
77             my $pattern = shift;
78             my $block = shift;
79            
80             my %values;
81             foreach my $direction qw( top right bottom left ) {
82             my $key = sprintf $pattern, $direction;
83             my $value = $block->{ $key };
84             $values{ $value }++;
85             }
86            
87             my @values;
88             my $key = sprintf $pattern, 'top';
89             my $top = $block->{ $key };
90             $key = sprintf $pattern, 'right';
91             my $right = $block->{ $key };
92             $key = sprintf $pattern, 'bottom';
93             my $bottom = $block->{ $key };
94             $key = sprintf $pattern, 'left';
95             my $left = $block->{ $key };
96             my $two_values = $top ne $right;
97             my $three_values = $top ne $bottom;
98             my $four_values = $left ne $right;
99            
100             push @values, $top;
101             push @values, $right
102             if $two_values or $three_values or $four_values;
103             push @values, $bottom
104             if $three_values or $four_values;
105             push @values, $left
106             if $four_values;
107            
108             my $value = join ' ', @values;
109            
110             return( $value, scalar keys %values );
111             }
112              
113             sub expand_clip {
114             my $value = shift;
115            
116             my %values;
117             my $get_clip_values = qr{
118             ^
119             rect \( \s*
120             ( $length_value | auto ) \s* \, \s*
121             ( $length_value | auto ) \s* \, \s*
122             ( $length_value | auto ) \s* \, \s*
123             ( $length_value | auto ) \s*
124             \)
125             $
126             }x;
127            
128             if ( $value =~ $get_clip_values ) {
129             $values{'clip-rect-top'} = $1;
130             $values{'clip-rect-right'} = $2;
131             $values{'clip-rect-bottom'} = $3;
132             $values{'clip-rect-left'} = $4;
133             }
134            
135             return %values;
136             }
137              
138             sub shorten_colour_value {
139             my $value = shift;
140            
141             return unless defined $value;
142            
143             # try to collapse to shortest value
144             $value = colour_rgb_to_hex( $value );
145             $value = colour_shorten_hex( $value );
146             $value = colour_keyword_to_hex( $value );
147             $value = colour_hex_to_keyword( $value );
148            
149             $value = lc $value;
150            
151             return $value;
152             }
153             sub colour_keyword_to_hex {
154             my $value = shift;
155            
156             my %keywords = (
157             yellow => '#ff0',
158             fuchsia => '#f0f',
159             white => '#fff',
160             black => '#000',
161             );
162             if ( defined $keywords{ $value } ) {
163             return $keywords{ $value };
164             }
165            
166             return $value;
167             }
168             sub colour_hex_to_keyword {
169             my $value = shift;
170            
171             my %values = (
172             '#800000' => 'maroon',
173             '#f00' => 'red',
174             '#ffa500' => 'orange',
175             '#808000' => 'olive',
176             '#800080' => 'purple',
177             '#008000' => 'green',
178             '#000080' => 'navy',
179             '#008080' => 'teal',
180             '#c0c0c0' => 'silver',
181             '#808080' => 'gray',
182             );
183             if ( defined $values{ $value } ) {
184             return $values{ $value };
185             }
186            
187             return $value;
188             }
189             sub colour_shorten_hex {
190             my $value = shift;
191            
192             if ( $value =~ m{^ \# (.)\1 (.)\2 (.)\3 }x ) {
193             $value = "#$1$2$3";
194             }
195            
196             return $value;
197             }
198             sub colour_rgb_to_hex {
199             my $value = shift;
200            
201             my $extract_rgb_values = qr{
202             ^ rgb\( \s*
203             (\w+)(%?) \, \s*
204             (\w+)(%?) \, \s*
205             (\w+)(%?)
206             }x;
207             if ( $value =~ $extract_rgb_values ) {
208             my $red = $1;
209             my $green = $3;
210             my $blue = $5;
211            
212             $red = ( $red * 255 ) / 100
213             if $4;
214             $green = ( $green * 255 ) / 100
215             if $5;
216             $blue = ( $blue * 255 ) / 100
217             if $6;
218            
219             $value = sprintf '#%02x%02x%02x', $red, $green, $blue;
220             }
221            
222             return $value;
223             }
224              
225             sub shorten_length_value {
226             my $value = shift;
227            
228             $value = '0'
229             if $value =~ m{^0([ceimnptx]{2})};
230            
231             return $value;
232             }
233             sub shorten_url_value {
234             my $value = shift;
235             my $location = shift // undef;
236             my $self = shift // undef;
237            
238             return
239             unless defined $value;
240            
241             # CSS2.1 4.3.4: "The format of a URI value is ’url(’ followed by
242             # optional white space followed by an optional single quote (’)
243             # or double quote (")..."
244             if ( $value =~ m{ (.*) url\( \s* ['"]? (.*?) ['"]? \s* \) (.*) }x ) {
245             my $url = $2;
246            
247             if ( defined $location && defined $self ) {
248             $url = $self->copy_file_to_staging( $url, $location )
249             if $self->assets_output;
250             }
251            
252             $value = "${1}url($url)${3}";
253             }
254            
255             return $value;
256             }
257              
258             sub validate_any_order_shorthand {
259             my $value = shift;
260             my %types = @_;
261            
262             # prepare the return value hash
263             my %return;
264             foreach my $type ( keys %types ) {
265             $return{ $type } = '';
266             }
267            
268             my $options_string = join '|', values %types;
269             my $shorthand_option = qr{ ( $options_string ) \s* }x;
270             my $count = scalar keys %types;
271             my $shorthand_properties = qr{ ^ (?: $shorthand_option ){1,$count} $}x;
272            
273             if ( $value =~ m{$shorthand_properties} ) {
274             my %properties;
275            
276             # pull each property out of the shorthand string
277             # and determine which type(s) it is
278             while ( $value =~ s{^$shorthand_option}{}x ) {
279             my $property = $1;
280             foreach my $type ( keys %types ) {
281             my $check = $types{ $type };
282             $properties{ $property }{ $type } = 1
283             if $property =~ m{^$check$};
284             }
285             }
286             return if length $value;
287            
288             # sort with the lowest matches first, to ensure properties that could
289             # be multiples are resolved correctly eg. in "list-style: none
290             # url(dot.gif)" the "none" part is either a list-style-type or
291             # list-style-image property, but "url(dot.gif)" can only be a
292             # list-style-image property. By removing list-style-image from the
293             # available options of "none" first, we make sure that that is
294             # correctly resolved as being a list-style-type property.
295             my $lowest_children_first = sub {
296             my $a_count = scalar keys %{$properties{$a}};
297             my $b_count = scalar keys %{$properties{$b}};
298             return $a_count <=> $b_count;
299             };
300            
301             my @properties = sort $lowest_children_first keys %properties;
302             foreach my $property ( @properties ) {
303             # without at least one remaining type, its an invalid shorthand
304             my @types = sort keys %{$properties{ $property }};
305             my $type = shift @types;
306             return unless defined $type;
307            
308             # set the type and remove other possibilities
309             $return { $type } = $property;
310             delete $properties{ $property };
311            
312             my @others = keys %properties;
313             foreach my $property ( @others ) {
314             delete $properties{ $property }{ $type };
315             delete $properties{ $property }
316             unless scalar keys %{$properties{ $property }};
317             }
318             }
319            
320             # if anything remains unallocated to a property, then we have an
321             # invalid shorthand
322             return if scalar keys %properties;
323             }
324            
325             return %return;
326             }
327              
328             sub get_corner_values {
329             my $block = shift;
330             my $type = shift // 'css3';
331            
332             my @horizontal;
333             my @vertical;
334            
335             my $get_border_radius_corner_value = qr{
336             ( $individual_border_radius_value )
337             (?: \s+ ( $individual_border_radius_value ) )?
338             }x;
339            
340             foreach my $corner ( @standard_corners ) {
341             my $moz_corner = $corner;
342             $moz_corner =~ s{-}{};
343            
344             my $key = 'css3' eq $type ? "border-${corner}-radius"
345             : 'moz' eq $type ? "-moz-border-radius-${moz_corner}"
346             : "-webkit-border-${corner}-radius";
347             my $value = $block->{ $key };
348            
349             $value =~ m{ $get_border_radius_corner_value }x;
350             push @horizontal, $1;
351             push @vertical, $2;
352             }
353            
354             return( \@horizontal, \@vertical );
355             }
356             sub expand_corner_values {
357             my @values = @_;
358            
359             my @return;
360             if ( 1 == scalar @values ) {
361             push @return, $values[0];
362             push @return, $values[0];
363             push @return, $values[0];
364             push @return, $values[0];
365             }
366             elsif ( 2 == scalar @values ) {
367             push @return, $values[0];
368             push @return, $values[1];
369             push @return, $values[0];
370             push @return, $values[1];
371             }
372             elsif ( 3 == scalar @values ) {
373             push @return, $values[0];
374             push @return, $values[1];
375             push @return, $values[2];
376             push @return, $values[1];
377             }
378             else {
379             push @return, @values;
380             }
381            
382             return @return;
383             }
384             sub collapse_corner_values {
385             my $top_left = shift // '';
386             my $top_right = shift // '';
387             my $bottom_right = shift // '';
388             my $bottom_left = shift // '';
389            
390             my $two_values = $top_left ne $top_right;
391             my $three_values = $top_left ne $bottom_right;
392             my $four_values = $top_right ne $bottom_left;
393             my @values;
394            
395             push @values, $top_left;
396             push @values, $top_right
397             if $two_values or $three_values or $four_values;
398             push @values, $bottom_right
399             if $three_values or $four_values;
400             push @values, $bottom_left
401             if $four_values;
402            
403             return join ' ', @values;
404             }
405              
406             1;