Files
flowdeck/scripts/generate_pwa_icons.py
bruno b5207216f1 feat: v6.0.0 PWA offline support
- manifest + icones, service worker (precache, network-first, Background Sync)

- module client FlowOffline (IndexedDB, queue, delta, flush) + hook editeur

- endpoints /api/v2/sync/{delta,batch,status} + moteur de sync (conflits LWW/orpheline/copie offline)

- migrations offline_sync_queue + sync_version (triggers)

- UI offline (banner, badge sync, toasts, icone dirty) + doc /help

- tests pytest (sync, migrations, SW, offline) + E2E Playwright; bump 6.0.0
2026-09-18 13:05:40 -04:00

70 lines
2.2 KiB
Python

#!/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()