From 86f9bda4ef716e9fb82abb8aa0cff794ff326baf Mon Sep 17 00:00:00 2001
From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com>
Date: Fri, 23 Oct 2026 19:06:10 +0200
Subject: [PATCH] chore: minify the svg logos
svgo drops the editor markup, and the PNG inside RetroDECK's icon is cut to the 80 px tile: 12.7 KB down to 5.1 KB.
---
docs_assets/logos/lakka.svg | 37 +---------------------
docs_assets/logos/retroarch.svg | 13 +-------
docs_assets/logos/retrodeck.svg | 54 +--------------------------------
scripts/fetch_site_images.py | 46 ++++++++++++++++++++++++++--
4 files changed, 47 insertions(+), 103 deletions(-)
diff --git a/docs_assets/logos/lakka.svg b/docs_assets/logos/lakka.svg
index fc89ee3c..ca13e9b3 100644
--- a/docs_assets/logos/lakka.svg
+++ b/docs_assets/logos/lakka.svg
@@ -1,36 +1 @@
-
-
+
\ No newline at end of file
diff --git a/docs_assets/logos/retroarch.svg b/docs_assets/logos/retroarch.svg
index a35b434a..a7f0e558 100644
--- a/docs_assets/logos/retroarch.svg
+++ b/docs_assets/logos/retroarch.svg
@@ -1,12 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs_assets/logos/retrodeck.svg b/docs_assets/logos/retrodeck.svg
index 185f9d63..f5e30ab2 100644
--- a/docs_assets/logos/retrodeck.svg
+++ b/docs_assets/logos/retrodeck.svg
@@ -1,53 +1 @@
-
-
+
\ No newline at end of file
diff --git a/scripts/fetch_site_images.py b/scripts/fetch_site_images.py
index 783f911c..740eff63 100644
--- a/scripts/fetch_site_images.py
+++ b/scripts/fetch_site_images.py
@@ -10,7 +10,8 @@ them. The site build only reads those copies (``sitediagrams.localize_images``).
Raster logos are normalised to an 80 px square WebP, twice the 40 px they
are shown at, and system icons to a 48 px PNG, so every tile has the same
-weight. SVG is kept as published.
+weight. SVG is minified by svgo, and a raster an SVG embeds is cut to the
+tile size.
Normalising needs Pillow. This is a maintenance tool run by hand when a
logo changes, not a build step: the build stays on the standard library
and PyYAML.
@@ -22,8 +23,11 @@ and PyYAML.
from __future__ import annotations
import argparse
+import base64
import io
import json
+import re
+import subprocess
import sys
import urllib.error
import urllib.parse
@@ -79,6 +83,44 @@ def safe_svg(data: bytes, url: str) -> bytes:
return data
+# A raster inside an SVG logo: RetroDECK's icon carried a 441 x 334 PNG,
+# 9 KiB of a mark drawn 40 px wide. It never needs more pixels than the
+# whole tile.
+EMBEDDED_RASTER = re.compile(rb'data:image/(?:png|jpeg|webp);base64,([A-Za-z0-9+/=\s]+)')
+
+
+def shrink_embedded(data: bytes) -> bytes:
+ """Each raster an SVG embeds, at most LOGO_SIZE pixels on its longest side."""
+
+ def shrink(match: re.Match[bytes]) -> bytes:
+ try:
+ from PIL import Image
+ except ImportError as exc:
+ raise SystemExit("Pillow is needed to normalise raster images: pip install Pillow") from exc
+ image = Image.open(io.BytesIO(base64.b64decode(re.sub(rb"\s", b"", match.group(1))))).convert("RGBA")
+ if max(image.size) <= LOGO_SIZE:
+ return match.group(0)
+ image.thumbnail((LOGO_SIZE, LOGO_SIZE), Image.LANCZOS)
+ out = io.BytesIO()
+ image.save(out, "PNG", optimize=True)
+ return b"data:image/png;base64," + base64.b64encode(out.getvalue())
+
+ return EMBEDDED_RASTER.sub(shrink, data)
+
+
+# Editor markup (Inkscape's namedview, sodipodi attributes, metadata) was
+# most of the bytes of two logos. svgo comes with the site's packages.
+SVGO = ROOT / "website" / "node_modules" / ".bin" / "svgo"
+
+
+def minify_svg(data: bytes) -> bytes:
+ if not SVGO.is_file():
+ raise SystemExit("svgo is needed to minify SVG logos: npm ci --prefix website")
+ result = subprocess.run([str(SVGO), "--multipass", "-i", "-", "-o", "-"],
+ input=data, capture_output=True, check=True)
+ return result.stdout
+
+
def write_manifest(directory: Path, note: str, entries: list[dict]) -> None:
entries.sort(key=lambda entry: entry["file"])
document = {"note": note, "images": entries}
@@ -101,7 +143,7 @@ def fetch_logos() -> None:
download = f"{url}?s={LOGO_SIZE * 2}" if AVATAR_HOST in url else url
data = fetch(download)
if is_svg(data):
- file_name, payload = f"{name}.svg", safe_svg(data, url)
+ file_name, payload = f"{name}.svg", minify_svg(shrink_embedded(safe_svg(data, url)))
else:
file_name, payload = f"{name}.webp", square_image(data, LOGO_SIZE, "WEBP")
for stale in LOGO_DIR.glob(f"{name}.*"):