Skip to the fonts
Portfolio
David Hatleydesign · AI/ML · generative tools

No. 01 / 16 · PixelSpecimen · 2026-10-27

Cap 2100X 1200Base 0Desc −600Outline

The pixel font drawn on the grid of my mark. Five sizes, zero grey pixels.

Glyphs 121Styles 3Kerning 181 pairsUPM 2400Cap 2100x 1200WOFF2 3.4 KBTTF 12.6 KBSizes 16 · 32 · 48 · 72 · 120Checksum 1C92
00Type tester

Try it

Every edge on a whole pixel

72 px · 9 px cells · kerning on · click the text to edit it

01Construction

How it is built

A pixel font that refuses to blur. Set it at 16, 32, 48, 72 or 120 px and every edge lands on a whole pixel. Other sizes work too. They just look disappointed in you.

Regular, a Bold drawn by hand, and Grid, a variable face that draws every cell as its own square. Tabular figures, curly quotes, dashes, arrows, the dh mark (type dh) and seven project glyphs in the private-use area. Version 1.001.

One cell is the font size divided by eight, so at 16, 32, 48, 72 and 120 px every edge lands on a whole pixel. The tester and the waterfall only offer those sizes.

PLATE 1.1 · CONSTRUCTION · STEM 7 g12 ON-CURVE · 0 OFF-CURVE · STRAIGHT
Baseline 0x-height 1200Cap height 2100Descender −6001234
  1. NodeAn on-curve point at (0, -600). 12 in this glyph.
  2. Cell300 units, an eighth of the em: 2 px at 16, 15 px at 120, never a fraction.
  3. Advance1500 units wide: 0 of space on the left, 300 on the right.
  4. The mark's barThe bar of the dh mark sits on row 4, so the x-height is 4 cells (1,200 units) and every bar, dash and arrow sits there too.
Plate 1.1. Stem 7 g, drawn from the built font file: every square is a node and every ring a handle the outline really has.
1.1How it was made

It started as a logo

My mark is a d and an h sharing one stem, drawn on 7 × 7 cells. Its stem set the cap height, its bar set the x-height, and the rest of the alphabet had to fit around it. Type dh in Stem 7 and you get the mark back.

PLATE 1.1 · THE MARK IS THE GRID7 cells to the cap

The mark · 7 × 7

Caps are 7 cells, the x-height is 4, the descender is 2. The bar of the mark sits on row 4, so every bar, dash, arrow and operator in the font sits there too.
1.2The source

Drawn in a text file

No font editor. Each glyph is nine rows of X and . in a plain text file, with the reason for the drawing written above it. Diffs are readable and every argument with myself is in version control.

PLATE 1.2 · SRC/REGULAR.S7
regular.s7

The built font at 120 px

One CLI reads these files and builds the TTF and WOFF2. It also lints the drawings, derives the kerning, renders proofs and counts grey pixels.
1.3The em

Eight cells to the em

The em is 2,400 units, or 8 cells of 300. So one cell is always the font size divided by eight, and at the five house sizes that comes out to a whole number of pixels.

PLATE 1.3 · ONE CELL = SIZE / 8UPM 2400
Font hinting is the usual fix for blurry pixel fonts. I tested it, and Chromium on Linux ignored the hints in every rendering mode, so the outlines do all the work themselves.
1.4The test

Zero grey pixels

A crisp pixel font has two colours: ink and not ink. Any pixel in between is the rasteriser guessing. This loupe renders the font in your browser right now and counts the guesses.

PLATE 1.4 · LIVE, IN THIS BROWSER
0grey pixels
Share of grey pixels by size, before and after
Build16324872120
The first build (1,000 UPM)63.5%35.5%25.9%18.4%0%
Stem 7 1.001, Regular00000
Stem 7 1.001, Bold00000
Try 20 or 25: the loupe marks every grey pixel. The table comes from the build's own test, which renders every style at every size in headless Chromium and fails the build if one grey pixel shows up.
1.5Grid

The one with gaps

Grid draws every cell as its own square, with a gap cut from the top and right. No single gap stays whole at every size, so Grid is a variable font: the gap is half a cell up to 40 px and a third of a cell from 48 px. Browsers pick it automatically.

PLATE 1.5 · STEM 7 GRID, OPSZ 8–160gap in px
At 16 and 32 px it is crisp but light, so it reads best from 48 px up.
1.6Revisions

Three rounds of arguing with myself

The first draft looked fine as an alphabet and fell apart in words. place read as ploce, and 64×64 had a lowercase x in the middle. Each fix below has its reason written into the source.

PLATE 1.6 · ROUND 0 → 1.001before · after
Grey is round 0, black is the shipped drawing. Most fixes came from setting real words and reading them at 1:1, not from staring at the alphabet.
1.7Bold

Bold, by hand

The bold draft is a rule: double every column that carries a stem. That works for H and falls apart on diagonals, curves and dots. 64 of the 120 bold glyphs were redrawn by hand.

PLATE 1.7 · AUTO-BOLD DRAFT → DRAWNchanged cells in cobalt
Lint now fails any Bold glyph with a stroke one cell thin, which is how the rule's misses were found.
1.8Spacing

Kerning in whole cells

Kerning comes from the drawings, not from my eyes at 2 a.m. For each pair, compare the facing edges row by row and average the gap. Under 3 cells: leave it. 3 to 5: pull in one cell. 5 or more: two. Never closer than one cell, and never into a mouth like C's.

PLATE 1.8 · ROW GAPS
02Spacing

How it fits

PLATE 1.2 · SPACING · STEM 7TIGHTEST 8 OF 320 PAIRS
12
FJ −600
LT −600
PJ −600
Cj −300
Ct −300
Cv −300
Fa −300
Fc −300
  1. UnkernedThe second letter where its advance width alone would put it: hairline.
  2. KernedThe pair as the font sets it, pulled in by the value under it, in units of a 2400-unit em.
Plate 1.2. The eight tightest pairs in Stem 7’s kerning table, each set over its unkerned ghost.
03Waterfall

Every size it is drawn for

120 px · 15 px cellEvery edge on a whole pixel
72 px · 9 px cellEvery edge on a whole pixel
48 px · 6 px cellEvery edge on a whole pixel
32 px · 4 px cellSphinx of black quartz, judge my vow.
16 px · 2 px cellSphinx of black quartz, judge my vow.
04Character set

All 121 characters

Char
g
Code point
U+0067
Glyph
g
Advance
1500
LSB · RSB
0 · 300
Contours · points
2 · 12

Uppercase · 26

Lowercase · 26

Figures · 10

Punctuation and symbols · 59

05In use

Good at

HUDs, counters, labels, numbers that change while you watch. Figures are tabular, so they don't jiggle. Not recommended for your novel.

HP12/12LV07
A game HUD at 32 px
00:00:00
A clock at 72 px. Tabular figures, no jiggle
nibblePROTOTYPE · 2026
A title card. The glyph is U+E003, in the font
PLATE 2.1 · TILE RULESDRAFT
SEED 4127
A plate header and seed strip at 16 px
$ stemctl crispRegular  16  0 grey  PASSRegular 120  0 grey  PASSall crisp
Build output, set in its own font
  • Coffee3.00
  • Toast4.50
  • Regret0.00
A menu board. Dot leaders not included
06Download

Get Stem 7

TTF for desktops, WOFF2 for the web, or the zip with both, the licence and a readme. Free for anything, commercial included, under the SIL Open Font License 1.1.

CSS · put the .woff2 next to your stylesheet
@font-face {
  font-family: "Stem 7";
  src: url("Stem7-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Stem 7";
  src: url("Stem7-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: "Stem 7";
  src: url("Stem7-Grid.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

h1 { font-family: "Stem 7", sans-serif; font-size: 48px; /* 16 32 48 72 120 */ }

Install straight from GitHub. No registry account involved.

Terminal
npm install github:davidghatley/stem7
Your CSS or JS entry
import "stem7/stem7.css";
HTML
<h1 class="stem7 stem7--48">Hello</h1>
07Small print

Changelog and licence

VersionWhat changed
1.001F now kerns against a, c, e, g, m, n, o, r, u, w and y. The source asked for it all along; the build quietly dropped those 11 pairs. A new test shapes every pair with HarfBuzz so it can't happen again.
1.000Regular, hand-drawn Bold and variable Grid. 0 grey pixels at all five sizes.

Free for anything, commercial included, under the SIL Open Font License 1.1. There is no reserved font name, so subset it, convert it, rename it. Just don't sell the font files on their own. The sources, build tools and tests are on GitHub.