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

Comments

  • Beats by Dr. Dre Studio– hvor det faktisk fungere særdeles godt pÃ¥ alle enhederne hver især

    Posted by ggfzdu812 on 07/17/2013 08:51pm

    This focus brings a nice clarity to vocal performances. On Bill Callahan’s “Drover,” the vocals are delivered with an extra bit of treble edge that only adds to Callahan’s unique baritone delivery. The vocals also standout nicely on Jay-Z and Kanye West’s “No Church in the Wild”—the mix still features plenty of low-end resonance, but the emphasis on mids and highs helps the vocals shine, rather than get lost by the intense production, which can happen pretty easily with hip hop and electronic tracks on lesser headphones. [url=http://beatsbydrdredanmark.weebly.com/]Beats by dre Hovedtelefoner[/url] Producenter frembringer deres egne beats på nettet åbner ubegrænsede samarbejdsmuligheder med hensyn til designere på et stramt budget. Den særlige udseende tilgængelig er utallige der en bred vifte af friske rekorder på markedet hver dag derude sted. I tilfælde af at du synes at købe overgår, anmeld af din web selskabet samt høre deres varer. Du er i stand til at Google eller måske Ask tilstande såsom “internet-producenter” eller “køb er bedre end online” og masser af troværdige producent internetsider vil helt sikkert producere.En anden tendens jeg opdager i trap rekorder kunne være den store brug af rumklang omkring den primære enhed. Der er typisk en enkelt pind ud værktøj put, der har en velvoksen reverb effekt sige for eksempel en grand violin, guitar, samt synth. I de fleste tilfælde en let melodi, der gentager flere gange samt modulerer op eller ned. Processen mod kaos er meget simpelt, så prøv ikke at under vurdere de grunde, der kan placeres i dette. beats [url=http://beatsbydrdredanmark.weebly.com/]beats by dre[/url] IOC og LOCOG ikke havde reageret på en anmodning om at kommentere på tidspunktet for offentliggørelsen. Den officielle sponsor, der er mest tilbøjelige til at føle sig mest forurettede ved stunt er Panasonic.England fodboldspillere brugte Dr. Dre hovedtelefoner ved Euro 2012 for at undgå pressen. Hovedtelefonerne voksede i popularitet på Beijing 2008 Olympiske Lege, efter at selskabet gav dem til basketball stjerne LeBron James, og han gav dem ud til medlemmer af Team USA.Den baghold markedsføring initiativet kommer i hælene på en protest – iværksat af amerikanske sportsfolk, herunder 400m løber Sanya Richards-Ross – kritisere regel 40 i IOC adfærdskodeks. Reglen forbyder atleter fra at nævne deres personlige sponsorer på sociale medier under legene.

    Reply
  • It's broken...

    Posted by Andy on 12/12/2012 08:32pm

    Sadly, this is broken and no longer works :( GetGlyphOutline() returns 0 bytes when using the GGO_NATIVE|GGO_UNHINTED flags -- if you use GGO_UNHINTED only, it returns buffer size, but TTPOLYGONHEADER::dwType doesn't equal TT_POLYGON_TYPE I am on the lookout on how to get this to work. I have been using this code for about 3 years.

    • It works now.

      Posted by Andy on 04/10/2013 06:25pm

      This was actually fixed -- it was a buggy update from microsoft which they have since fixed. This code works wonderfully.

      Reply
    Reply
  • Trying to use OpenType from C#/GDI+

    Posted by Alan8 on 08/28/2012 02:24pm

    "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." It appears to be deeper than this in C#: a Font object can't even be created with an OpenType font. If you try it, .NET substitutes "Microsoft Sans Serif". So to use OpenType from C# .NET, both the font creation and rendering have to be done in GDI (no plus).

    Reply
  • Is it possible to render non-system fonts

    Posted by vyetrintala on 02/27/2006 01:34pm

    Location for fonts right now is c:\windows\fonts (system fonts) -- is there a way to change this directory, means read non-system fonts. In my case have 80,000 fonts to render and dont want to put all of them in system directory which slows down the perfomance. Instead i would like to keep fonts in some temp directory like c:\tmpfonts. thanks in advance -VY

    Reply
  • I would crawl 100km over broken glass to kiss your feet!

    Posted by jhudler on 10/03/2005 05:31pm

    You have no idea how timely this is. I ran into the same issues and decided to implement FreeType 2 to solve this issue, only to open another bag of worms that you get with 'free' code. FreeType 2 may work but itbs horribly incomplete in the area of font enumeration.
    
    I was just about to de-crypt GetGlyphOutline with I found your article... I only wish I had found this earlier. Plus you implemented Kerning as well, outstanding!
    
    Thank you again for yet another excellent article on GDI+ weirdness.

    Reply
  • If GDI+ is broken for fonts, why use it ?

    Posted by fredwobus on 10/03/2005 09:47am

    Aren't you saying that we should all be using GDI for font output, because GDI+ is broken in that respect ? That makes even more sense to me looking at the timing info in the screenshots. It looks like the GDI part works for both types of fonts, so why use something else ?

    • RE: If GDI+ is broken for fonts, why use it ?

      Posted by Sjaakp on 10/04/2005 04:34am

      That's a good point, as long as you're looking for straight text output. However, a GDI+ GraphicsPath can be used for outline drawing, it can be filled with a pattern, it can be transformed or warped, used to define a PathGradientBrush, etc.

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds