|
|
@@ -6,7 +6,10 @@ use re '/aa';
|
|
|
use Cwd ();
|
|
|
use File::Basename ();
|
|
|
use File::Which ();
|
|
|
-use File::Copy ();
|
|
|
+use File::Temp ();
|
|
|
+use List::Util qw{uniq};
|
|
|
+
|
|
|
+use Imager ();
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
@@ -16,13 +19,15 @@ Render a favicon SVG out to every size and format a browser might ask for.
|
|
|
|
|
|
bin/favicon_mongler.pl /path/to/favicon.svg
|
|
|
|
|
|
-Writes favicon-<size>.<ext> next to the source SVG for each size below, and
|
|
|
-copies the 32x32 .ico to favicon.ico, which is what browsers guess at when a
|
|
|
-page doesn't tell them otherwise.
|
|
|
+Writes favicon-<size>.png next to the source SVG for each size below, plus a
|
|
|
+favicon.ico, which is what browsers guess at when a page doesn't tell them
|
|
|
+otherwise.
|
|
|
|
|
|
-The sizes are the ones which actually get asked for: 32 for the tab, 48 for the
|
|
|
-desktop shortcut, 167 and 180 for iPads and iPhones, and 192 and 512 for the
|
|
|
-web app manifest.
|
|
|
+The PNG sizes are the ones which actually get asked for: 32 and 48 for the tab
|
|
|
+and the desktop shortcut, 167 and 180 for iPads and iPhones, and 192 and 512
|
|
|
+for the web app manifest. Everything but the .ico is named in either
|
|
|
+components/header.tx or Trog::Routes::JSON::webmanifest, so adding a size here
|
|
|
+means adding it there too, and vice versa.
|
|
|
|
|
|
=head2 CAVEATS
|
|
|
|
|
|
@@ -30,6 +35,10 @@ Needs inkscape on the PATH to do the rendering, and dies if it isn't there.
|
|
|
|
|
|
Overwrites whatever is already at those paths without asking.
|
|
|
|
|
|
+Note that inkscape only ever exports PNG -- it has no idea what an .ico is.
|
|
|
+Asking it for one, as this script used to, gets you a PNG with the wrong
|
|
|
+extension. The real container is assembled with Imager afterwards.
|
|
|
+
|
|
|
=cut
|
|
|
|
|
|
die "Usage:\n favicon_mongler.pl /path/to/favicon.svg" unless $ARGV[0];
|
|
|
@@ -38,25 +47,46 @@ my $bin = File::Which::which('inkscape');
|
|
|
die "Please install inkscape" if !$bin;
|
|
|
my $dir = File::Basename::dirname($icon) || die "Can't figure out dir from $icon";
|
|
|
|
|
|
-my %files = (
|
|
|
- 32 => 'ico',
|
|
|
- 48 => 'png',
|
|
|
- 167 => 'png',
|
|
|
- 180 => 'png',
|
|
|
- 192 => 'png',
|
|
|
- 512 => 'png',
|
|
|
-);
|
|
|
-foreach my $size ( sort { $b <=> $a } keys(%files) ) {
|
|
|
- print "*** Generating ${size}x${size} .$files{$size} now... ***\n";
|
|
|
- my @cmd = ( $bin, '-w', $size, '-h', $size, $icon, '-e', "$dir/favicon-$size.$files{$size}" );
|
|
|
+# The sizes the site links to directly.
|
|
|
+my @png_sizes = qw{32 48 167 180 192 512};
|
|
|
+
|
|
|
+# The sizes that go inside favicon.ico. 16 is not linked anywhere on its own,
|
|
|
+# so it only ever exists as a member of the container.
|
|
|
+my @ico_sizes = qw{16 32 48};
|
|
|
+
|
|
|
+# Somewhere to put the sizes we need but don't want to leave lying around.
|
|
|
+my $scratch = File::Temp->newdir();
|
|
|
+
|
|
|
+# Render each size from the vector source rather than downscaling one big
|
|
|
+# raster -- at 16 and 32 pixels the difference is the whole ballgame.
|
|
|
+my %rendered;
|
|
|
+foreach my $size ( sort { $b <=> $a } uniq( @png_sizes, @ico_sizes ) ) {
|
|
|
+ my $keep = grep { $_ == $size } @png_sizes;
|
|
|
+ my $out = $keep ? "$dir/favicon-$size.png" : "$scratch/favicon-$size.png";
|
|
|
+
|
|
|
+ print "*** Generating ${size}x${size} .png now... ***\n";
|
|
|
+ my @cmd = ( $bin, '-w', $size, '-h', $size, $icon, '-e', $out );
|
|
|
|
|
|
# There is no maintained Perl SVG rasterizer to bind instead -- Imager has
|
|
|
# no SVG reader, and Image::LibRSVG was last released in 2006 -- so driving
|
|
|
# the renderer as a subprocess is the only option here.
|
|
|
system(@cmd) and die "Failed to run @cmd: " . ( $? == -1 ? $! : 'exit status ' . ( $? >> 8 ) ); ## no critic (logicLAB::ProhibitShellDispatch)
|
|
|
- print "*** Wrote $dir/favicon-$size.$files{$size} ***\n\n";
|
|
|
+ print "*** Wrote $out ***\n\n";
|
|
|
+
|
|
|
+ $rendered{$size} = $out;
|
|
|
}
|
|
|
|
|
|
-File::Copy::copy( "$dir/favicon-32.ico", "$dir/favicon.ico" );
|
|
|
+# An .ico is a container of several images, and browsers do pick the size they
|
|
|
+# want out of it, so give them all three rather than one scaled at display time.
|
|
|
+print "*** Generating favicon.ico (@{[ join 'x, ', @ico_sizes ]}x) now... ***\n";
|
|
|
+my @members = map {
|
|
|
+ my $img = Imager->new;
|
|
|
+ $img->read( file => $rendered{$_} ) or die "Could not read $rendered{$_}: " . $img->errstr;
|
|
|
+ $img;
|
|
|
+} @ico_sizes;
|
|
|
+
|
|
|
+Imager->write_multi( { file => "$dir/favicon.ico", type => 'ico' }, @members )
|
|
|
+ or die "Could not write $dir/favicon.ico: " . Imager->errstr;
|
|
|
+print "*** Wrote $dir/favicon.ico ***\n";
|
|
|
|
|
|
0;
|