| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- #!/usr/bin/env perl
- use 5.040;
- use re '/aasx';
- use warnings FATAL => 'all';
- use Cwd ();
- use File::Basename ();
- use File::Which ();
- use File::Temp ();
- use List::Util qw{uniq};
- use Imager ();
- =head1 SYNOPSIS
- Render a favicon SVG out to every size and format a browser might ask for.
- =head2 USAGE
- bin/favicon_mongler.pl /path/to/favicon.svg
- 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 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
- Needs inkscape on the PATH to do the rendering, and dies if it isn't there.
- Works with either era of inkscape. 1.0 reworked the command line and renamed
- the export flag from -e to -o, and 1.x rejects -e outright, so the version is
- read off the binary rather than assumed -- see export_flag().
- 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
- # Inkscape 1.0 reworked the command line: the export flag went from -e to -o,
- # and 1.x errors out on -e rather than quietly accepting it. Ask the binary
- # which era it belongs to instead of guessing -- this script has already been
- # flipped between the two by hand twice (e23b678, then back in 5f0cdca).
- my sub export_flag ($bin) {
- # Shelling out, on purpose and on the record. system() cannot hand back
- # what the child printed, and reaching for a pipe open instead would only
- # be picking the spelling ProhibitShellDispatch happens not to look at --
- # which is what Troglodyne::ProhibitPipeOpen now exists to stop. $bin is
- # quoted because File::Which returns whatever path it found, spaces and all.
- my $version = qx{"$bin" --version}; ## no critic (logicLAB::ProhibitShellDispatch)
- my ($major) = ( $version // '' ) =~ m/Inkscape\s+(\d+)/;
- die "Could not read a version out of `$bin --version`, got: " . ( $version // '(nothing)' ) unless defined $major;
- return $major >= 1 ? '-o' : '-e';
- }
- die "Usage:\n favicon_mongler.pl /path/to/favicon.svg" unless $ARGV[0];
- my $icon = Cwd::abs_path( $ARGV[0] );
- 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 $export = export_flag($bin);
- # 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, $export, $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 $out ***\n\n";
- $rendered{$size} = $out;
- }
- # 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;
|