#!/usr/bin/env python3 """Generate FlowDeck PWA icons as PNG (72→512 px) from the favicon design. Draws the same logo as static/favicon.svg (blue rounded square + white "FD") at every size required by the web manifest. No cairosvg dependency required — pure Pillow. """ from __future__ import annotations from pathlib import Path from PIL import Image, ImageDraw, ImageFont ICON_BLUE = "#2383E2" ICON_WHITE = "#FFFFFF" SIZES = [72, 96, 128, 144, 152, 192, 384, 512] OUT_DIR = Path(__file__).resolve().parent.parent / "static" / "icons" def _font_at(px: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: """Pick the best available bold font for the requested icon size.""" candidates = [ "C:/Windows/Fonts/arialbd.ttf", "C:/Windows/Fonts/Arialbd.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", ] for path in candidates: if Path(path).exists(): try: return ImageFont.truetype(path, size=int(px * 0.42)) except OSError: continue return ImageFont.load_default() def draw_icon(size: int) -> Image.Image: """Render the FlowDeck rounded-square logo at `size` x `size` px.""" radius = max(2, round(size * 0.1875)) # rx=6 on a 32 viewBox → ~18.75% img = Image.new("RGB", (size, size), ICON_BLUE) draw = ImageDraw.Draw(img) draw.rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=ICON_BLUE) font = _font_at(size) bbox = draw.textbbox((0, 0), "FD", font=font) tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1] draw.text( ((size - tw) / 2 - bbox[0], (size - th) / 2 - bbox[1]), "FD", font=font, fill=ICON_WHITE, ) return img def main() -> None: OUT_DIR.mkdir(parents=True, exist_ok=True) for size in SIZES: path = OUT_DIR / f"icon-{size}x{size}.png" draw_icon(size).save(path, "PNG", optimize=True) print(f"Generated {path}") # Apple touch icon: standalone 180 px (not part of the manifest set). apple = OUT_DIR / "apple-touch-icon.png" draw_icon(180).save(apple, "PNG", optimize=True) print(f"Generated {apple}") if __name__ == "__main__": main()