On Fitting Fonts

Apr 16, 2010 02:25


A little while back I mentioned that I was working on a custom programming font. It's been rather educational and I've learned a ton of trivia about computer fonts. By far the most interesting thing is that drafting the shape of each glyph has turned out to be the easy part. What's the hard part? Grid fitting (a.k.a. hinting or instructing).

Here's the uppercase D from my font, shown rasterized for a particular resolution and without any grid fitting:



Bi-level rasterizing a TrueType font is remarkably simple minded in the basic case: pixels whose centers fall inside the outlines get turned on, those that fall outside are left. (There's a bit more to it than this for handling thin parts.) The problem, as you can see is that this tends to look awful when the outlines aren't aligned to the pixel grid. Antialiasing gives better results, but now it's all fuzzy:



Ideally, it would be nice for the control points in the outline to somehow get shifted to the right places on the grid to give a good rendering for each resolution:


 

Of course, the devil is in the details. So how does that happen? In a TrueType font, it's done with a virtual machine defined by a rather arcane spec! It turns out that each glyph in a font can have its own little bytecode program that tells the interpreter how to jiggle its control points around so that it looks good at each size.

The nice thing is that this gives the font designer tremendous control over how their font appears on screen. The downside is that doing it properly is a lot of work. It means writing and testing a little program for each character.

There are some partial shortcuts with varying tradeoffs in time and quality. Most good font creation tools now provide an autoinstructor that will try to create these automatically, and FontForge, the tool that I've been using is no exception. These are fast, but the quality can't compare to a good handwritten set of font instructions. Some commercial programs offer an intermediate level by letting you graphically define the relationships between points and then synthesize code for you from that. Surprisingly there's no opensource tool for this.

I've also seen one or two higher level languages that insulate one a bit from the bytecode, but I found that they aren't really higher level in a useful way and they compile down to some atrocious code. Too bad.

In the end, I've settled for coding up a preprocessor tool of sorts in Python. There's still a fairly one-to-one correspondence between the source and the TrueType instructions, but my program translates coordinates to control point indices (it's easier for me to read coordinates, and they're stable at this point while their indices are not) and labels to control value table indicies. Groups of constants to inside parenthesis automatically push the length of list after the constants themselves so that I don't have to deal with counting them and keeping the count updated. What I'm particularly proud of is that it can do some light optimization -- in particular the byte code is stack based and my preprocessor will perform safe coalesces of pushes of constants for me. Here's what I wrote to do the grid fitting for that uppercase D above:

SVTCA[x-axis] <160,0> MDAP[rnd] <160,1600> ALIGNRP <320,160> "stem" MIRP[rp0,min,rnd,grey] <320,1440> ALIGNRP <960,800> MDAP[rnd] FLIPOFF <960,0> "o800-400" MIRP[] <960,1600> "o800-400" MIRP[] FLIPON <800,800> "stem" MIRP[rp0,min,rnd,grey] FLIPOFF <800,160> "i640-240" MIRP[rp0,grey] FLIPON <800,1440> ALIGNRP <160,0> SRP1 <960,800> SRP2 ( <560,0> <560,160> <560,1440> <560,1600> ) SLOOP IP SVTCA[y-axis] <160,0> SRP0 <320,160> "stem" MIRP[rp0,min,rnd,grey] ( <560,160> <800,160> ) SLOOP ALIGNRP <160,1600> MDAP[rnd] ( <560,1600> <960,1600> ) SLOOP ALIGNRP <320,1440> "stem" MIRP[rp0,min,rnd,grey] ( <560,1440> <800,1440> ) SLOOP ALIGNRP <160,0> SRP1 <160,1600> SRP2 ( <800,800> <960,800> ) SLOOP IP
...and here's the byte code that turns into, using FontForge's mnemonics:

40 | NPUSHB 26 | 38 0a | 10 04 | 4 01 | 1 00 | 0 0c | 12 0b | 11 0d | 13 1b | 27 02 | 2 03 | 3 01 | 1 08 | 8 09 | 9 07 | 7 1b | 27 00 | 0 06 | 6 08 | 8 0c | 12 02 | 2 04 | 4 04 | 4 00 | 0 0b | 11 09 | 9 20 | 32 0a | 10 1b | 27 03 | 3 26 | 38 05 | 5 26 | 38 04 | 4 0d | 13 07 | 7 1b | 27 01 | 1 00 | 0 01 | SVTCA[x-axis] 2f | MDAP[rnd] 3c | ALIGNRP fc | MIRP[rp0,min,rnd,grey] 3c | ALIGNRP 2f | MDAP[rnd] 4e | FLIPOFF e0 | MIRP[grey] e0 | MIRP[grey] 4d | FLIPON fc | MIRP[rp0,min,rnd,grey] 4e | FLIPOFF f0 | MIRP[rp0,grey] 4d | FLIPON 3c | ALIGNRP 11 | SRP1 12 | SRP2 17 | SLOOP 39 | IP 00 | SVTCA[y-axis] 10 | SRP0 fc | MIRP[rp0,min,rnd,grey] 3c | ALIGNRP 3c | ALIGNRP 2f | MDAP[rnd] 3c | ALIGNRP 3c | ALIGNRP fc | MIRP[rp0,min,rnd,grey] 3c | ALIGNRP 3c | ALIGNRP 11 | SRP1 12 | SRP2 39 | IP 39 | IP
So instructing the letters is something of a tedious slog, and before I could even do that I spent a lot of time surveying existing programs and writing a few preliminary programs of my own while searching for a way to make a high quality grid fit easier on myself.

In the end, I don't think there's really a good shortcut. I suppose I could just have copped out and stuck with the autoinstructor. But I'm a perfectionist and good grid fitting is what separates the amateurs from the professionals in the digital font world. I certainly understand now how there's a rare few who's entire job is simply to instruct fonts.

I'm taking my time, but when I finally release this font, I'd like it to be among the list of those that always seems to crop up whenever people discuss good programming fonts.

Previous post Next post
Up