Make GDI+ Less Finicky About Fonts

Until a few years ago, the world of scalable vector fonts was divided into two camps. On one side stood the PostScript fonts of Adobe. On the opposite side, Apple and Microsoft, in a remarkable strategic alliance, had positioned their TrueType fonts.

Then, the walls were torn down, and the two competing technologies were merged. Out of PostScript and TrueType grew OpenType. For the graphics programmer, life became a bit easier.

Consequently, Windows became a lot friendlier towards fonts. From Windows 2000, it accepted not only TrueType and the new OpenType fonts, but also PostScript Type 1 fonts. It was no longer necessary to install Adobe Type Manager (one of the cleverest Windows apps ever written, by the way) to access a wide variety of fonts.

In Windows 2000 and XP, GDI makes no difference between the fonts. The font type is completely transparent to the programmer. He or she simply doesn't have to spend a single thought at it.

As I learned the hard way, however, this is different for GDI+, the extension for GDI that is integrated into Windows XP. The string functions in GDI+ don't work with PostScript fonts, nor with OpenType fonts having an internal PostScript-like format, as many 'classical' fonts have. GDI+ simply doesn't display them, and nitpicking, the functions return the error code 'NotTrueTypeFont'.

GDI+ trying to display an OpenType/PostScript font. Nothing appears. GDI (top) works fine, as does my QGraphicsPath class (bottom).

GDI+'s refusal of PostScript fonts isn't documented anywhere, but I can't say that it struck me with surprise. Although GDI+ was introduced only with Windows XP, Microsoft seems already have forgotten about it and never really completed it. The library has more anomalies; see, for instance, my article on Weird Warps.

Microsoft may be content with an 'advanced graphics library' working with only half of the fonts in the world, but that doesn't mean we have to be. I tried to fill in the gap by designing the class QGraphicsText. It adds a text string to a GDI+ GraphicsPath. In fact, it does the same as the member function GraphicsPath::AddString(). But, unlike this method, it also accepts PostScript and OpenType/Postscript fonts.

A Function from Hell

I guess it was the mere challenge that made me design this class. In Windows, simply outputting characters generally isn't exactly fun, with all these complex functions and structs, but it's nothing compared to retrieving basic character data from the system's depths.

You have to deal with GetGlyphOutine(), a function from hell, probably made up by the nastiest member of the Windows design team when he was suffering from a really, really bad temper. To use it, you have to struggle with horribly complex nested structs of varying sizes, bearing data in weird formats. The documentation is very dense and sketchy, and only recently more or less free of errors and inconsistencies. Only in some remote, dim corners of the huge MSDN library one or two small, partly outdated articles can be found.

No wonder most programmers tend to give this part of Windows GDI a wide berth. It's almost as if Microsoft doesn't want you to play with text. It wouldn't have something to do with the fact that it was Apple, who originally designed the core font technology for Windows, wouldn't it?

Using QGraphicsText

Anyway, using QGraphicsText makes it manageable. It uses a mixture of GDI and GDI+ techniques. The public interface is very simple and amounts to the following:

class QGraphicsText
{
public:
  QGraphicsText();
  ~QGraphicsText();

  void SetFont(const LOGFONT& logfont, bool bFlipY = true);
  int GetGraphicsText(GraphicsPath& path, LPCTSTR str,
                      PointF pntOrigin) const;
  int GetGraphicsText(GraphicsPath& path, LPCTSTR str,
                      Point pntOrigin) const;
  ...
}

After construction, the font can be set by using the SetFont() method. It takes a reference to a LOGFONT as a parameter.

Once the font is set, the GetGraphicsText() method simply takes a reference to a GDI+ GraphicsPath object, and puts a text path in it, ready for display or to do any of the other things you can do with a GraphicsPath. That's really all there is to it.

The SetFont() member function has a second parameter, a boolean called bFlipY. This determines how QGraphicsText sees the vertical dimension. If bFlipY is false, it assumes that the vertical axis is going upward, and if it's true, the opposite is assumed.

You'll notice soon enough whether bFlipY has the correct value or not, because if it doesn't, the text characters will appear mirrored and upside down. In default situations—that is, when working in Windows mapping mode MM_TEXT—the y-axis goes down, and bFlipY should have its default value of true.

GetGraphicsText() will not empty the GraphicsPath before it adds the string to it. This means that a string can be added to any other figure. Also, the function might be called several times in succession with the same path, and different font settings or strings. GetGraphicsText() will change the path's fill mode to FillModeWinding.

Notice that QGraphicsText is designed with western, horizontal left-to-right running text in mind. I made some precautions to avoid the computer going up in smoke if an exotic upside-down, inside-out, zigzag running font is set, but I very much doubt the result would be acceptable.

QGraphicsText is in the header file QGraphicsText.h and the source file QGraphicsText.cpp. The code is not dependent on MFC, ATL, or any other framework. It just uses bare Windows API calls. Consequently, it can be used in pretty much any kind of Windows C++ project.

Inside QGraphicsText

As said, QGraphicsText relies on the dreaded GetGlyphOutline() Windows function. The glyph outline you'll obtain is formatted as a number of structs with varying sizes. It consists of line segments and so-called quadratic B-splines. The latter are curves, defined by three points. However, GetGlyphOutline() will give you only two; you'll have to calculate the third point yourself.

You can't do much with quadratic B-splines as such; Windows GDI and GDI+ don't understand them. Therefore, they first have to be transformed into cubic Bézier curves. On top of that, GetGlyphOutline() uses a special fixed point number format that is completely different from anything else in Windows.

GetGlyphOutline() is also called to obtain the character cell width. If applicable, this is corrected by the kerning amount for the character and its right neighbour. Notice that not many fonts have kerning information. Of the Windows standard fonts, 'Garamond' and 'Monotype Corsiva' are among the few.

Demo

The demo accompanying this article is a small MFC program. It's not exactly spectacular; it just displays a piece of text in four different ways.

The first one is the 'classic' GDI technique, using TextOut(). The second one uses the GDI+ Graphics::DrawString() method. The third one is also an example of standard GDI+ programming, using GraphicsPath::AddString(), followed by Graphics::FillPath(). The final one employs my QGraphicsText class, also followed by Graphics::FillPath().

If you select a PostScript font, or an OpenType font of the PostScript variety, both GDI+ methods will fail, simply displaying nothing. GDI will work normally, as will QGraphicsText.

You may not have PostScript fonts on your system. A standard Windows installment comes without them. PostScript fonts typically are products from 'great', printed media-oriented type foundaries like Adobe, Bitstream, Agfa, or Monotype. I won't guarantee it, but if you have Adobe Acrobat Reader installed, you may find some OpenType/PostScript fonts in its program directory (look for a 'Resource\Font' subdirectory). Also, the Internet has quite a few places offering free fonts.

You may often perceive subtle differences in the four ways the demo displays text. Font technology is a big subject, and my experiments only scratch at one of the surfaces. I'm not claiming that the way my QGraphicsText handles text display is the preferred one; there is room for improvement.

As an extra, I implemented some timing measurment, using my QPerformanceTimer class. You'll see that there's really a huge speed difference between GDI and GDI+. Surprisingly, my class seems to be somewhat faster than Graphics::DrawString().

Note: your system must support GDI+, which currently only XP does natively. However, other Windows versions can be upgraded. Also, VC++ 6.0 comes without the GDI+ headers. You may obtain them by downloading the Windows Platform SDK. The GDI+ headers are included with VC++ 7.0 and later.

References



Downloads