#!/usr/bin/env python3 # Builds artwork/banner.svg, the README's banner: the logo above the "JARL" # wordmark, laid out as on the web UI's login page. It reads both from the # web UI's own sources, so it matches the login page: # # internal/web/static/assets/logo.js the logo's paths (from scripts/logo.py) # internal/web/static/assets/brand.js the wordmark's letters and rule # # The letters and rule take the UI's text and accent colors, switching to # the dark theme's when the viewer prefers dark. Needs only Python; run from # the repository root after changing either file (scripts/logo.py runs it): # # python3 scripts/banner.py import re import sys ASSETS = "internal/web/static/assets" OUT = "artwork/banner.svg" # The UI's --text and --accent (style.css), light and dark. STYLE = (".t{fill:#1d2330}.a{fill:#2f5fb3}.l{fill:none;stroke:#2f5fb3;stroke-width:2.6}" ".d{fill:#2f5fb3;font:700 12.5px system-ui,sans-serif;letter-spacing:.5px}" "@media (prefers-color-scheme:dark){.t{fill:#e3e7ee}.a,.d{fill:#6d9be8}.l{stroke:#6d9be8}}") CLASSES = {"fill": "a", "line": "l", "dns": "d"} # The login page's sizes in pixels (style.css): a 104px logo, 10px above a # 220px wordmark whose viewBox is 288 units wide. UNIT = 288 / 220 LOGO = round(104 * UNIT, 1) GAP = round(10 * UNIT, 1) def attrs(src): """Turns a JS object literal's body ({ cx: 106, "text-anchor": "middle" }) into SVG attributes.""" out = [] for key, val in re.findall(r'"?([\w-]+)"?:\s*("[^"]*"|[\d.]+)', src): val = val.strip('"') out.append(f'class="{CLASSES[val]}"' if key == "class" else f'{key}="{val}"') return " ".join(out) def main(): logo = open(f"{ASSETS}/logo.js").read() view_box = re.search(r'viewBox = "([^"]*)"', logo).group(1) transform = re.search(r'transform = "([^"]*)"', logo).group(1) layers = re.findall(r'\["(#\w+)", "([^"]*)"\]', logo) brand = open(f"{ASSETS}/brand.js").read() letters = re.findall(r'\{ x: (\d+), d: "([^"]*)" \}', brand) rule = brand[brand.index("const ruleG"):] rule = rule[:rule.index(");\n")] rule_at = re.search(r'class: "wordmark-rule", transform: "([^"]*)"', rule).group(1) shapes = re.findall(r'el\("(path|circle|text)", \{ ([^}]*) \}(?:, "([^"]*)")?\)', rule) mark_box = re.search(r'viewBox: "(-4 -4 288 162)"', brand).group(1) if len(layers) < 2 or len(letters) != 4 or len(shapes) < 5: sys.exit("banner.py: logo.js or brand.js no longer looks as expected; update the script") width, height = 288, LOGO + GAP + 162 svg = [f'', f"", f"Jarl", f'' f'' + "".join(f'' for fill, d in layers) + "", f'' '' + "".join(f'' for x, d in letters) + "" f'' + "".join(f"<{tag} {attrs(a)}>{text}" if text else f"<{tag} {attrs(a)}/>" for tag, a, text in shapes) + "", ""] with open(OUT, "w") as f: f.write("\n".join(svg) + "\n") print(f"{OUT}: {width}x{height:g}") if __name__ == "__main__": sys.exit(main())