"""Every icon Inkwell ships, from one drawing. The mark is a black inkpot with a quill standing in it, on the brand yellow (Scribe note 5071). It is drawn ONCE below, in a 512 box, and each variant places it with its own scale, because each platform crops differently: - icon.svg the rounded tile: web favicon/PWA "any", desktop app icon - icon-maskable.svg full bleed, mark inside the central 80% (PWA "maskable") - Android foreground mark alone on transparency inside the 66dp safe circle of a 108dp adaptive canvas; the yellow is the background layer, and the transparent mark is also what themed (monochrome) icons need Run from the repo root after changing the drawing; needs rsvg-convert (librsvg). Generated output is committed, like any other generated file. python3 packaging/icons.py """ import subprocess from pathlib import Path INK = "#18181b" GOLD = "#F5C518" # The quill is drawn upright at the pot mouth (0,0), then rotated into place. QUILL_AT = "translate(256 232) rotate(34)" QUILL_INK = f''' ''' POT_INK = f''' ''' # The detail — the quill's shaft, two splits in its vane, the glint on the pot — is # CUT OUT of the ink rather than painted over it in yellow. On the tile the two look # the same; on Android's transparent foreground only a cut-out is right, because a # themed icon draws the foreground's alpha as a silhouette, and painted detail # would come out as solid, with the vane splits sticking out past its edge. CUTS = ''' '''.format(at=QUILL_AT) GLYPH = f''' {CUTS} {QUILL_INK}{POT_INK} ''' # Glyph bounds, measured from a render: centre the mark, then scale per variant. CX, CY = 262, 226 def place(scale, cy=256): return f'translate(256 {cy}) scale({scale}) translate({-CX} {-CY})' def svg(body, label=True): aria = ' role="img" aria-label="Inkwell"' if label else '' return f'\n{body}\n\n' def tile(scale=0.92): return svg(f''' {GLYPH} ''') def maskable(scale=0.8): return svg(f''' {GLYPH} ''') def foreground(scale=0.64): # Android adaptive foreground: the mark alone on transparency, inside the 66dp # safe circle of a 108dp canvas. The yellow lives in the background layer. return svg(f''' {GLYPH} ''', label=False) ROOT = Path(__file__).resolve().parent.parent WEB = ROOT / "frontend" / "public" DESKTOP = ROOT / "desktop" / "src-tauri" ANDROID = ROOT / "android" / "app" / "src" / "main" / "res" / "mipmap-xxxhdpi" def render(svg_text: str, size: int, out: Path, background: str | None = None) -> None: cmd = ["rsvg-convert", "-w", str(size), "-h", str(size), "-o", str(out)] if background: cmd += ["-b", background] subprocess.run(cmd, input=svg_text.encode(), check=True) def main() -> None: icon, mask, fg = tile(), maskable(), foreground() (WEB / "icon.svg").write_text(icon) (WEB / "icon-maskable.svg").write_text(mask) render(icon, 32, WEB / "favicon-32.png") render(icon, 192, WEB / "icon-192.png") render(icon, 512, WEB / "icon-512.png") render(mask, 512, WEB / "icon-maskable-512.png") # iOS rounds the corners itself and shows transparency as black. render(mask, 180, WEB / "apple-touch-icon.png") render(icon, 1024, DESKTOP / "app-icon.png") render(icon, 32, DESKTOP / "icons" / "32x32.png") render(icon, 128, DESKTOP / "icons" / "128x128.png") render(icon, 256, DESKTOP / "icons" / "128x128@2x.png") render(icon, 512, DESKTOP / "icons" / "icon.png") render(fg, 512, ANDROID / "ic_launcher_foreground.png") if __name__ == "__main__": main()