The logo was an 880 KB SVG wrapping a 1254px PNG, downloaded again on every page load. Keep that PNG as the master (artwork/logo.png) and have scripts/logo.py trace it with potrace into a 6 KB logo.svg (favicon, README) and logo.js, which brand.js draws inline in the header and on the login page so the logo arrives with the page. Remove the unused logo-white.svg. Static files now carry content ETags, so reloads get 304s instead of downloading the UI again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
3.3 KiB
Python
Executable File
73 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Builds the web UI's logo from the master image, artwork/logo.png (a blue
|
|
# and white figure on a transparent background), by tracing it into vector
|
|
# paths with potrace:
|
|
#
|
|
# internal/web/static/assets/logo.svg the favicon, and the README's logo
|
|
# internal/web/static/assets/logo.js the same paths for the web UI, which
|
|
# draws them inline (brand.js), so the
|
|
# logo appears together with the page
|
|
#
|
|
# Both are a few KB, where the PNG is 650 KB. Needs potrace and ImageMagick
|
|
# (convert). Run from the repository root after changing the master:
|
|
#
|
|
# python3 scripts/logo.py
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
MASTER = "artwork/logo.png"
|
|
OUT = "internal/web/static/assets"
|
|
BLUE = "#1d5287" # the master's blue
|
|
WHITE = "#fff"
|
|
PAD = 6 # pixels of the master around the figure
|
|
|
|
|
|
def run(*args):
|
|
return subprocess.run(args, check=True, capture_output=True, text=True).stdout
|
|
|
|
|
|
def trace(pbm):
|
|
"""Traces a bitmap into one SVG path (in the master's pixels, y up)."""
|
|
svg = run("potrace", "-b", "svg", "--flat", "-t", "4", "-a", "1.0", "-O", "0.4", "-u", "1", pbm, "-o", "-")
|
|
return " ".join(re.search(r'<path d="([^"]*)"', svg, re.S).group(1).split())
|
|
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
base, white = os.path.join(tmp, "base.pbm"), os.path.join(tmp, "white.pbm")
|
|
# The figure's whole shape (opaque pixels), drawn in blue...
|
|
run("convert", MASTER, "-alpha", "extract", "-threshold", "50%", "-negate", base)
|
|
# ...and its white parts on top; the blue lines show between them.
|
|
run("convert", MASTER, "-background", "black", "-alpha", "remove", "-alpha", "off",
|
|
"-colorspace", "gray", "-threshold", "75%", "-negate", white)
|
|
layers = [(BLUE, trace(base)), (WHITE, trace(white))]
|
|
height = int(run("identify", "-format", "%h", MASTER))
|
|
w, h, x, y = map(int, re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)",
|
|
run("convert", MASTER, "-alpha", "extract", "-threshold", "50%", "-format", "%@", "info:")).groups())
|
|
side = max(w, h) + 2 * PAD
|
|
view_box = f"{x + w / 2 - side / 2:g} {y + h / 2 - side / 2:g} {side:g} {side:g}"
|
|
transform = f"translate(0 {height}) scale(1 -1)"
|
|
|
|
paths = "".join(f'<path fill="{fill}" d="{d}"/>' for fill, d in layers)
|
|
with open(os.path.join(OUT, "logo.svg"), "w") as f:
|
|
f.write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{view_box}">'
|
|
f'<!-- Generated by scripts/logo.py from {MASTER}. -->'
|
|
f'<g transform="{transform}">{paths}</g></svg>\n')
|
|
with open(os.path.join(OUT, "logo.js"), "w") as f:
|
|
f.write(f"// The Jarl logo, traced from {MASTER} by scripts/logo.py; don't edit.\n"
|
|
f"// brand.js draws it; logo.svg holds the same paths.\n"
|
|
f'export const viewBox = "{view_box}";\n'
|
|
f'export const transform = "{transform}";\n'
|
|
"export const layers = [\n"
|
|
+ "".join(f' ["{fill}", "{d}"],\n' for fill, d in layers)
|
|
+ "];\n")
|
|
for name in ("logo.svg", "logo.js"):
|
|
print(f"{OUT}/{name}: {os.path.getsize(os.path.join(OUT, name))} bytes")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|