SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
19 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
5.0 KB · 142 lines python
Raw Blame History
1#!/usr/bin/env python32"""Rend UNE carte de stat KA (1080x1350) à partir d'un insight JSON sur stdin.34Style maison Groupe KA : charbon + accent du site + crème. Nombre-héros,53 tuiles, et barres à étiquettes directes si l'insight en fournit.6Imprime le chemin du PNG produit sur stdout.7"""8import json, sys, os, datetime9from PIL import Image, ImageDraw, ImageFont1011CHARCOAL = (20, 24, 20)12CHARCOAL2 = (30, 36, 30)13CREAM = (242, 241, 234)14GRAY = (150, 156, 148)15RING = (46, 54, 46)1617BLACK_F = "/System/Library/Fonts/Supplemental/Arial Black.ttf"18BOLD_F = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"19MONO_F = "/System/Library/Fonts/Supplemental/Courier New Bold.ttf"2021W, H = 1080, 1350222324def hex2rgb(h):25    h = h.lstrip("#")26    return tuple(int(h[i:i + 2], 16) for i in (0, 2, 4))272829def f(p, s):30    return ImageFont.truetype(p, s)313233def fit(d, text, path, size, maxw, floor=40):34    while size > floor:35        fo = f(path, size)36        if d.textlength(text, font=fo) <= maxw:37            return fo38        size -= 439    return f(path, size)404142def wrap(d, text, font, maxw, maxlines=2):43    words, lines, cur = text.split(), [], ""44    for w in words:45        probe = (cur + " " + w).strip()46        if d.textlength(probe, font=font) <= maxw:47            cur = probe48        else:49            lines.append(cur); cur = w50    lines.append(cur)51    return lines[:maxlines]525354def render(ins, out_dir):55    accent = hex2rgb(ins["accent"])56    domain = "www." + ins["site"] + ".com"57    img = Image.new("RGB", (W, H), CHARCOAL)58    d = ImageDraw.Draw(img)59    d.rounded_rectangle([16, 16, W - 16, H - 16], radius=26, outline=RING, width=3)6061    # Eyebrow avec la date du jour62    today = datetime.date.today()63    mois = ["janvier","février","mars","avril","mai","juin","juillet","août",64            "septembre","octobre","novembre","décembre"][today.month - 1]65    d.line([64, 92, 112, 92], fill=accent, width=6)66    d.text((130, 92), f"{ins['label'].upper()}  ·  STAT DU JOUR  ·  {today.day} {mois.upper()} {today.year}",67           font=f(BOLD_F, 25), fill=accent, anchor="lm")6869    # Boîte KA inclinée70    box = Image.new("RGBA", (150, 96), (0, 0, 0, 0))71    bd = ImageDraw.Draw(box)72    bd.rounded_rectangle([0, 0, 149, 95], radius=20, fill=accent)73    bd.text((75, 48), "KA", font=f(BLACK_F, 58), fill=CHARCOAL, anchor="mm")74    box = box.rotate(-4, expand=True, resample=Image.BICUBIC)75    img.paste(box, (W - 64 - box.width, 52), box)7677    # Nom + tagline78    d.text((64, 165), ins["label"], font=f(BLACK_F, 88), fill=CREAM)79    if ins.get("tagline"):80        d.text((66, 292), ins["tagline"], font=f(BOLD_F, 31), fill=GRAY)8182    # Héros83    hf = fit(d, ins["headline"], BLACK_F, 176, W - 128)84    d.text((60, 372), ins["headline"], font=hf, fill=accent)85    hl = wrap(d, ins["headline_label"], f(BOLD_F, 40), W - 128, 2)86    for i, ln in enumerate(hl):87        d.text((66, 372 + hf.size + 44 + i * 46), ln, font=f(BOLD_F, 40), fill=CREAM)8889    y = 372 + hf.size + 44 + len(hl) * 46 + 409091    # Tuiles92    tiles = ins.get("tiles") or []93    if tiles:94        d.line([64, y, W - 64, y], fill=RING, width=2)95        y += 2696        col = (W - 128) / len(tiles)97        for i, (val, lab) in enumerate(tiles):98            x = 64 + int(i * col)99            vf = fit(d, str(val), BLACK_F, 52, col - 24)100            d.text((x, y), str(val), font=vf, fill=CREAM)101            for j, ln in enumerate(wrap(d, lab, f(BOLD_F, 25), col - 24, 2)):102                d.text((x, y + 70 + j * 32), ln, font=f(BOLD_F, 25), fill=GRAY)103        y += 150104105    # Barres (optionnelles)106    bars = ins.get("bars")107    if bars:108        maxv = max(b[1] for b in bars) or 1109        avail = (1150 if not ins.get("note") else 1120) - y110        rowh = min(avail / len(bars), 118)111        for i, b in enumerate(bars):112            label, v, vtxt = b[0], b[1], b[2]113            sub = b[3] if len(b) > 3 else None114            yy = y + int(i * rowh)115            d.text((64, yy), str(label), font=f(BOLD_F, 31), fill=CREAM)116            d.text((W - 64, yy), str(vtxt), font=f(BLACK_F, 32), fill=accent, anchor="ra")117            if sub:118                d.text((64, yy + 42), sub, font=f(BOLD_F, 24), fill=GRAY)119            by = yy + (78 if sub else 46)120            d.rounded_rectangle([64, by, W - 64, by + 15], radius=7, fill=CHARCOAL2)121            fw = max(18, int((W - 128) * v / maxv))122            d.rounded_rectangle([64, by, 64 + fw, by + 15], radius=7, fill=accent)123124    if ins.get("note"):125        d.text((64, 1150), ins["note"], font=f(BOLD_F, 27), fill=GRAY)126127    # Bandeau bas128    d.rounded_rectangle([16, 1246, W - 16, H - 16], radius=26, fill=accent)129    d.text((64, 1290), domain, font=f(MONO_F, 40), fill=CHARCOAL, anchor="lm")130    d.text((W - 64, 1290), "GROUPE ·KA", font=f(BLACK_F, 34), fill=CHARCOAL, anchor="rm")131132    os.makedirs(out_dir, exist_ok=True)133    path = os.path.join(out_dir, f"card-{ins['site']}-{ins['insight_id']}.png")134    img.save(path)135    return path136137138if __name__ == "__main__":139    ins = json.load(sys.stdin)140    out_dir = sys.argv[1] if len(sys.argv) > 1 else "/tmp/ka-social-cards"141    print(render(ins, out_dir))142