Stroke-based text rendering in XNA
There are some nice texture-based solutions out there for drawing text in XNA. I've used the font code in XNAExtras a couple times, and it works fine. There's another font utility on Ziggyware that looks nice, and I believe there are others too. There's also talk of an official font solution being added to XNA at some point.
For the kinds of XNA programs I'm playing with, I would prefer a font solution that is resolution-independent and shader-based. All my other game elements are specified in world space coordinates, so I don't want to switch over to screen space coordinates to draw text, or have the text look relatively tiny when my game is at high resolution. And I want to be able to do the same kind of "fun" effects with text that I can do with everything else.
So I have a couple ideas on how to do scalable fonts, but today's post is about using stroke fonts as opposed to other kinds. Stroke fonts are pretty rare these days; I think they were primarily designed for use with pen-based plotters. The idea is to draw the font's glyphs with a series of strokes of a virtual (or actual) pen. Since I've written some XNA code to do thick, rounded lines, stroke fonts sounded like a natural match.
With a binary editor, I opened up the "modern.fon" file, one of the three stroke fonts that (still) ship with Windows, and with help from this page I was able to extract most of the font information. But I couldn't find any documentation on how to interpret the stroke data, so I had to put my little brain to work to figure that out. If you want a little puzzle, see if you can crack the code from a couple examples:
- ! 16 bytes: 800504000e800005ff01010101ffffff
- " 10 bytes: 80040400078008f90007
- # 20 bytes: 800b00f920800de0f92080faed0e0080f1060e00
- $ 51 bytes: 800800001d8004e3001d8005eafefefdfffc...
(See StrokeFont.AddCharacter() in StrokeFont.cs for the solution.) Anyway, I got it working and I'm fairly happy with the result.
- Nice and scalable!
- Flexible: I can do my usual mischief with vertex shaders and pixel shaders to make text look interesting and "game-like."
- Tight: You can probably tell by now that I like small programs that do a lot, so a complete font solution in 500 lines (and no external resources needed) makes me happy.
- You can change the font boldness to whatever you like by changing the line thickness.
- With a script font, it would be possible to draw text slowly and incrementally (as if by an invisible pen) for dramatic effect.
- Drawing curvy glyphs with straight lines is pretty inefficient, especially with my rounded caps at both ends of every segment. I'm sure my code is much slower than just splatting down a textured quad per character, but for the kind of programs I'm doing, Windows and the Xbox 360 have so much graphics horsepower to spare that I'm not concerned about it, especially for the small amount of text I'll be drawing (e.g., "Game Over" or "Score: 56,000"). The demo app draws a lot of text, so it's a bit clunky.
- If you zoom enough, the tessellation of curved characters is visible.
- Most of my line effects look bad on the stroke text, due to the overlap between line segments. I'm planning to write some different pixel shaders that do effects based on the transformed position, rather than on each line's rho/theta -- that should avoid those issues.
I added one thing to my Line class: the Draw method now takes a transformation matrix that applies to the entire lineList. So you can build a lineList representing some text, then easily transform that whole block to size/position it in worldspace.
If you don't need the characters from 0x80 to 0xff (accented characters and some less-used symbols), you can cut down the size of StrokeFont.cs even further. Search the code for "0x80+" to see tips on what lines to delete.
The StrokeFontDemo program (attached) has the same controls as the LinesDemo program, with one addition: use the Y axis of the right stick (or D/C keys) to change the line thickness.
I'm still working on a game based on the Microbes code...I'm hoping to wrap it up soon and post it here.