The header now shows the login page's wordmark letters (without the rule) instead of "Jarl" set in Cinzel, so the bundled Cinzel font, its preload and its licence stanza in the .deb's copyright file are removed: the web UI needs no web font. The README opens with the login page's banner, the logo above the wordmark (artwork/banner.svg). scripts/banner.py builds it from the web UI's logo.js and brand.js, with light and dark colors, and logo.py runs it after tracing the logo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
3.4 KiB
Python
Executable File
76 lines
3.4 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
|
|
# 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
|
|
#
|
|
# and then runs scripts/banner.py to rebuild the README's banner.
|
|
#
|
|
# 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", flush=True)
|
|
subprocess.run([sys.executable, "scripts/banner.py"], check=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|