#!/usr/bin/env python3
"""Generate the ihasvpn brand set from one drawing.
Writes docs/brand/* and the favicons and app icons under web/public. The same
drawing is inlined in web/src/components/Mark.tsx; change both together.
Needs rsvg-convert, ImageMagick 7 (`magick`) and the Fira Sans font, the face
the ihasmail.org cards use. Run from anywhere:
python3 docs/brand/generate.py
"""
import atexit, os, shutil, subprocess, tempfile
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
OUT = os.path.join(REPO, "docs/brand")
PUB = os.path.join(REPO, "web/public")
TMP = tempfile.mkdtemp(prefix="ihasvpn-brand-")
atexit.register(shutil.rmtree, TMP, ignore_errors=True)
NAVY, TEAL, ORANGE, EAR = "#17404f", "#46cac3", "#f9a34b", "#ef7f2f"
SHIELD = "M64 6 L114 22 V60 C114 90 93 112 64 122 C35 112 14 90 14 60 V22 Z"
HEAD = ("M31 64 C30 50 31 38 34 27 Q36 20 42 23 L55 32 Q64 29.5 73 32 L86 23 "
"Q92 20 94 27 C97 38 98 50 97 64 C96 83 82 92 64 92 C46 92 32 83 31 64 Z")
HEAD_T = "translate(64 62) scale(0.9) translate(-64 -60)"
EARS = "M38 30 L50 37.5 L40 45.5 Z M90 30 L78 37.5 L88 45.5 Z"
EYES = "M44 60 Q49.5 53 55 60 M73 60 Q78.5 53 84 60"
NOSE = "M60.8 65.5 h6.4 l-3.2 3.6 z"
MOUTH = "M56 71.5 Q60 76.5 64 71.5 Q68 76.5 72 71.5"
WHISKERS = "M14 58 L30 60 M15 67 L30 65 M114 58 L98 60 M113 67 L98 65"
LEDGE = "M6 88 Q64 81 122 88"
PAW_L = "M36.5 90 C36 81 40 76.5 46 76.5 C52 76.5 56 81 55.5 90 C55.5 94 51 96 46 96 C41 96 36.5 94 36.5 90 Z"
PAW_R = "M91.5 90 C92 81 88 76.5 82 76.5 C76 76.5 72 81 72.5 90 C72.5 94 77 96 82 96 C87 96 91.5 94 91.5 90 Z"
TOES = "M43 88.5 V93 M49 88.5 V93 M79 88.5 V93 M85 88.5 V93"
def mark_body(pfx):
"""The colour mark's drawing, ids prefixed so several can share a page."""
return f'''
'''
def svg(body, w=128, h=128, vb="0 0 128 128", label="ihasvpn", comment=""):
c = f"\n " if comment else ""
return f'\n'
MARK = svg(mark_body("ihasvpn"), comment="ihasvpn: the ihasmail cat peeking over the edge of a shield")
MONO = svg(f'''
''',
comment="single-colour version: inherits currentColor, for print, status bars and anywhere one ink")
def write(path, text):
with open(path, "w") as f:
f.write(text)
def render(src, dst, w, h=None):
subprocess.run(["rsvg-convert", "-w", str(w), "-h", str(h or w), src, "-o", dst], check=True)
def magick(*args):
subprocess.run(["magick", *args], check=True)
write(f"{OUT}/ihasvpn-mark.svg", MARK)
write(f"{OUT}/ihasvpn-mark-mono.svg", MONO)
write(f"{PUB}/favicon.svg", MARK)
render(f"{OUT}/ihasvpn-mark.svg", f"{OUT}/ihasvpn-mark-256.png", 256)
render(f"{OUT}/ihasvpn-mark.svg", f"{OUT}/ihasvpn-mark-1024.png", 1024)
# Mono PNG in the brand navy, since currentColor has no value outside a page.
write(f"{TMP}/mono-navy.svg", MONO.replace("currentColor", NAVY))
render(f"{TMP}/mono-navy.svg", f"{OUT}/ihasvpn-mark-mono-256.png", 256)
# Wordmarks: light ground (navy text) and dark ground (pale text).
for name, fg in (("ihasvpn-wordmark", "#10303d"), ("ihasvpn-wordmark-dark", "#eaf6f6")):
body = f'''{mark_body("wm")}
ihasvpn'''
write(f"{TMP}/{name}.svg", svg(body, 936, 346, "0 0 936 346"))
render(f"{TMP}/{name}.svg", f"{OUT}/{name}.png", 936, 346)
# Social card, GitHub's 1280x640, in the ihasmail.org card's layout.
social = f'''
{mark_body("sc")}
ihasvpn
Self-hosted WireGuard server
with a secure web console
One container · kernel data plane · 2FA
github.com/Coffey-Labs/ihasvpn
AGPL-3.0 · Coffey Labs'''
write(f"{TMP}/social.svg", svg(social, 1280, 640, "0 0 1280 640"))
render(f"{TMP}/social.svg", f"{OUT}/ihasvpn-social.png", 1280, 640)
# Favicons and app icons.
for s in (16, 32, 48, 192, 512):
render(f"{OUT}/ihasvpn-mark.svg", f"{TMP}/fav-{s}.png", s)
magick(f"{TMP}/fav-16.png", f"{TMP}/fav-32.png", f"{TMP}/fav-48.png", f"{PUB}/favicon.ico")
magick(f"{TMP}/fav-32.png", f"{PUB}/favicon-32.png")
magick(f"{TMP}/fav-192.png", f"{PUB}/icon-192.png")
magick(f"{TMP}/fav-512.png", f"{PUB}/icon-512.png")
# iOS paints transparency black, so the touch icon gets the navy ground.
render(f"{OUT}/ihasvpn-mark.svg", f"{TMP}/fav-144.png", 144)
magick("-size", "180x180", "xc:#0d2430", f"{TMP}/fav-144.png", "-gravity", "center", "-composite", f"{PUB}/apple-touch-icon.png")
print("brand set written")