Pārlūkot izejas kodu

Make favicon_mongler produce a real .ico, and ship the files we reference

Three things were wrong here, and they compounded.

inkscape only exports PNG.  Asking it for '32 => ico' got us a PNG called
favicon-32.ico -- the committed one starts 89504e47, not 00000100.  Sizes are
now all rendered as PNG, and the container is assembled afterwards with
Imager, which gives a genuine multi-image ICO holding 16, 32 and 48.  Each is
rendered from the SVG rather than downscaled off one big raster; at 16 pixels
that is the whole ballgame.  16 is only ever an ico member, so it goes to a
File::Temp scratch dir instead of being left lying around.

Trog::Routes::JSON::webmanifest has been pointing at favicon-32.png since it
was written, and nothing ever generated one -- that entry 404'd.  It is in
@png_sizes now, and the file is committed.

/favicon.ico 404'd too, for a stranger reason.  The bare 'favicon.ico' line in
.gitignore comes from 6a34784, back in the index.php days, and exists so an
operator can drop their own favicon at the repo root.  Unanchored, it also
swallows the default we ship in www/img/icon, so the file the icon() route
serves could never be committed.  Anchored to /favicon.ico it does what it was
written to do, and the default is tracked.

favicon-32.ico is deleted; it was referenced by nothing and only existed to be
copied over favicon.ico.

Note the two new assets are downscaled from the committed favicon-512.png, not
re-rendered -- there is no rasterizer on this box.  Re-running the script with
inkscape replaces them with natively rendered ones.

Makefile.PL picks up Imager, Imager::File::ICO, Imager::File::PNG and
File::Temp.  Imager was only ever an implicit dep via Imager::QRCode, and the
format handlers are separate distributions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ2NAMQyytncwHKSmD5dFe
George Baugh 2 mēneši atpakaļ
vecāks
revīzija
c4ba786024

+ 3 - 1
.gitignore

@@ -1,6 +1,8 @@
 *.swp
 *.swo
-favicon.ico
+# A site operator dropping their own favicon at the repo root -- 6a34784.
+# Anchored, or it also swallows the default we ship in www/img/icon.
+/favicon.ico
 dist/
 www/assets
 data/

+ 4 - 0
Makefile.PL

@@ -38,10 +38,14 @@ WriteMakefile(
     'File::Touch'            => '0',
     'File::Path'             => '0',
     'File::Which'            => '0',
+    'File::Temp'             => '0',
     'HTML::SocialMeta'       => '0',
     'HTTP::Body'             => '0',
     'IO::String'             => '0',
     'IO::Compress::Gzip'     => '0',
+    'Imager'                 => '0',
+    'Imager::File::ICO'      => '0',
+    'Imager::File::PNG'      => '0',
     'Imager::QRCode'         => '0',
     'JSON::MaybeXS'          => '0',
     'JSON::Validator::Schema::Troglodyne' => '0',

+ 50 - 20
bin/favicon_mongler.pl

@@ -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;

BIN
www/img/icon/favicon-32.ico


BIN
www/img/icon/favicon-32.png


BIN
www/img/icon/favicon.ico