After poking around the concepts of raster and vector images, I really wanted to dive a bit deeper into the most common form of vector image you will find today: fonts. Most of the fonts you run into today on a Windows system will be either True Type or Open type fonts, which are vector (outline) fonts. And, just like vector images you draw yourself (or metafiles that contain instructions to draw vector images), fonts have to be rasterized before you can display them on a screen. (Unless, of course, you procured your monitor from an old Asteroids video game machine, in which case it can render true vectors.) Rasterizing fonts has the same considerations as rasterizing any other vector images - when you map an infinitely precise mathematical line segment against a discrete pixel, you need software to determine what each pixel should display. With most vector images, it uses a generic algorithm. The results you obtain using this same generic algorithm against fonts can be disappointing, to say the least.

When you render text using Windows forms, you can specify the Text Rendering Hint on the Graphics object. I created a short program to iterate through the various options. This is the code that I used:

protected override void OnPaint(PaintEventArgs e) {
  Graphics graphics = e.Graphics;
  string text = "the quick brown fox jumped over the lazy dog";
  SizeF size = graphics.MeasureString(text, Font);
  using (SolidBrush brush = new SolidBrush(ForeColor)) {
    for (int i = 0; i < 6; i++) {
      graphics.TextRenderingHint = TextRenderingHint.SystemDefault;
      graphics.DrawString(((TextRenderingHint)i).ToString(), Font, brush, 
        new PointF(0, size.Height * i));
      graphics.TextRenderingHint = (TextRenderingHint)i;
      graphics.DrawString(text, Font, brush, new PointF(125, size.Height * i));
    }
  }
}

Let's take a look at what we come up with. For this discussion, I am not interested in the differences between various anti-aliasing options, such as clear type. Personally, I'm fanatical about turning on ClearType everwhere that I can, but that is beside the point. The differences I am interested in looking at are between the GridFit enumeration values and the rest. (You can think of the GridFit suffix as basically means to use font hinting. However, this isn't entirely true. For grid fitting, the font glyph outlines are actually being moved around to best fit the bitmap grid On top of that, font hinting will be used to make additional suggestions to the rasterizer as to how the glyph should be rendered.)

8-Point Segoe UI with Various Text Rendering Hints

As an aside, I am a big fan of the Segoe UI font, which is the system font for Windows Vista and is also the font used by the 2007 Office System. Curiously, while a variant on this font is used for Windows Mobile 5.0 Smartphones, it is not used for Windows Mobile 5.0 Pocket PCs. And, unfortunately, the Palm Treo 700w I use is built on the Pocket PC version. I have absolutely no idea why I am stuck with Tahoma on my phone, which I find much less pleasurable to use. Bummer.

The differences are much more obvious on smaller fonts, which is why I selected an 8-point font. If you zero in on the SingleBitPerPixel rendering, you will notice that some of the characters aren't even complete! This is where font hinting helps out. It is a way that the developer of the font can make explicit suggestions to improve rendering.

Why might you care about this? Well, I found a few useful nuggets out of this little exploration.

First of all, if you are concerned about performance, it's good to know what your costs and trade-offs are. Rendering fonts includes rasterization if you are using an outline font. If you turn on hinting, then you have to actually run code (and yes, hinting code can contain loops) above and beyond rasterization code. But, from a trade-off perspective, it's very clear to me that these are CPU cycles that are very worthwhile! If you use bitmapped fonts (and there still are some in a standard installation of Windows) then you do not have to pay either cost. However, you also can't take advantage of anti-aliasing - either standard or clear type. (Personally, I can't stand to use software that uses bitmapped fonts any more. If it doesn't support clear type, and I am not compelled to use it for some reason, I'll remove the program.)

Notice that there is an option for ClearType. Normally, you may think of ClearType as a system-wide setting, but its presence here should suggest that you can render fonts using Clear Type even if this smoothing technique has not been enabled system-wide! While normally you would generally prefer to honor the user's system settings, with a reading technology as powerful as Clear Type that wasn't enabled by default when Windows XP was released (and where the setting is potentially non-obvious to somebody who isn't a power user) it is nice to know that you could provide this benefit to your user base without requiring them to discover this setting.

You probably noticed that the text we are outputting are not all exactly the same length. The GridFit fonts tend to be longer. This isn't entirely unexpected - if we are moving glyphs around to find the best fit for the bitmap grid we are rasterizing to, you probably expect that the movement will more often be to the right (on right to left text) so that you don't end up with overlap and crowding with the previous glyph. However, if you inject some code to use the Graphics.MeasureString API, you will discover that the length of the string is reported as always being the same, even though your eyes tell you differently! This is actually a more complex operation than it seems. To measure the size of a string, are doing a translation from a vector image to a raster image. This translation also includes grid fitting and hinting. Apparently, the MeasureString API is not running all of this code before returning a response, and the most obvious reason why is that it does this in the interest of performance. Considering trade-offs, the question then becomes this - how important is it to be to-the-pixel accurate, compared to the value of the CPU cycles that could otherwise be doing something the user could actually see. For example, I am using the MeasureString API to find an approximation of the string's height in order to space out my output. If the result is 1 pixel greater than it would otherwise need to be, do I really care? In this example, I do not. However, it's good to keep this in mind when you use this API.

You will also notice that I used the Graphics.DrawString API rather than the TextRender.MeasureText API. TextRenderer internally uses some font caching, as well as calling into the GDI function DrawTextEx. The result is that everything I render is rendered using hinted fonts, which made it a poor choice for the purpose of this demo. Wait ... it's using GDI instead of GDI+? I have heard (but I have not personally verified) that GDI font rendering is approximately 10x faster than GDI+ font rendering (1M glyphs / second vs. 100K glyphs / second). Of course, there is a reason why GDI+ takes more work - text is truly device independent using GDI+. Yet another tradeoff. If you are interested in the details, check out GDI+ Text, Resolution Independence, and Rendering Methods. Or - Why does my text look different in GDI+ and in GDI?.

Knowing about font hinting is also important if you are using text with GraphicsPath objects. The GraphicsPath object exposes AddString methods, and you can use this to include text in your path. However, internally, a GraphicsPath object contains nothing but a collection of points. There are no exceptions made to store text. That means that the text is stored as the points required to construct this image. Once the text is converted to points, there is no way to differentiate these points from any other points in the path. And this, of course, means that when you rasterize this path, you will be rasterizing the points comprising the text, leveraging none of the grid fitting and hinting provided by fonts. If your fonts are smaller, the outcome could likely be as unattractive and unreadable as the SingleBitPerPixel font rendering.

The final thing I pondered was this: if the technique of hinting so visibly helpful in rendering font vectors, wouldn't it be just as helpful in rendering arbitrary vectors? I certainly think so. Obviously, the completeness of a font has a much higher impact than the completeness of an arbitrary picture - you want to be able to process a large number of glyphs as efficiently as possible. However, as we slowly (glacially) make our way towards true device independence and vector images, I can foresee a need to support vector hinting on arbitrary vector images, and not fonts alone. I have no idea if or when this is coming...

Note that I am not an expert in the inner workings of fonts, and that I have discovered many of these things during the course of my exploration. I just need to use them. If you have something to add or correct, please feel free to comment, trackback, or email!