Eli Heuer’s Blog

Virtua Grotesk: Grid as Dataset

index

Virtua Grotesk is a typeface I am designing on a powers-of-two grid, and if you are reading this on elih.net it is the typeface you are reading right now. It is also a machine-learning experiment: the same grid that gives the font its aesthetic makes its source files unusually good training data, and a small neural network is now learning to draw it. This post is about both the typeface and the neural network, and why they are the same idea. The font sources are on GitHub at github.com/eliheuer/virtua-grotesk, free under the SIL Open Font License.

The word Grid set in Virtua Grotesk as a specimen dimension sheet: red glyphs on the 16-unit design grid, blue vertical-metric lines marking cap height 768, x-height 576, baseline, and the 16-unit overshoots, with advance widths and hatched side bearings dimensioned below.

Section Index

01. A Font Is a Program

A digital font is not a set of pictures, it is a small program you can read. Each glyph is text, a short list of drawing instructions (start here, curve to there, close the path).

Designing the system instead of the finished thing is an old idea in design. Karl Gerstner made the argument in 1964, in a book called Designing Programmes. Its opening essay was titled “Programme as Typeface.” The program, not the individual letter drawings, is the work.

Most fonts, until very recently, were not designed to be training data for neural networks or to work well in agentic AI harnesses. Their coordinates land wherever a designer’s hand put them, at whatever resolution the editor defaulted to, contours as individual as handwriting. That’s fine, the rasterizer doesn’t care. But a neural network does, and the current wave of font AI trains on exactly that accidental data: scraped fonts, Google Fonts, heterogeneous in every dimension that matters.

02. Replica and the Coarse Grid

The purest precedent to what I am doing with Virtua Grotesk is LL Replica (Norm: Dimitri Bruni and Manuel Krebs; Lineto, 2008). Norm took the drawing grid their font editor provided and made it ten times coarser. FontLab provides a 700-unit grid, and instead of drawing on that 700-grid they allowed themselves a 70-grid. Fewer legal positions for every node and every bézier handle. Deliberately less freedom.

Two details make Replica more than a constraint stunt. First, the bevels: Replica’s corners are cut, and the cut is exactly one grid unit wide, so the grid isn’t just a discipline, it’s visible in the letterforms. Second, the cut diagonals: A, K, R have no pointed apexes, so the letters can be set tight. The constraint produced the aesthetic; the aesthetic advertises the constraint.

03. The Virtua Grotesk Design System

Virtua Grotesk is drawn against powers of two, not the base-10 grid of Replica. The full normative spec lives in the repo as DESIGN.md; these are the load-bearing numbers:

measurementvaluenote
UPM10242^10
drawing grid2 unitsprefer even coordinates
ascender768shared with cap height
cap height768512 + 256
x-height576512 + 64
descender−256−2^8
chamfers16 unitsthe signature corner cut
favored measurements2, 4, 8, 16, 32, 64, 128or short sums of them

One rule keeps it humane: optical corrections. The grid is the starting point, not a cage. When a curve needs a two-unit nudge to look right, the eye takes precedence, but corrections are understood as small, even-numbered deviations from a power-of-two intent, so the intent stays recoverable.

04. From Aesthetic Discipline to Machine Legibility

Andrej Karpathy calls a large language model a zip file of the internet: billions of parameters that compress terabytes of text into a lossy gestalt. A trained model is a compression of its training data.

Type designers already build compressors. A variable font ships a base outline plus a set of interpolation deltas and reconstructs every weight on demand. A neural network trained on a typeface is that idea taken further, a learned compression of the design that moves through a space it found for itself rather than one a designer drew by hand. The grid’s discipline pays off two ways:

Consistency is signal. Same stroke logic, same chamfer size, same start-point conventions across every glyph: the model spends its capacity learning the design system instead of averaging over five hundred designers’ bézier habits.

Constraints make outputs checkable. If every legal coordinate is even and every stem is a power-of-two sum, then a generated glyph can be verified mechanically: off-grid points are detectable and snappable, wrong stem weights are measurable. Generation quality stops being vibes and becomes a test suite.

The clearest case is arithmetic. Language models are bad at it largely because tokenization is arbitrary: 127 is one token, 677 is two, so the model does math on a representation that keeps changing shape. Virtua’s grid is the opposite. Every coordinate tokenizes exactly one way, one canonical quantization with nothing for the model to fight.

I think this is where type is heading. An outline font is a compression scheme for the Latin idea of a letter, a fixed shape reused everywhere, and it strains at scripts built differently. Arabic in its manuscript forms is a continuous, context-dependent stroke, and today’s workaround is thousands of glyphs and rules faking what a hand does in one motion. A generative font, a small network that has compressed the typeface and draws each glyph in context, handles that natively, and I would bet the first ones are built on a grid like this.

Replica made the grid visible to people. Virtua makes it legible to machines, the same modernist move with a 2026 reason under the 1957 one. The grid is back, and this time the audience includes the machines.

05. Glyphs as Sentences

The title is the method. A glyph is already a small program, a list of drawing commands, which makes it a sentence: symbols in order under a grammar, exactly what transformers are built to predict. So instead of a picture of a letter or an unordered bag of points, I hand the model each glyph as the sentence it already is and train it to speak outlines.

The source is a UFO, a folder of XML files, one per glyph, storing each outline as points on the grid. A transformer can’t read a tree of XML; it reads a flat sequence of tokens from a small vocabulary. So a small codec reads each glyph’s outline out of the UFO and rewrites it as one flat line: the drawing commands and their coordinates, nothing else. The format is mine but not arbitrary, just the font’s own drawing operations in order, with the XML thrown away.

The vocabulary is 1,336 tokens of three kinds. The structural markers (MOVE, LINE, CURVE, CLOSE) are the drawing verbs. The conditioning tokens for weight and glyph name are the prompt, they tell the model what to draw before it draws. And there is one token per legal grid coordinate. The numeral 2 in the Regular weight comes out like this:

BOS N_two W400 ADV 560
MOVE 64 0
LINE 64 96
CURVE 64 232 148 328 292 328
...
CLOSE
EOS

Read it top to bottom. BOS opens the sequence and EOS ends it. N_two names the glyph, W400 sets the weight (Regular), and ADV 560 is its advance width, how far the pen moves before the next character. The rest is the outline itself: MOVE starts a contour at a point, LINE draws a straight segment to the next, CURVE draws a cubic bézier through two control points to an endpoint, and CLOSE shuts the loop. Every bare number is a grid coordinate.

That last part is where the grid earns its keep. At the same UPM of 1024, a normal font lets a point sit on any of a thousand-odd positions, and because outlines are drawn by hand, the same edge lands a unit or two off each time it recurs. Virtua’s 2-unit grid keeps only every other position, and the designer commits to them: a stem is 96 every time, never 95 or 97. So the model sees a small alphabet of coordinates, each common and each meaning the same place, instead of a wide scatter of near-duplicates. Same em square; what the grid changes is how many positions are legal and whether the design reuses them.

The codec runs both ways. To train, it turns every glyph into a sequence and the model learns to predict the next token, across every glyph in both weights. To generate, you hand it the opening tokens, the name and weight you want, let it finish, and run the codec backward into a UFO outline, a real, editable glyph. It round-trips with zero loss, because the grid already did the quantizing at design time.

Training on the raw .glif files would waste the model on XML scaffolding, and their bare coordinates would hit the tokenization problem from the last section. There is no rasterization, no image encoder, no diffusion here: the font source itself, stripped to the drawing, is the training data, about 80 tokens for a median glyph.

This is the part that training on found fonts can never have. When the data is quantized after the fact, snapping destroys the designer’s intent by some unknowable amount. When the font was drawn on the grid, the tokens are the intent.

None of the machinery here is new. Tokenizing outlines as drawing commands and learning them with a transformer is well-trodden: DeepSVG, DeepVecFont, IconShop, and StarVector all do a version, several quantizing coordinates to a small integer grid exactly as I do. What none of them can do is begin from data that was grid-native. They train on found fonts snapped down in preprocessing, so the quantization is lossy and the corpus is a scrape of a thousand disagreeing hands. Simon Cozens, surveying the field, names both problems in his own words. On the representation he finds that “vectorization of glyph images has been historically very bad.” On the data, his own experiment is the evidence: he trained a model on the Google Fonts dataset and reports that “it completely failed,” the tokenizer collapsing to a single all-white output. Of the field as a whole he writes that “nobody from type-world has had any input into this at all.” Grid-as-dataset answers both. The representation is borrowed; the data is the contribution.

A running theme in Andrej Karpathy’s work (he built a whole minimal tokenizer, minbpe, and gave a lecture making the point) is that much of what’s strange about language models traces back to tokenization. Text has no native quantization, so we impose one after the fact with byte-pair encoding, and the seams show up as model weirdness. A grid-native font never has that problem: the quantization happened at design time, chosen by a designer for design reasons. The tokenizer here is not a learned compromise. It is a ruler.

06. A Small Model Learns to Draw

The model is deliberately small: a 12M-parameter decoder-only transformer, written in MLX, trained on an M4 Pro MacBook with 48 GB of RAM. No cloud, no GPU cluster: an overnight run is about 30,000 steps. The corpus is Virtua’s two masters: 427 glyphs with their own contours, structurally identical between Regular and Bold.

The lineage should be obvious to anyone who has followed Karpathy’s from-scratch projects. In 2015, char-rnn generated C code with balanced braces; watching this model learn to close contours is the same unreasonable effectiveness pointed at bézier space. His makemore generates names character by character; this generates glyphs point by point. And his latest, microgpt, trains a 4,192-parameter GPT over a 27-token vocabulary (32,000 names, one minute on a MacBook) as the distilled essence of the whole stack. This project is microgpt’s cousin in another alphabet: 1,722 tokens, glyphs in, glyphs out, forty minutes on an M4 Pro. In fact, the honest origin of this whole design system is that I got the powers-of-two idea while watching Karpathy videos and thinking about what a typeface would look like if it were designed to be tokenized.

Training follows his Recipe for Training Neural Networks more closely than I planned. The first-step loss lands where cross-entropy over a uniform distribution says it should, the recipe’s first sanity check. The first overnight run drove training loss to 0.07, which at ~340 epochs over a few thousand sequences is memorization, not learning; the fix was the recipe’s: augment (continuous interpolation weights, contour-start rotation), add dropout, and hold out a validation split that gets the final say. The recipe step I still owe is the dumb baseline: for weight transfer, “add the average training delta to every point” is the number to beat, and I haven’t measured it yet. When I publish the model, the baseline ships with it.

The first test is glyph completion: give the model 40% of a held-out glyph’s outline and ask it to finish. Below, the model completes the letter R, a glyph it never saw in any weight during training. It gets the stem, the bowl, and the overall proportions:

Glyph completion of the letter R, drawn with designbot: gray reference, the green 40% given to the model, and the model's red completion on the powers-of-two grid. Preliminary model output, refreshed each training run.

It is not production quality: interior counters come out mangled, and roughly one sample in three goes somewhere strange. But this is one font family, a few thousand training sequences, and one night of laptop compute. The signal is unmistakable, and in machine learning, signal at toy scale is the thing you scale.

07. Boldening as Local Prediction

The second task is the one with immediate production value: given a Regular glyph, draw the Bold. In font production this is real, tedious work: every glyph drawn once must be drawn again heavier, with the same structure, or the variable font breaks.

The encoding is where the design system pays off again. Because Virtua’s masters are point-compatible, I interleave them per point (MOVE x_reg y_reg x_bold y_bold), so every Bold coordinate sits directly after its Regular partner in the sequence. Boldening stops being a long-range translation problem and becomes local prediction. Better: at generation time the Regular coordinates are forced from the input, and the model only fills the Bold slots, so the output Bold cannot structurally diverge from the Regular. Master compatibility isn’t a QA check anymore; it’s a property of the representation.

Regular-to-Bold weight transfer on a held-out g, drawn with designbot: the green Regular input, the model's red predicted Bold, and the gray actual Bold on the powers-of-two grid. Preliminary model output, refreshed each training run.

Current state, honestly: g is the hardest glyph in the set, and the predicted Bold above is still mostly noise. Simpler shapes already come out with the right mass and stems; the curves are where it breaks down. A few days ago all of it was noise. The next lever is delta-encoding: predicting each Bold coordinate as a small offset from its Regular twin, which shrinks the effective vocabulary for the task to a few dozen offsets. On a grid, boldening is almost arithmetic.

08. The Designspace Is a Data Factory

Two masters sounds like no data at all. But type design has been quietly synthesizing valid data for decades: every interpolation between compatible masters is a legitimate instance of the family. Each training batch samples a continuous weight between Regular and Bold, snaps it back to the grid, and gets a “new” glyph, with its exact parameters attached as conditioning tokens, because synthetic data comes with labels found data can’t have. Add contour-start rotation (any cyclic rotation of a closed contour draws the same shape) and one family amplifies to effectively unbounded training sequences.

This generalizes beyond one font. img2bez, my Rust autotracer, snaps traced outlines to a 2-unit grid and writes UFO sources, which makes it a grid-ifier: render any OFL font, trace it, and it comes out speaking Virtua’s coordinate language. That is the long-term data plan: a growing corpus of grid-native families, every one of them OFL, with a provenance manifest I can publish. No scraping, no gray areas: training data you could audit glyph by glyph.

Karpathy’s Software 2.0 essay gives this a name. If the weights are the program and training is the compiler, then the dataset is the source code, and “grid as dataset” is what happens when you take that seriously from the source-code side. Write the data the way you’d write software: a style guide (DESIGN.md), a linter (the grid is mechanically checkable), code review (mark colors in the UFOs), version control (the masters live in git). A foundry that designs its own training data is just a software shop that keeps its codebase clean.

09. One Pipeline

The pieces are converging into one system: img2bez traces raster references into grid-snapped UFO sources; the glyph model completes, boldens, and (soon) repairs outlines; designbot renders proofs and the figures in this post; Runebender is the visual review surface; and an agentic harness in the Virtua repo orchestrates all of it against DESIGN.md, with mark colors in the UFO as the human control channel. The goal of the whole apparatus is unglamorous and concrete: finish Virtua Grotesk and ship it to Google Fonts, with the neural net doing real production work (the Bold companions, the tedious completions, the cleanup passes) and a human doing what humans are for.

10. What Ships Next

Three deliverables, in order. First, Virtua Grotesk on Google Fonts: the font has to be real before the system that made it is interesting. Second, the glyph model on Hugging Face with a proper model card: one family, small weights, honest limitations, so anyone can reproduce the experiments. Third, the grid spec as a document other type designers could adopt, because the thesis only compounds if there is more than one grid-native family in the world.

And a fourth deliverable is hiding inside this draft. Tariq Rashid’s Make Your Own Neural Network taught a generation their first network with handwritten digits; Karpathy’s Zero to Hero rebuilt GPT from atoms with a names dataset. I want to write the type-design version: a from-scratch introduction to neural networks where the “hello world” is a font: you train a small model on a laptop, it does real work finishing a real typeface, and at the end you install the result. Digits and names are good toy domains, but they evaporate when the lesson ends. A font is a toy domain that ships. As the model and Virtua converge, this draft becomes that piece.

If any of this is interesting to you (the type design, the grid system, the tiny models), the repos are open, the font is OFL, and the draft you just read wants criticism.

11. A Hidden Treasure: The Mysticism of 2ⁿ

Everything above treats a font as a program and a glyph as a sentence. This is the part I can’t put in a diagram. We taught sand to think: we melt quartz, etch it, and it holds a conversation with us in a language of two states, on and off, one and zero. Every glyph the model draws is that single distinction repeated, powers of two all the way down.

The number is old, and it keeps returning. The I Ching is sixty-four hexagrams, two to the sixth, six stacked lines each broken or whole; the video game Animal Well hides exactly sixty-four eggs, the same completeness wearing a different mask; in 1701 Leibniz saw his own binary arithmetic in the hexagram diagram and took the coincidence as a glimpse of the mind of God. Terence McKenna said the spirit world is made not of matter but of a language you behold instead of hear, a logos you see rather than speak. Seekers have always hunted for God’s arithmetic in scripture, the nineteens of the Quran, the letter-sums of the Kabbalah. My number is humbler than any of them. It is two.

I can’t defend this in English, and the failure is the point: some things are only sayable in form. An old hadith has God say he was “a hidden treasure” who loved to be known, and so made a world to be seen in, and I think that is the whole job of a typographer. The Báb taught that every thing should be raised to its highest beauty, that refinement is a form of worship, that a thing perfected is not deprived of its own paradise. So maybe a letter drawn cleanly enough on a grid of twos can carry what the sentence cannot. Maybe the aesthetics are the argument, and if the font is beautiful enough, it says the thing about God that I can’t.

12. Build Log

This is a running log of the actual work, in plain language, newest entry last. The sections above describe the ideas; this section is what happened.

2026-07-08: One glyph at a time, and a scoreboard.

The early neural-net experiments taught me a lesson the hard way: when I pointed the model at all 147 glyphs it had never seen boldened, it produced mangled garbage. So I simplified. The current rule is: do one glyph correctly, then build on it.

The task is bootstrapping the Bold master. Virtua has a finished Regular and a Bold that is mostly placeholder copies. The tool that is working now does form transfer: the Regular outline supplies the structure and style (which points exist, which edges are vertical), and a reference image (a picture of the glyph in some bold typeface, chosen by me, one glyph at a time) supplies the form and weight. The fitter is only allowed to slide each edge in or out along its own perpendicular. It cannot rotate an edge, so verticals stay vertical. It cannot move anything off the baseline or the cap height, because those are pinned. The small corner cuts that give Virtua its character are never fitted to the image at all: they are re-cut at their designed size wherever two edges meet, because the design system outranks the pixel evidence. And everything lands on the grid, always.

Before trusting the tool on a real image, it has to pass exams where the answer is known: scramble a glyph’s edges by random amounts, render it, and ask the tool to recover the scramble from the picture alone. It recovers it exactly, zero error, or it doesn’t ship. The exclamation mark passed first. The figure 4 passed second, and on the way it exposed a real bug the exams then verified fixed. Two glyphs sounds like nothing; two correct glyphs, with an exam system that catches regressions, is the whole point.

The other new thing is boring and important: a skeleton and a scoreboard. The skeleton is one command that runs the entire factory end to end (compile the fonts, run Google Fonts’ QA suite, regenerate reports) today, while most of the font is still debt. The first time it ran it caught a broken draft glyph that would have silently blocked the variable font build. The scoreboard reduces the project to one number: glyphs finished in both masters, currently 7 of 690. Every work session should move that number. When it reads 690, Virtua ships to Google Fonts, and its sources become the first grid-native training set.

2026-07-08: Two grids, two species.

Grading machine drafts in Runebender surfaced the clearest statement yet of what this design system is: the 8-unit grid belongs to the machine, the 2-unit grid belongs to the human. Advance widths, stem widths, chamfer sizes (the measurable skeleton of a glyph) should always land on multiples of 8, and putting them there is rule-following that software must do perfectly. Dropping from the 8-grid to the 2-grid is something else entirely: an optical correction, a 2-unit nudge made because a shape looks wrong to a trained eye even though the math says it’s right. That judgment is what the biological human is for.

I was spending grading time snapping machine output back onto the 8-grid: janitorial work that proves a tool is broken. So now the whole stack enforces the split: the fitting tool only moves things in 8-unit steps, a lint screams if a machine draft contains 2-grid noise, and my time in the editor is reserved for the corrections only eyes can make.

The machine-learning consequence is the best part. Because drafts arrive on the 8-grid, every 2-unit deviation in a glyph I’ve approved is, by definition, a human optical decision: recorded, labeled, and sitting in git. The difference between what the machine proposed and what I approved is a growing dataset of pure typographic judgment. That’s the thing you can’t scrape.

And to be clear about where this is going: the split is a curriculum, not a permanent wall. The goal is automated type design: a model that learns how, why, and where I make optical corrections and starts making them itself, staying on the 8-grid except where the training data shows a correction is needed. Realistically, that happens in stages: first the model just learns to point (“these four points probably need your eyes”, useful even with a small corpus), then it proposes corrections and we measure how often I accept them, and only the categories it reliably gets right graduate to automatic. Two things never automate: final approval, and corrections with no precedent in the data. The honest test it has to beat first: a model that predicts “no correction anywhere” is right about most points, so it has to prove it knows where the exceptions are, not just play the averages.