JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
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