| """Certificate renderer. |
| |
| Draws the participation certificate directly with Pillow, so the Space has no |
| runtime dependency on any other Space. Fonts come from `fonts-dejavu-core` and |
| `fonts-noto-cjk` (see packages.txt); set CERT_FONT_DIR to render with a |
| different font set. |
| """ |
|
|
| import functools |
| import os |
|
|
| from PIL import Image, ImageDraw, ImageFont |
|
|
| |
| W, H = 2000, 1414 |
| SS = 2 |
|
|
| PAPER = (253, 252, 249) |
| GRID = (243, 243, 240) |
| INK = (31, 41, 55) |
| ORANGE = (234, 88, 12) |
| MUTED = (107, 114, 128) |
| SEP = (211, 214, 220) |
|
|
| FONT_DIRS = [ |
| os.environ.get("CERT_FONT_DIR"), |
| "/usr/share/fonts/truetype/dejavu", |
| "/usr/share/fonts/dejavu", |
| "/usr/share/fonts/TTF", |
| "/usr/share/fonts/opentype/noto", |
| "/usr/share/fonts/truetype/noto", |
| ] |
| SERIF_BOLD, SANS, SANS_BOLD, MONO_BOLD = ( |
| "DejaVuSerif-Bold.ttf", "DejaVuSans.ttf", |
| "DejaVuSans-Bold.ttf", "DejaVuSansMono-Bold.ttf", |
| ) |
| |
| |
| |
| |
| BOLD_FALLBACKS = ("NotoSansCJK-Bold.ttc", "NotoSansCJK-Regular.ttc") |
| FALLBACKS = { |
| SERIF_BOLD: BOLD_FALLBACKS, |
| SANS_BOLD: BOLD_FALLBACKS, |
| MONO_BOLD: BOLD_FALLBACKS, |
| SANS: ("NotoSansCJK-Regular.ttc", "NotoSansCJK-Bold.ttc"), |
| } |
|
|
|
|
| def font_path(filename, required=True): |
| for d in FONT_DIRS: |
| if d and os.path.exists(os.path.join(d, filename)): |
| return os.path.join(d, filename) |
| if not required: |
| return None |
| raise FileNotFoundError( |
| f"{filename} not found in {[d for d in FONT_DIRS if d]}. " |
| "Install fonts-dejavu-core or set CERT_FONT_DIR." |
| ) |
|
|
|
|
| @functools.lru_cache(maxsize=None) |
| def font(filename, size): |
| return ImageFont.truetype(font_path(filename), int(size * SS)) |
|
|
|
|
| @functools.lru_cache(maxsize=None) |
| def fallback_path(filename): |
| """First installed fallback font for `filename`, or None if none is.""" |
| for candidate in FALLBACKS.get(filename, ()): |
| path = font_path(candidate, required=False) |
| if path: |
| return path |
| return None |
|
|
|
|
| @functools.lru_cache(maxsize=None) |
| def fallback_font(filename, size): |
| path = fallback_path(filename) |
| return ImageFont.truetype(path, int(size * SS)) if path else None |
|
|
|
|
| @functools.lru_cache(maxsize=None) |
| def covered(path): |
| """Codepoints the font can actually draw (empty set = assume everything).""" |
| if path is None: |
| return frozenset() |
| try: |
| from fontTools.ttLib import TTCollection, TTFont |
| f = TTCollection(path).fonts[0] if path.endswith(".ttc") else TTFont(path, fontNumber=0) |
| with f: |
| return frozenset(f.getBestCmap()) |
| except Exception: |
| return frozenset() |
|
|
|
|
| def runs(text, filename, size): |
| """Split `text` into (string, font) runs, using the fallback font for |
| characters the primary font lacks and dropping ones neither can draw.""" |
| primary, fb = font(filename, size), fallback_font(filename, size) |
| main_cmap, fb_cmap = covered(font_path(filename)), covered(fallback_path(filename)) |
| out = [] |
| for ch in text: |
| if not main_cmap or ord(ch) in main_cmap or ch == " ": |
| f = primary |
| elif fb is not None and (not fb_cmap or ord(ch) in fb_cmap): |
| f = fb |
| else: |
| continue |
| if out and out[-1][1] is f: |
| out[-1][0] += ch |
| else: |
| out.append([ch, f]) |
| return [(t, f) for t, f in out] |
|
|
|
|
| def runs_width(rs): |
| return sum(f.getlength(t) for t, f in rs) |
|
|
|
|
| def fit_runs(text, filename, size, max_width, min_size): |
| """Shrink to fit `max_width`, then ellipsize if it still doesn't fit.""" |
| while size > min_size: |
| rs = runs(text, filename, size) |
| if runs_width(rs) <= max_width * SS: |
| return rs |
| size -= 2 |
| while text: |
| rs = runs(text + "…", filename, min_size) |
| if runs_width(rs) <= max_width * SS: |
| return rs |
| text = text[:-1] |
| return runs("…", filename, min_size) |
|
|
|
|
| def fit_font(filename, size, text, max_width, min_size=32): |
| """Largest size <= `size` at which `text` fits in `max_width` design px.""" |
| while size > min_size: |
| f = font(filename, size) |
| if f.getlength(text) <= max_width * SS: |
| return f |
| size -= 2 |
| return font(filename, min_size) |
|
|
|
|
| def line_height(f): |
| return sum(f.getmetrics()) |
|
|
|
|
| |
| def tracked_width(f, text, tracking): |
| return sum(f.getlength(c) for c in text) + tracking * SS * max(len(text) - 1, 0) |
|
|
|
|
| def draw_tracked(draw, cx, y, text, f, fill, tracking): |
| """Centered text with CSS-style letter-spacing (Pillow has no tracking).""" |
| x = cx - tracked_width(f, text, tracking) / 2 |
| for ch in text: |
| draw.text((x, y), ch, font=f, fill=fill, anchor="la") |
| x += f.getlength(ch) + tracking * SS |
| return y + line_height(f) |
|
|
|
|
| def draw_centered(draw, cx, y, text, f, fill): |
| draw.text((cx, y), text, font=f, fill=fill, anchor="ma") |
| return y + line_height(f) |
|
|
|
|
| def draw_runs_centered(draw, cx, y, rs, fill): |
| x = cx - runs_width(rs) / 2 |
| for text, f in rs: |
| draw.text((x, y), text, font=f, fill=fill, anchor="la") |
| x += f.getlength(text) |
| return y + max(line_height(f) for _, f in rs) |
|
|
|
|
| def draw_rich(draw, cx, y, segments, max_width, leading): |
| """Word-wrap `segments` [(text, font, color), ...] into centered lines.""" |
| words = [] |
| for text, f, color in segments: |
| words += [(w, f, color) for w in text.split()] |
|
|
| lines, line, width = [], [], 0.0 |
| for w, f, color in words: |
| space = f.getlength(" ") if line else 0 |
| adv = f.getlength(w) |
| if line and width + space + adv > max_width * SS: |
| lines.append(line) |
| line, width = [(w, f, color)], adv |
| else: |
| line.append((w, f, color)) |
| width += space + adv |
|
|
| if line: |
| lines.append(line) |
|
|
| for ln in lines: |
| total = sum(f.getlength(w) for w, f, _ in ln) |
| total += sum(f.getlength(" ") for _, f, _ in ln[:-1]) |
| x = cx - total / 2 |
| for i, (w, f, color) in enumerate(ln): |
| draw.text((x, y), w, font=f, fill=color, anchor="la") |
| x += f.getlength(w) + (f.getlength(" ") if i < len(ln) - 1 else 0) |
| y += leading * SS |
| return y |
|
|
|
|
| def clean_name(name, limit=64): |
| """Collapse whitespace and cap length so the layout can't be blown up.""" |
| name = " ".join(str(name or "").split()) |
| return name[:limit].rstrip() if len(name) > limit else name |
|
|
|
|
| |
| def render_certificate(participant_name, stats_line, out_path): |
| name = clean_name(participant_name) or "Anonymous participant" |
| img = Image.new("RGB", (W * SS, H * SS), PAPER) |
| d = ImageDraw.Draw(img) |
| cx = W * SS // 2 |
|
|
| for x in range(0, W + 1, 40): |
| d.line([(x * SS, 0), (x * SS, H * SS)], fill=GRID, width=SS) |
| for y in range(0, H + 1, 40): |
| d.line([(0, y * SS), (W * SS, y * SS)], fill=GRID, width=SS) |
|
|
| d.rounded_rectangle([60 * SS, 60 * SS, (W - 60) * SS, (H - 60) * SS], |
| radius=24 * SS, outline=INK, width=3 * SS) |
| d.rounded_rectangle([134 * SS, 134 * SS, (W - 134) * SS, (H - 134) * SS], |
| radius=16 * SS, outline=ORANGE, width=2 * SS) |
|
|
| y = 232 * SS |
| y = draw_tracked(d, cx, y, "REPRODUCING ICML 2026 · OPEN REPRODUCTIONS", |
| font(MONO_BOLD, 24), ORANGE, 8) |
|
|
| y += 26 * SS |
| y = draw_centered(d, cx, y, "Certificate of Participation", |
| fit_font(SERIF_BOLD, 92, "Certificate of Participation", 1640), INK) |
|
|
| y += 40 * SS |
| y = draw_centered(d, cx, y, "This certificate is proudly presented to", |
| font(SANS, 32), MUTED) |
|
|
| y += 30 * SS |
| name_runs = fit_runs(name, SERIF_BOLD, 88, 1560, 40) |
| if not name_runs: |
| name_runs = fit_runs("Anonymous participant", SERIF_BOLD, 88, 1560, 40) |
| y = draw_runs_centered(d, cx, y, name_runs, INK) |
|
|
| y += 34 * SS |
| d.rounded_rectangle([cx - 280 * SS, y, cx + 280 * SS, y + 3 * SS], |
| radius=2 * SS, fill=ORANGE) |
| y += 3 * SS |
|
|
| y += 48 * SS |
| body, body_bold = font(SANS, 32), font(SANS_BOLD, 32) |
| y = draw_rich(d, cx, y, [ |
| ("for contributing to the community reproduction of ICML 2026, with", body, INK), |
| (stats_line, body_bold, ORANGE), |
| ("independently confirmed by the automated Logbook Judge, as part of " |
| "the largest open, claim-by-claim audit of a machine learning " |
| "conference to date.", body, INK), |
| ], max_width=1380, leading=50) |
|
|
| y += 32 * SS |
| draw_tracked(d, cx, y, "JULY 15 – AUGUST 2, 2026", font(MONO_BOLD, 24), MUTED, 5) |
|
|
| |
| partners = [("Trackio", INK), ("·", SEP), ("Hugging Face", INK), |
| ("·", SEP), ("alphaXiv", INK)] |
| pf = font(SANS_BOLD, 30) |
| gap = 34 * SS |
| total = sum(pf.getlength(t) for t, _ in partners) + gap * (len(partners) - 1) |
| x, py = cx - total / 2, (H - 268) * SS |
| for text, color in partners: |
| d.text((x, py), text, font=pf, fill=color, anchor="la") |
| x += pf.getlength(text) + gap |
|
|
| org = ("Issued by the ICML 2026 Agent Repro organizers · " |
| "huggingface.co/ICML-2026-agent-repro") |
| d.text((cx, (H - 202) * SS), org, font=font(SANS, 22), fill=MUTED, anchor="ma") |
|
|
| img.resize((W, H), Image.LANCZOS).save(out_path, "PNG", optimize=True) |
| return out_path |
|
|
|
|
| if __name__ == "__main__": |
| render_certificate("Ada Lovelace", |
| "1673 claims verified across 347 papers", |
| "preview.png") |
|
|