← eden_hadad

presentation-generator

AI to editable PowerPoint, without the model ever touching a pixel.

stack: python · pptx / ooxml · pydantic · claude

role: sole builder

// the problem

Ask an AI deck tool for ten slides and you get mush: headings that drift between runs, cards that almost line up, fonts that change their mind mid-deck. The cause is always the same — the model is allowed to freehand pixel positions, colors, and sizes, and language models are bad at pixels the way poets are bad at accounting. The decks also usually arrive as flattened images you can't edit.

// what I built

A system with a hard wall down the middle. On one side, Claude — allowed to produce only semantic content: headings, bullets, card text, and a layout choice from a fixed vocabulary of nine (title, section header, bullet list, three cards, two columns, split-image, full quote, and so on). On the other side, a deterministic Python renderer that turns those specs into native PowerPoint shapes by writing raw OOXML, with every visual decision — spacing, typography, corner radii, gradients — coming from an immutable design system. Same input, identical pixel-perfect output, every run. And it's a real .pptx: every text box editable.

// how the semantic wall works

The contract is enforced at three layers. Pydantic schemas define exactly what the model may say — a card spec has an icon label, a heading, and a body, and no optional styling fields exist, so if the model tries to sneak in a color the output fails validation before the renderer ever sees it.

The renderer is a vertical flow machine: content starts at a fixed top margin, headings claim their space, fixed gaps follow, and remaining width divides equally among cards. Nothing is positioned "about here." Corner radius is computed per shape — radius / (min_dimension / 2), clamped — so a 20px radius looks right on a wide card and a narrow column without anyone tweaking it.

Backgrounds are pre-baked OOXML strings. The model picks one of three by name; the renderer splices in the full XML. There is no path through the system where AI output becomes a visual decision.

// what broke

OOXML is a format where rounding a rectangle's corners means computing a percentile adjustment value against the shape's smaller dimension and writing it into prstGeom by hand. Clipping photos to rounded corners took direct surgery on the geometry XML. And some constants confess their origin story: the micro-label letter-spacing is 1.28pt — exactly +8% of 16pt — a number you only arrive at by rendering, squinting, and rendering again.

// where it is now

Working, public, and usable three ways: a CLI (generate "Topic" --slides 8), a small web UI with logo upload, and a spec-first workflow where you generate the JSON, edit it by hand, and render. A deck takes about 10–20 seconds. The whole thing is 1,453 lines of Python, and roughly half of them are the renderer holding the line.

// © eden_hadad · edenhadad.com