alpinux.site.2026/scripts/build-assets.py
Cédrix 565165cc70 Architecte static.alpinux.org : assets binaires hors git
- Logo SVG (source texte) ajouté dans docs/assets/alpinux-logo.svg
- .gitignore : exclut *.png, *.ico, docs/assets/images/ (binaires → static.alpinux.org)
- overrides/partials/logo.html : logo depuis https://static.alpinux.org/logo/
- overrides/main.html : favicons depuis static.alpinux.org via {% block extrahead %}
- mkdocs.yml : logo → SVG, ajout pymdownx.emoji (icônes Material)
- home/index.html : page d'accueil alpinux.org (logo + favicon depuis static.alpinux.org, carte dynamic.alpinux.org)
- scripts/build-assets.py : génère PNG/favicon depuis le SVG source
- scripts/static.alpinux.org.vhost.conf : template vhost Apache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 08:46:06 +02:00

155 lines
5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Génère les assets binaires (logo PNG + favicons) depuis la source SVG.
Dépendances : Pillow, chromium
Usage :
python3 scripts/build-assets.py [--out /chemin/de/sortie]
Les fichiers générés sont ensuite uploadés sur static.alpinux.org/logo/
"""
import argparse
import subprocess
import tempfile
import os
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
REPO = Path(__file__).resolve().parent.parent
SVG = REPO / "docs/assets/alpinux-logo.svg"
FONT_R = "/usr/share/fonts/truetype/msttcorefonts/arial.ttf"
FONT_B = "/usr/share/fonts/truetype/msttcorefonts/arialbd.ttf"
COLOR = (15, 78, 143) # #0f4e8f
def render_shapes(svg_path: Path, width: int, height: int) -> Image.Image:
"""Render SVG shapes via chromium headless (no text)."""
html = f"""<!DOCTYPE html><html>
<head><meta charset="utf-8">
<style>*{{margin:0;padding:0}}html,body{{width:{width}px;height:{height}px;overflow:hidden;background:transparent}}</style>
</head>
<body><img src="file://{svg_path}" width="{width}" height="{height}"></body>
</html>"""
with tempfile.NamedTemporaryFile(suffix=".html", mode="w", delete=False) as f:
f.write(html)
tmp_html = f.name
out_png = tmp_html.replace(".html", ".png")
subprocess.run([
"chromium", "--headless", "--disable-gpu", "--no-sandbox",
f"--screenshot={out_png}", f"--window-size={width},{height}",
f"file://{tmp_html}"
], capture_output=True)
img = Image.open(out_png).convert("RGBA")
os.unlink(tmp_html)
os.unlink(out_png)
return img
def add_text(canvas: Image.Image) -> Image.Image:
"""Composite Alpinux text with correct weights onto the canvas."""
draw = ImageDraw.Draw(canvas)
size = 30
f_reg = ImageFont.truetype(FONT_R, size)
f_bld = ImageFont.truetype(FONT_B, size)
parts = [("A", False), ("l", True), ("p", False), ("inux", True)]
widths = []
for char, bold in parts:
f = f_bld if bold else f_reg
bb = f.getbbox(char)
widths.append(bb[2] - bb[0])
total_w = sum(widths)
x = (canvas.width - total_w) // 2
y = 164 + (36 - size) // 2 + 1
for (char, bold), w in zip(parts, widths):
f = f_bld if bold else f_reg
bb = f.getbbox(char)
draw.text((x - bb[0], y - bb[1]), char, font=f, fill=COLOR)
x += w
return canvas
def build_logo(out_dir: Path):
"""Build 200×200 logo PNG."""
shapes = render_shapes(SVG, 200, 164)
canvas = Image.new("RGBA", (200, 200), (255, 255, 255, 255))
canvas.paste(shapes, (0, 0))
canvas = add_text(canvas)
path = out_dir / "alpinux-logo.png"
canvas.convert("RGB").save(path)
print(f" {path} (200×200)")
# 512px high-res version
shapes512 = render_shapes(SVG, 512, 421)
canvas512 = Image.new("RGBA", (512, 512), (255, 255, 255, 255))
canvas512.paste(shapes512, (0, 0))
# Scale text proportionally
draw = ImageDraw.Draw(canvas512)
size = 77
f_reg = ImageFont.truetype(FONT_R, size)
f_bld = ImageFont.truetype(FONT_B, size)
parts = [("A", False), ("l", True), ("p", False), ("inux", True)]
widths = [f_bld.getbbox(c)[2]-f_bld.getbbox(c)[0] if b else f_reg.getbbox(c)[2]-f_reg.getbbox(c)[0] for c,b in parts]
total_w = sum(widths)
x = (512 - total_w) // 2
y = 421 + (91 - size) // 2 + 2
for (char, bold), w in zip(parts, widths):
f = f_bld if bold else f_reg
bb = f.getbbox(char)
draw.text((x - bb[0], y - bb[1]), char, font=f, fill=COLOR)
x += w
path512 = out_dir / "alpinux-logo-512.png"
canvas512.convert("RGB").save(path512)
print(f" {path512} (512×512)")
def build_favicons(out_dir: Path):
"""Build favicon PNG set + .ico from the icon portion of the SVG."""
icon_src = render_shapes(SVG, 200, 164).crop((0, 0, 164, 164))
sizes = {
"favicon-16.png": 16,
"favicon-32.png": 32,
"favicon.png": 48,
"favicon-96.png": 96,
"favicon-192.png": 192,
}
imgs = {}
for name, sz in sizes.items():
img = icon_src.resize((sz, sz), Image.LANCZOS)
bg = Image.new("RGBA", (sz, sz), (255, 255, 255, 255))
bg.paste(img, (0, 0))
p = out_dir / name
bg.save(p)
imgs[sz] = bg
print(f" {p} ({sz}×{sz})")
ico = out_dir / "favicon.ico"
imgs[16].save(ico, format="ICO", sizes=[(16,16),(32,32),(48,48)],
append_images=[imgs[32], imgs[48]])
print(f" {ico} (multi-size: 16+32+48)")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", default="/tmp/alpinux-static-assets",
help="Répertoire de sortie (défaut: /tmp/alpinux-static-assets)")
args = parser.parse_args()
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
print("Génération des assets Alpinux...")
build_logo(out_dir)
build_favicons(out_dir)
print(f"\nFichiers dans : {out_dir}")
print("À uploader sur : static.alpinux.org/logo/")
if __name__ == "__main__":
main()