← All projects  ·  Project 01

Programmatic Creative Pipeline

A Python and Node tool that takes one campaign brief and renders sixteen finished ad images, four products across four placement ratios, then exports them as a file a designer can keep editing.

Role: Sole builder, concept to layout system Period: 2026 Stack: Python · Node View source →
A REPEATABLE WORKFLOWCAMPAIGN BRIEFLAYOUT RULES16 AD ASSETS
16
ad images per run, from one spec
4×4
products by placement ratio
0
manual resizing

The problem

The job was resizing. Four products at 1:1, 4:5, 9:16 and 1.91:1 is sixteen versions of the same layout done by hand, and the headline ends up a different size on every one. So I stopped fixing outputs and wrote the thing that makes them.

How it works

The drawing was not the hard part. The layout solver was. Text scales itself down until it fits inside each placement's safe area, and the payment marks are generated at render time rather than pasted in, so they are always the right size for the frame they land in.

Four of the sixteen rendered ads: 1:1, 4:5, 9:16 and 1.91:1, same brief, same brand elements, no manual resizing
Four of the sixteen. One brief, one spec, four placement ratios. Nothing here was resized by hand.

The bit that does the work

render_set.py Python · PIL
 1def autofit(t, fn, start, mw, tr=0):
 2    """shrink until the string fits the safe width"""
 3    z = start
 4    while z > 10:
 5        f = F(fn, z)
 6        if tw(t, f, tr) <= mw: return f
 7        z -= 2
 8    return F(fn, 10)
 9
10def text(cx, y, t, f, fill, tr=0, box=None):
11    x = cx - tw(t, f, tr) / 2  # centre on the axis
12    if tr == 0: d.text((x, y), t, font=f, fill=fill)
13    else:  # manual letter-spacing
14        xx = x
15        for c in t:
16            b = d.textbbox((0, 0), c, font=f)
17            d.text((xx, y), c, font=f, fill=fill)
18            xx += b[2] - b[0] + tr
19
20    # every drawn element is also written to a spec,
21    # which is what makes the .pptx export possible
22    SPEC.append(dict(
23        kind='text', text=t, cx=cx, size=f.size,
24        color=HEX(fill), tracking=tr, weight=weight))

The layout solver: start big, measure, step down until the text fits the frame's safe width. Every element it draws also gets appended to a spec list, which is what lets the Node script rebuild the whole thing as an editable file.

What I learned. The interesting problem usually sits one level below the one you were handed. I was asked for ad variations and what I actually needed was a constraint solver. A tool nobody else can open is a dead end too, so exporting to an editable file mattered as much as the rendering did.