"""Generate platform-specific app icons from the source PNG asset. Outputs: build/icon.ico Windows multi-resolution icon (16..256 px sizes). build/icon.icns macOS icon bundle (16..1024 px scaled tiers). build/icon.png Plain 256x256 PNG used by the Linux AppImage. Source: ``src/gui/assets/datatools_icon_256.png`` (the same icon ``st.set_page_config`` uses, so the installer / Dock / Taskbar match the in-app tab favicon). Run manually: python build/generate_icons.py CI runs this automatically before invoking PyInstaller (see ``.github/workflows/build.yml``). Both files are .gitignored — they are build artifacts derived from the committed PNG. Self-contained: pulls only Pillow (already a transitive dep of ``pdfplumber``) so no extra installs are required. """ from __future__ import annotations import sys from pathlib import Path from PIL import Image # Repo layout: this script lives at /build/. The source PNG is at # /src/gui/assets/datatools_icon_256.png. BUILD_DIR = Path(__file__).resolve().parent REPO = BUILD_DIR.parent SOURCE_PNG = REPO / "src" / "gui" / "assets" / "datatools_icon_256.png" # Windows ICO needs every size the OS might render at: taskbar (16/24), # Start Menu (32/48), tile (64/128), shell properties dialog (256). ICO_SIZES = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)] def main() -> int: if not SOURCE_PNG.exists(): sys.stderr.write( f"Source icon not found at {SOURCE_PNG}.\n" "Add a 256x256 (or larger) RGBA PNG there and re-run.\n" ) return 1 src = Image.open(SOURCE_PNG).convert("RGBA") if src.size[0] < 256 or src.size[1] < 256: sys.stderr.write( f"Source icon is {src.size}; recommend 256x256 or larger " "so downscaled tiers look crisp.\n" ) ico_path = BUILD_DIR / "icon.ico" src.save(ico_path, format="ICO", sizes=ICO_SIZES) print(f"wrote {ico_path} ({ico_path.stat().st_size:,} bytes)") icns_path = BUILD_DIR / "icon.icns" # Pillow's ICNS writer derives the per-tier sizes from the source # image; passing a 256x256 source yields ic07..ic12 entries which # cover Finder, Dock, and the Get Info panel. src.save(icns_path, format="ICNS") print(f"wrote {icns_path} ({icns_path.stat().st_size:,} bytes)") # AppImage uses a plain PNG for its desktop entry. Copy the source # so the AppImage build script doesn't have to know the asset path. png_path = BUILD_DIR / "icon.png" src.save(png_path, format="PNG") print(f"wrote {png_path} ({png_path.stat().st_size:,} bytes)") return 0 if __name__ == "__main__": sys.exit(main())