← All posts
Design
1 min read

Drawing social images from a variable font

The redesign needed eleven new social images, from a 600px email header to 2560px channel art. They're drawn by one Python script from the site's own font, and the first run came out in the wrong weight entirely.


After the redesign, the link-preview image and every social banner still showed the old lime-on-black design. Eleven sizes, from a 600×200 email header to 2560×1440 channel art. Drawing them by hand in each size is how brand assets drift, so they’re generated by one script.

Same font as the site

The script uses Pillow with the same Archivo variable font the site ships. The font comes as woff2, which Pillow can’t read directly, so fontTools decompresses it in memory first:

f = TTFont(SRC); f.flavor = None
buf = io.BytesIO(); f.save(buf)

The axis order bug

The first render came out thin and extra-wide: exactly the opposite of heavy and condensed. set_variation_by_axes takes a plain list of values, in the order the font declares its axes. I’d passed [width, weight]; Archivo declares weight first. So “width 75” was read as a weight of 75, and “weight 800” became a width, clamped to the widest the font goes.

The fix reads the order from the font instead of assuming it:

axes = [a.axisTag for a in f["fvar"].axes]
ft.set_variation_by_axes([{"wght": wght, "wdth": wdth}[t] for t in axes])

Layouts by shape, not by name

Rather than a layout per network, the script picks by proportion: very short banners get the mark, the name and a row of crossed-off boxes; wide ones get the headline with a week strip; tall ones stack. New sizes need no new code, only a new file name.

AlzyWelzy

Building PaceStreak. GitHub

Related

Build the habit. Keep the week.

PaceStreak is a habit and streak tracker for training and everything else worth doing regularly.