use strict;
use Carp qw/croak/;
use Imager;
use HTTP::Date qw/time2str/;
src_image_file
src_image_obj
cache
custom_favicons
/;
our $VERSION = '0.03';
our @DEFAULT_FAVICONS = map {
$_->{type} ||= 'png';
$_->{mime_type} ||= 'image/png';
$_;
} (
{
cond => sub {
my ($self, $env) = @_;
$env->{HTTP_USER_AGENT}
and $env->{HTTP_USER_AGENT} =~ m!MSIE (?:[1-9]|10)\.!;
},
path => qr!^/favicon\.ico!, size => [16, 16],
type => 'ico', mime_type => 'image/x-icon',
},
{ path => qr!^/favicon\.ico!, size => [16, 16], mime_type => 'image/x-icon', },
{ path => qr!^/favicon-16x16\.png!, size => [16, 16], },
{ path => qr!^/favicon-32x32\.png!, size => [32, 32], },
{ path => qr!^/favicon-96x96\.png!, size => [96, 96], },
{ path => qr!^/favicon-160x160\.png!, size => [160, 160], },
{ path => qr!^/favicon-196x196\.png!, size => [196, 196], },
{ path => qr!^/mstile-70x70\.png!, size => [70, 70], },
{ path => qr!^/mstile-144x144\.png!, size => [144, 144], },
{ path => qr!^/mstile-150x150\.png!, size => [150, 150], },
{ path => qr!^/mstile-310x310\.png!, size => [310, 310], },
{ path => qr!^/mstile-310x150\.png!, size => [310, 150], },
{ path => qr!^/apple-touch-icon-57x57\.png!, size => [57, 57], },
{ path => qr!^/apple-touch-icon-60x60\.png!, size => [60, 60], },
{ path => qr!^/apple-touch-icon-72x72\.png!, size => [72, 72], },
{ path => qr!^/apple-touch-icon-76x76\.png!, size => [76, 76], },
{ path => qr!^/apple-touch-icon-114x114\.png!, size => [114, 114], },
{ path => qr!^/apple-touch-icon-120x120\.png!, size => [120, 120], },
{ path => qr!^/apple-touch-icon-144x144\.png!, size => [144, 144], },
{ path => qr!^/apple-touch-icon-152x152\.png!, size => [152, 152], },
{ path => qr!^/apple-touch-icon\.png!, size => [57, 57], },
{ path => qr!^/apple-touch-icon-precomposed\.png!, size => [57, 57], },
);
sub prepare_app {
my $self = shift;
croak "required 'src_image_file'"
unless $self->src_image_file;
croak "not found or not a file:". $self->src_image_file
unless -f $self->src_image_file;
my $imager = Imager->new(file => $self->src_image_file);
unless ($imager) {
croak sprintf(
"could not create object from %s. %s",
$self->src_image_file,
Imager->errstr || '',
);
}
$self->src_image_obj($imager);
if ($self->cache) {
for my $f (@{$self->custom_favicons || []}, @DEFAULT_FAVICONS) {
$self->_generate($f);
}
}
}
sub call {
my ($self, $env) = @_;
for my $f (@{$self->custom_favicons || []}, @DEFAULT_FAVICONS) {
next if $f->{cond} && !$f->{cond}->($self, $env);
if ($env->{PATH_INFO} =~ m!$f->{path}!) {
my $content = $self->_generate($f);
return [
200,
[
'Content-Type' => $f->{mime_type},
'Content-Length' => length $content,
'Last-Modified' => time2str(time),
],
[$content],
];
}
}
$self->app->($env);
}
sub _generate {
my ($self, $f) = @_;
if ($self->cache) {
if ( my $cache = $self->cache->get($self->_ckey($f)) ) {
return $cache;
}
}
my ($x, $y) = @{ $f->{size} };
my $favicon = '';
$self->src_image_obj->scale(
xpixels => $x,
ypixels => $y,
type => 'nonprop',
)->write(
data => \$favicon,
type => $f->{type},
);
if ($self->cache) {
$self->cache->set($self->_ckey($f) => $favicon);
}
return $favicon;
}
sub _ckey { join ':', @{$_[1]->{size}}, $_[1]->{type}; }
1;
__END__
=head1 NAME
Plack::Middleware::Favicon - deliver common favicon images
=head1 SYNOPSIS
use Plack::Builder;
builder {
enable 'Favicon',
src_image_file => 'src_favicon.png';
};
=head1 DESCRIPTION
You don't need to prepare favicon images any more if you use this module.
=head1 METHODS
=over 4
=item prepare_app
=item call
=back
=head1 REPOSITORY
Plack::Middleware::Favicon is hosted on github: L<http://github.com/bayashi/Plack-Middleware-Favicon>
I appreciate any feedback :D
=head1 AUTHOR
Dai Okabayashi E<lt>bayashi@cpan.orgE<gt>
=head1 SEE ALSO
=head1 LICENSE
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.
=cut