#!/usr/bin/env python3 """Write the site's own mark: header logo, favicon and touch icon. The mark is the chip of the project logo drawn as vector shapes: a body, four pins per side and the pixel glyph on a 4 by 4 grid. Three variants: - ``logo.svg``: white, the glyph cut out so the header colour shows through. It sits on the indigo header in both schemes. - ``favicon.svg``: indigo chip with a cyan glyph, lighter in a dark browser. - ``favicon-32.png`` and ``apple-touch-icon.png`` (180 px, opaque): the same shapes rasterised by a headless Chromium through Playwright, for browsers that take no SVG icon and for home-screen shortcuts. The files land in ``docs_assets/overrides/assets/brand/``, which MkDocs copies with the theme. The site build never runs this script. python scripts/make_site_icons.py """ from __future__ import annotations from pathlib import Path ROOT = Path(__file__).resolve().parent.parent OUT = ROOT / "docs_assets" / "overrides" / "assets" / "brand" HEADER = "#3b3f7a" GLYPH = "#8be0f7" GLYPH_DIM = "#4fa3c4" # (column, row, dim) on the 4 by 4 grid, read from the project logo. PIXELS = [ (0, 0, False), (1, 0, False), (2, 0, True), (1, 1, False), (3, 1, True), (0, 2, False), (3, 2, False), (1, 3, True), (2, 3, False), (3, 3, False), ] BODY = (8, 8, 32, 4) # x, y, size, corner radius in a 48 unit box PIN_CENTRES = (13.5, 20.5, 27.5, 34.5) PIN_WIDTH = 3.4 PIN_LENGTH = 5.5 CELL = 5.0 GAP = 1.6 def _pins(fill: str) -> str: half = PIN_WIDTH / 2 rects = [] for centre in PIN_CENTRES: start = centre - half rects += [ (start, 8 - PIN_LENGTH, PIN_WIDTH, PIN_LENGTH), (start, 40, PIN_WIDTH, PIN_LENGTH), (8 - PIN_LENGTH, start, PIN_LENGTH, PIN_WIDTH), (40, start, PIN_LENGTH, PIN_WIDTH), ] return "".join( f'' for x, y, w, h in rects ) def _glyph(bright: str, dim: str) -> str: span = 4 * CELL + 3 * GAP origin = BODY[0] + (BODY[2] - span) / 2 return "".join( f'' for c, r, is_dim in PIXELS ) def _body(fill: str, extra: str = "") -> str: x, y, size, radius = BODY return f'' def logo_svg() -> str: """White chip, glyph cut out: the header colour shows through it.""" return ( '' '' + _body("#fff") + _glyph("#000", "#5a5a5a") + "" + _pins("#fff") + _body("#fff", ' mask="url(#glyph)"') + "" ) def favicon_svg() -> str: return ( '' "" + _pins(HEADER).replace(f'fill="{HEADER}"', 'class="c"') + _body(HEADER).replace(f'fill="{HEADER}"', 'class="c"') + _glyph(GLYPH, GLYPH_DIM) + "" ) def touch_icon_svg() -> str: """Opaque square: iOS fills transparency with black.""" return ( '' f'' + logo_svg().split(">", 1)[1].rsplit("", 1)[0] + "" ) def main() -> None: try: from playwright.sync_api import sync_playwright except ImportError as exc: raise SystemExit("Playwright with Chromium is needed: pip install playwright") from exc OUT.mkdir(parents=True, exist_ok=True) for name, text in (("logo.svg", logo_svg()), ("favicon.svg", favicon_svg())): (OUT / name).write_text(text + "\n", encoding="utf-8") print((OUT / name).relative_to(ROOT)) rasters = ( ("favicon-32.png", 32, favicon_svg().replace("@media (prefers-color-scheme: dark)", "@media not all")), ("apple-touch-icon.png", 180, touch_icon_svg()), ) with sync_playwright() as playwright: browser = playwright.chromium.launch() for name, size, text in rasters: page = browser.new_page(viewport={"width": size, "height": size}) sized = text.replace("{sized}') page.screenshot(path=str(OUT / name), omit_background=True, clip={"x": 0, "y": 0, "width": size, "height": size}) page.close() print((OUT / name).relative_to(ROOT)) browser.close() if __name__ == "__main__": from common import run_cli run_cli(main)