File Coverage

blib/lib/Plack/Middleware/Favicon.pm
Criterion Covered Total %
statement 21 56 37.5
branch 0 24 0.0
condition 0 5 0.0
subroutine 7 11 63.6
pod 2 2 100.0
total 30 98 30.6


line stmt bran cond sub pod time code
1             package Plack::Middleware::Favicon;
2 4     4   270682 use strict;
  4         8  
  4         124  
3 4     4   16 use warnings;
  4         6  
  4         120  
4 4     4   15 use Carp qw/croak/;
  4         9  
  4         225  
5 4     4   472 use parent 'Plack::Middleware';
  4         279  
  4         24  
6 4     4   28255 use Imager;
  4         29763  
  4         24  
7 4     4   2271 use HTTP::Date qw/time2str/;
  4         12791  
  4         243  
8 4         23 use Plack::Util::Accessor qw/
9             src_image_file
10             src_image_obj
11             cache
12             custom_favicons
13             callback
14 4     4   23 /;
  4         5  
15              
16             our $VERSION = '0.05';
17              
18             our @DEFAULT_FAVICONS = map {
19             $_->{type} ||= 'png';
20             $_->{mime_type} ||= 'image/png';
21             $_;
22             } (
23             {
24             cond => sub {
25             my ($self, $env) = @_;
26             $env->{HTTP_USER_AGENT}
27             and $env->{HTTP_USER_AGENT} =~ m!MSIE (?:[1-9]|10)\.!;
28             },
29             path => qr!^/favicon\.ico!, size => [16, 16],
30             type => 'ico', mime_type => 'image/x-icon',
31             },
32             { path => qr!^/favicon\.ico!, size => [16, 16], mime_type => 'image/x-icon', },
33             { path => qr!^/favicon-16x16\.png!, size => [16, 16], },
34             { path => qr!^/favicon-32x32\.png!, size => [32, 32], },
35             { path => qr!^/favicon-96x96\.png!, size => [96, 96], },
36             { path => qr!^/favicon-160x160\.png!, size => [160, 160], },
37             { path => qr!^/favicon-196x196\.png!, size => [196, 196], },
38             { path => qr!^/mstile-70x70\.png!, size => [70, 70], },
39             { path => qr!^/mstile-144x144\.png!, size => [144, 144], },
40             { path => qr!^/mstile-150x150\.png!, size => [150, 150], },
41             { path => qr!^/mstile-310x310\.png!, size => [310, 310], },
42             { path => qr!^/mstile-310x150\.png!, size => [310, 150], },
43             { path => qr!^/apple-touch-icon-57x57\.png!, size => [57, 57], },
44             { path => qr!^/apple-touch-icon-60x60\.png!, size => [60, 60], },
45             { path => qr!^/apple-touch-icon-72x72\.png!, size => [72, 72], },
46             { path => qr!^/apple-touch-icon-76x76\.png!, size => [76, 76], },
47             { path => qr!^/apple-touch-icon-114x114\.png!, size => [114, 114], },
48             { path => qr!^/apple-touch-icon-120x120\.png!, size => [120, 120], },
49             { path => qr!^/apple-touch-icon-144x144\.png!, size => [144, 144], },
50             { path => qr!^/apple-touch-icon-152x152\.png!, size => [152, 152], },
51             { path => qr!^/apple-touch-icon\.png!, size => [57, 57], },
52             { path => qr!^/apple-touch-icon-precomposed\.png!, size => [57, 57], },
53             );
54              
55             sub prepare_app {
56 0     0 1   my $self = shift;
57              
58 0 0         croak "required 'src_image_file'"
59             unless $self->src_image_file;
60 0 0         croak "not found or not a file:". $self->src_image_file
61             unless -f $self->src_image_file;
62              
63 0           my $imager = Imager->new(file => $self->src_image_file);
64 0 0         unless ($imager) {
65 0   0       croak sprintf(
66             "could not create object from %s. %s",
67             $self->src_image_file,
68             Imager->errstr || '',
69             );
70             }
71 0           $self->src_image_obj($imager);
72              
73 0 0         if ($self->cache) {
74 0 0         for my $f (@{$self->custom_favicons || []}, @DEFAULT_FAVICONS) {
  0            
75 0           $self->_generate($f);
76             }
77             }
78             }
79              
80             sub call {
81 0     0 1   my ($self, $env) = @_;
82              
83 0 0         for my $f (@{$self->custom_favicons || []}, @DEFAULT_FAVICONS) {
  0            
84 0 0 0       next if $f->{cond} && !$f->{cond}->($self, $env);
85 0 0         if ($env->{PATH_INFO} =~ m!$f->{path}!) {
86 0           my $content = $self->_generate($f);
87             return [
88 0           200,
89             [
90             'Content-Type' => $f->{mime_type},
91             'Content-Length' => length $content,
92             'Last-Modified' => time2str(time),
93             ],
94             [$content],
95             ];
96             }
97             }
98              
99 0           $self->app->($env);
100             }
101              
102             sub _generate {
103 0     0     my ($self, $f) = @_;
104              
105 0 0         if ($self->cache) {
106 0 0         if ( my $cache = $self->cache->get($self->_ckey($f)) ) {
107 0           return $cache;
108             }
109             }
110              
111 0           my ($x, $y) = @{ $f->{size} };
  0            
112              
113 0           my $img = $self->src_image_obj->scale(
114             xpixels => $x,
115             ypixels => $y,
116             type => 'nonprop',
117             );
118              
119 0 0         if ($self->callback) {
120 0           $img = $self->callback->($self, $f, $img);
121             }
122              
123 0           my $favicon = '';
124 0           $img->write(
125             data => \$favicon,
126             type => $f->{type},
127             );
128              
129 0 0         if ($self->cache) {
130 0           $self->cache->set($self->_ckey($f) => $favicon);
131             }
132              
133 0           return $favicon;
134             }
135              
136 0     0     sub _ckey { join ':', @{$_[1]->{size}}, $_[1]->{type}; }
  0            
137              
138             1;
139              
140             __END__