"""Generate the social-share card (Open Graph / Twitter image), 1200x630. Run once; the PNG is committed to static/ and copied to the site root at build time. The container does not need Pillow at runtime. Re-run only if the wording or brand colours change: python generate_og.py """ from __future__ import annotations import os from PIL import Image, ImageDraw, ImageFont from generate_icons import make_icon STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") W, H, SS = 1200, 630, 2 # final size + supersample factor for crisp text INK = (33, 37, 41) GREY = (90, 99, 110) GREEN = (26, 152, 80) # Price legend gradient (matches the map): cheap green -> pricey red. LEGEND = [(26, 152, 80), (145, 207, 96), (254, 224, 139), (252, 141, 89), (215, 48, 39)] # Font candidates per weight: Arial on macOS (local), DejaVu in the container. FONTS = { "bold": ["/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", "/Library/Fonts/Arial Bold.ttf"], "regular": ["/System/Library/Fonts/Supplemental/Arial.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/Library/Fonts/Arial.ttf"], } def _font(weight: str, size: int) -> ImageFont.FreeTypeFont: for path in FONTS[weight]: if os.path.exists(path): return ImageFont.truetype(path, size) return ImageFont.load_default() def _lerp(a, b, t): return tuple(round(a[i] + (b[i] - a[i]) * t) for i in range(3)) def _gradient_bar(draw: ImageDraw.ImageDraw, x0, y0, x1, y1) -> None: """Horizontal multi-stop gradient bar (the price scale).""" span = x1 - x0 seg = span / (len(LEGEND) - 1) for x in range(x0, x1): f = (x - x0) / seg i = min(int(f), len(LEGEND) - 2) draw.line([(x, y0), (x, y1)], fill=_lerp(LEGEND[i], LEGEND[i + 1], f - i)) def build() -> Image.Image: w, h = W * SS, H * SS img = Image.new("RGB", (w, h), (255, 255, 255)) d = ImageDraw.Draw(img) # Left: the brand icon tile. icon = make_icon(360 * SS, rounded=True) img.paste(icon, (70 * SS, (h - icon.height) // 2), icon) tx = 470 * SS # text column d.text((tx, 160 * SS), "Prix des carburants", font=_font("bold", 60 * SS), fill=INK) d.text((tx, 240 * SS), "en France", font=_font("bold", 60 * SS), fill=GREEN) d.text((tx, 350 * SS), "Carte en temps réel — gazole, SP95,", font=_font("regular", 34 * SS), fill=GREY) d.text((tx, 396 * SS), "SP98, E85, GPL & bornes de recharge", font=_font("regular", 34 * SS), fill=GREY) _gradient_bar(d, tx, 470 * SS, (W - 80) * SS, 492 * SS) d.text((tx, 504 * SS), "moins cher", font=_font("regular", 26 * SS), fill=GREY) pricey = "plus cher" pf = _font("regular", 26 * SS) d.text(((W - 80) * SS - d.textlength(pricey, font=pf), 504 * SS), pricey, font=pf, fill=GREY) d.text((tx, 556 * SS), "samdnx-prix-carburant.hf.space", font=_font("bold", 26 * SS), fill=GREEN) return img.resize((W, H), Image.LANCZOS) def save(path: str) -> None: """Render the card and write it to path (used at build time).""" build().save(path, optimize=True) def main() -> None: os.makedirs(STATIC_DIR, exist_ok=True) out = os.path.join(STATIC_DIR, "og-card.png") save(out) print("wrote", out, f"({os.path.getsize(out) // 1024} KB)") if __name__ == "__main__": main()