Files
abrown38andClaude Opus 5.5 1afed741bf Use the JARL wordmark in the header and as the README banner
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>
2026-10-08 18:09:41 +00:00

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())