Fast Bitmap Fonts for DirectDraw – Converting from TrueType Fonts

by Michael Fötsch
June 19, 2000 (Last Update: July 22, 2000)

This article was originally published at Gary Simmons’ Mr GameMaker.

Table of Contents

  1. Introduction
  2. TrueType converter
    1. How does it work?
    2. Planning the TrueType converter
    3. Measuring the font
    4. Drawing the characters
    5. Saving the font
    6. Putting it all together
    7. Running the TrueType converter
  3. DirectDraw font engine
    1. Planning the DirectDraw font engine
    2. The font engine classes
    3. Loading the bitmap
    4. Loading the font information
    5. Drawing text
    6. Wrapping it up
  4. Finally…
  5. References
  6. Code archive
  7. Appendix

Introduction

In this tutorial, we’ll write a fast bitmap font engine for DirectDraw. To have something to render from the start, we’ll also write a program to create sample fonts from existing TrueType fonts.
I’d suggest we dive right in! (I can’t think of any good introduction anyway…)

How does it work?

Bitmap fonts store the characters as bitmaps which are blitted to the screen side by side to form words. That’s faster than vector fonts (TrueType fonts fit into this category as well), because they require that you plot lines or bézier curves whenever you wish to display text. (Although you might create bitmaps from the vector font first and simply blit these afterwards.) In fact, bitmap fonts are very simple. Here’s an example:

Sample bitmap font created with the TrueType converter from this  tutorial

To bring the words “Hello, world!” (or any other words, of course) to the screen, a font engine would have to blit the letters sequentially from their positions in the bitmap. An important task when writing such an engine will be to identify these positions. As you can see, the sample font is not fixed but variable pitch, which means that the characters are not equally wide. This makes the task more complicated. (There are Windows API calls that will help us determining how wide they actually are.)

Note: We could also place the characters side by side into the bitmap. It just looks better when they are arranged nicely. And we reduce the risk of failing on systems that do not support wide DirectDraw surfaces. (Are there any?)

Planning the TrueType converter

Converting from TrueType to bitmap will work like this:

  1. Gathering information about the font (font height, max. character width)
  2. Drawing the characters using standard GDI functions
  3. Saving the device context (GDI’s canvas) to disk along with the font info

As a little appetizer, here’s a screenshot of the final program:

Screenshot of the TrueType converter window
(The colorful lines will not be saved! Their meaning will be revealed shortly.)

Measuring the font

As our next task will be to arrange the characters in a grid of 16×14 cells, we need to know what size these should be. All characters of a given font are equally high (although their visual representation need not be); their widths may differ. The TEXTMETRIC structure of the Win API has two fields that are of interest here: tmHeight and tmMaxCharWidth.

TEXTMETRIC tm;
GetTextMetrics(hdc, &tm);
int GridWidth=16*tm.tmMaxCharWidth;
int GridHeight=14*tm.tmHeight;

The GetCharABCWidths function returns the character widths of a given range of characters:

ABC abc[224];
GetCharABCWidths(hdc, 32, 255, abc));

Note: We will always only use 224 of 256 possible characters. The first character, #32, is the space character. Characters with lower codes are usually not displayable.

It’s time to talk about ABC widths and the ABC structure. Take a look at its declaration:

typedef struct _ABC { // abc
    int     abcA;
    UINT    abcB;
    int     abcC;
} ABC;

The structure members are described as follows:

abcA
Specifies the “A” spacing of the character. The “A” spacing is the distance to add to the current position before drawing the character glyph.
abcB
Specifies the “B” spacing of the character. The “B” spacing is the width of the drawn portion of the character glyph.
abcC
Specifies the “C” spacing of the character. The “C” spacing is the distance to add to the current position to provide white space to the right of the character glyph.

Consider the following italic text:

Demonstration  of ABC widths in an italic font

The green line marks the leftmost visible pixel of the letter “W”. The blue line marks its width. You’ll notice that the next letter, the “i”, begins before the “W” actually ends. What happened is that the GDI, when rendering the text, “went back” a few pixels after it had finished drawing the “W” and before it went on drawing the “i”. The space between the blue line and the magenta line is the C width (abcC).
“The total width of a character is the summation of the ‘A,’ ‘B,’ and ‘C’ spaces. Either the ‘A’ or the ‘C’ space can be negative to indicate underhangs or overhangs.”
Just for fun, we’ll make the ABC widths visible in our little program. (Take a look at the screenshot again. For some reason that is going to be given later, the characters there do not always start at the green line…)

Drawing the characters

The code to render the text is straight-forward:

// Select previously created font into device context:
if (fi.hFont) SelectObject(ps.hdc, fi.hFont);

SetBkMode(ps.hdc, TRANSPARENT);     // do not fill character background

unsigned char c;
int left, top, bottom;

// arrange characters in 14 rows:
for (int y=0; y < 14; y++)
{
    top = y*fi.tm.tmHeight;
    bottom = top + fi.tm.tmHeight;

    // draw baseline with red pen:
    SelectObject(ps.hdc, Pens[ptBaseline]);
    MoveToEx(ps.hdc, 0, bottom-fi.tm.tmDescent, NULL);
    LineTo(ps.hdc, width, bottom-fi.tm.tmDescent);

    // arrange characters in 16 columns per row:
    for (int x=0; x < 16; x++)
    {
        left = x*fi.tm.tmMaxCharWidth;
        c = (unsigned char)(y*16+x+32);
        // draw char:
        TextOut(ps.hdc, left-fi.abc[c-32].abcA, top, (const char*)&c, 1);
            // See below why abcA is subtracted.

        // draw A width (see docs, "ABC" and "GetCharABCWidth") using green:
        SelectObject(ps.hdc, Pens[ptAbcA]);
        MoveToEx(ps.hdc, left+fi.abc[c-32].abcA, top, NULL);
        LineTo(ps.hdc, left+fi.abc[c-32].abcA, bottom);
        // draw B width using blue:
        left += fi.abc[c-32].abcB; SelectObject(ps.hdc, Pens[ptAbcB]);
        MoveToEx(ps.hdc, left, top, NULL); LineTo(ps.hdc, left, bottom);
        // draw C width using magenta:
        left += fi.abc[c-32].abcC; SelectObject(ps.hdc, Pens[ptAbcC]);
        MoveToEx(ps.hdc, left, top, NULL); LineTo(ps.hdc, left, bottom);
    }
}

This code is used to display the font on the screen. When storing it, we do not draw any helper lines.

Note: It is necessary to subtract abcA from the drawing position before drawing a character. The GDI will automatically add it again. We want all characters to start at the left edge of their cells. If abcA was negative, the GDI would actually draw the character partially within the preceding cell. We do not want this to happen. The green helper line that indicates the A width, on the other hand, is still moved abcA pixels. This way, you can see how far and in which direction the GDI actually moves the characters.

Note: The code above uses some items that I have not introduced yet, e.g. the “fi” object or the “Pens” array. See section “Putting it all together” for descriptions of those.

Saving the font

For saving the DirectDraw bitmap font, I made up a custom file format, where the font information is simply appended to a regular BMP file. That’s it. The font information is stored in a TEXTMETRIC structure, in an array of ABC structures, and in a LOGFONT structure (that is used to create a GDI font object for drawing).

Note: It is not really necessary to store a LOGFONT, but the structure contains at least some information that might be useful in the future. We shouldn’t throw it away just because we don’t know what to do with it!

Our first task is to create a monochrome offscreen bitmap. A device context to select this into will also be created:

// Create an offscreen device context:
OffscrDC = CreateCompatibleDC(0);

if (fi.hFont) SelectObject(OffscrDC, fi.hFont);

// Create an offscreen bitmap:
int width=16*fi.tm.tmMaxCharWidth;
int height=14*fi.tm.tmHeight;
HBITMAP OffscrBmp = CreateCompatibleBitmap(OffscrDC, width, height);
// Select bitmap into DC:
HBITMAP OldBmp = (HBITMAP)SelectObject(OffscrDC, OffscrBmp);

Now we can fill the background with black and draw the characters using the already known code (but without the helper lines!).
The remaining code to save the bitmap is taken from my tutorial “Using the GDI to Take DirectDraw Screenshots” [1], which is also available on this site. The code in its entirety is contained in the code archive at the end of this article. If you need a walkthrough, see the other tutorial.

These are the lines that have been added:

if (WriteFile(BmpFile, &fi.tm, sizeof(fi.tm), &Written, NULL) &&
WriteFile(BmpFile, fi.abc, 224*sizeof(ABC), &Written, NULL) &&
WriteFile(BmpFile, &fi.lf, sizeof(fi.lf), &Written, NULL) == false) ERROR_BREAK(13);

Following is a summary of the DDF file format. The fields in the darker rows contain a standard DIB (device-independent bitmap) as stored in .bmp files.

OffsetContentsSize
0BITMAPFILEHEADER bmfh
(bfReserved1=’DD’; bfReserved2=’FF’)
sizeof(bmfh)
sizeof(bmfh)BITMAPINFOHEADER bmihsizeof(bmih)
sizeof(bmfh)+sizeof(bmih)RGBQUAD Palette[](depending on color format)
bmfh.bfOffBitsBYTE BitmapBits[](depending on format and dimensions)
bmfh.bfSizeTEXTMETRIC tmsizeof(tm)
bmfh.bfSize+sizeof(tm)ABC abc[224]224*sizeof(ABC)
bmfh.bfSize+sizeof(tm)+224*sizeof(ABC)LOGFONT lfsizeof(lf)

Putting it all together

I should tell you a bit about the implementation details of the program. For keeping all the font-related stuff together, for example, I introduced class CFontInfo, which “fi” is a global instance of. Here’s its declaration:

class CFontInfo
{
public:
    CFontInfo() { hFont=NULL; }
    ~CFontInfo() { if (hFont) DeleteObject(hFont); }
    void InitChooseFont(HWND hwnd);

    HFONT hFont;      // stores font handle used in GDI calls
    LOGFONT lf;       // font description for CreateFontIndirect
    CHOOSEFONT cf;    // font description for ChooseFont dialog
    TEXTMETRIC tm;    // text metrics, e.g. character height
    ABC abc[224];     // character widths
};

I have already used most of CFontInfo’s members in the code snippets, except the CHOOSEFONT structure. CHOOSEFONT structures are passed to the ChooseFont Win API function, which, in turn, displays a common dialog box:

Screenshot of the ChooseFont common dialog box
Part of the ChooseFont common dialog box

The CHOOSEFONT structure is filled at startup using the InitChooseFont method:

void CFontInfo::InitChooseFont(HWND hwnd)
{
    ZeroMemory(&lf, sizeof(lf));
    ZeroMemory(&cf, sizeof(cf));
    cf.lStructSize = sizeof(cf);
    cf.hwndOwner = hwnd;
    cf.lpLogFont = &lf;
    cf.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT;
}

The lpLogFont member of CFontInfo::cf points to CFontInfo::lf, which is initially filled with the standard font’s LOGFONT (so that the standard font is selected when the user opens the dialog box for the first time):

hdc = GetDC(hWnd);
GetObject(GetCurrentObject(hdc, OBJ_FONT), sizeof(LOGFONT), &fi.lf);
MeasureFont(hdc);
ReleaseDC(hWnd, hdc);

As a reaction to the user clicking “File | Load Font”, the program executes the following code which creates a new font handle for use by the rendering functions:

// invoke ChooseFont common dialog:
if (ChooseFont(&fi.cf))
{
    // create an HFONT:
    if (fi.hFont) { DeleteObject(fi.hFont); fi.hFont = NULL; }
    fi.hFont = CreateFontIndirect(&fi.lf);

    // get HDC:
    HDC hdc = GetDC(hWnd);

    // select font:
    SelectObject(hdc, fi.hFont);

    // get text metrics and char widths:
    MeasureFont(hdc);

    // release HDC:
    ReleaseDC(hWnd, hdc);

    // redraw window:
    RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
}

Note: The details of using Windows common dialog boxes is beyond the scope of this tutorial. If you’re interested, see the MSDN Library [2] or the Win API help files that came with your development environment.

I also promised to tell you what the “Pens” array was. It’s simply an array of five HPENs that are used to draw the grid and the helper lines for ABC widths and baseline. The pt… constants are indices into the array:

enum TPenTypes {
    ptGrid,                   // used to draw grid lines
    ptBaseline,               // used to draw the baseline of the font
    ptAbcA, ptAbcB, ptAbcC    // used to draw ABC widths of characters
};

Running the TrueType converter

It’s time to test the program! The complete source code can be found in the code archive of this article, from where you can also download a compiled version of the program.
Load a font and save it as a DDF file. Use the “.bmp” file name extension to be able to open the file with MS Paint (or any other paint program). (Some paint programs might refuse to open DDF files as bitmaps because of the Reserved fields not being 0.)

Note: There might be errors when working with fonts that are generated when an italic or bold style is requested but not available. In such a case, the graphics device interface (GDI) may simulate those styles using an existing raster or vector font. TrueType fonts seem to always be safe.

Screenshot demonstrating the simulated font styles error
Demonstration of the errors that occur when simulating the italic font style for the “Courier” font.
The characters do not fit into their cells.

Having a TrueType converter is nice. But it’s useless without the DirectDraw bitmap font engine…

Planning the DirectDraw font engine

Drawing text requires the following preparation:

  1. Loading bitmap and font information into memory
  2. Pre-calculating RECTs to speed up the blitting process
  3. Changing the foreground color of the bitmap to the desired text color

Rendering a string will work like this:

  1. Iterate through the characters of the string
  2. Blit each character using the pre-calculated RECTs

Screenshot of the DirectDraw font engine in action

To make it possible to switch between fonts quickly and easily, we will separate the font and font engine classes. Programmers can create any number of TDDFont objects. Fonts are selected for drawing by calling TDDFontEngine::SelectFont, which is similar to the GDI approach. Selecting a new font does not require reading any information from disk.

Here’s an example of how working with our font engine will feel:

// Create font engine object:
TDDFontEngine *Engine; Engine = new TDDFontEngine(lpDD);

// Create font objects:
TDDFont *FirstFont, *SecondFont;
FirstFont = new TDDFont(Engine, “Arial.ddf”);
SecondFont = new TDDFont(Engine, “Times New Roman.ddf”);

// Draw using first font:
Engine->SelectFont(FirstFont);
Engine->DrawText(lpDDSBack, 0, 0, “Hello, Arial World!”);

// Draw using second font:
Engine->SelectFont(SecondFont);
Engine->DrawText(lpDDSBack, 0, 0, “Hello, Times New Roman World!”);

The font engine classes

Following are the declarations of TDDFont and TDDFontEngine:

class TDDFont
{
public:
    TDDFont(TDDFontEngine *ddfe, LPCSTR fontfile,
        DWORD textcolor=TEXTCOLOR(255, 255, 255));
    ~TDDFont();

    TDDFontEngine *Ddfe;
    LPDIRECTDRAWSURFACE7 lpFontSurf;
    char *FontFile;
    LPBITMAPINFO lpbi;
    LPVOID lpBits;
    TEXTMETRIC TextMetrics;
    LOGFONT LogFont;
    int SurfWidth, SurfHeight;
    int CellWidth, CellHeight;
    RECT SrcRects[256];     // Pre-calculated SrcRects for Blt
    ABC ABCWidths[256];
    int BPlusC[256];
        // For speed reasons, the arrays have 256 items (and not 224). See below.
    DWORD TextColor;

    bool LoadFont();
    void SetTextColor(DWORD textcolor);
};

class TDDFontEngine
{
public:
    TDDFontEngine(LPDIRECTDRAW7 lpdd);
    ~TDDFontEngine();

    LPDIRECTDRAW7 lpDD;
    TDDFont *Ddf;

    void SelectFont(TDDFont *ddf);
    HRESULT DrawText(LPDIRECTDRAWSURFACE7 lpDDS, int x, int y, char *Text);
};

It is not possible to create an instance of TDDFont without loading a font. Once LoadFont has been called, the entire font data (bitmap and metrics) is contained in the class members. Calling LoadFont again will simply re-load the bitmap into the surface and does not involve reading from disk. This is useful when lpFontSurf needs to be restored after DDERR_SURFACELOST.
TDDFontEngine performs the actual blitting in its DrawText method, thereby using TDDFont’s DirectDraw surface (pointed to by lpFontSurf) and font information.

Loading the bitmap

The following two sections will walk you through the code of the TDDFont::LoadFont method. The first one shows you how to load the bitmap into a DirectDraw surface.

As you know, the first part of a DDF file contains a standard DIB. We can use the StretchDIBits GDI function to blit such a bitmap to a DirectDraw surface. If necessary (and it will be necessary!), StretchDIBits converts the bitmap to match the format of the destination device context. Such a device context can be obtained by calling IDirectDrawSurface7::GetDC.

Note: The following method to load a bitmap can be used universally without modifications. Simply copy and paste it to have a bitmap loader for all bitmap and surface formats.

In the first step, we open the font file and verify whether it’s a valid DDF file. In DDF files, the Reserved fields of the BITMAPFILEHEADER are set to ‘DDFF’:

if ((f = CreateFile(
        FontFile,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
        NULL)) == INVALID_HANDLE_VALUE)
    throw (int)0;

DWORD BytesRead;
BITMAPFILEHEADER bmfh;
ZeroMemory(&bmfh, sizeof(bmfh));
ReadFile(f, &bmfh, sizeof(bmfh), &BytesRead, NULL);

// Check whether it's a valid DDF file:
if ((bmfh.bfType != 19778)      // 'BM'
    || (bmfh.bfReserved1 != 'DD') || (bmfh.bfReserved2 != 'FF'))
    throw (int)1;

Note: When using this code for a universal bitmap loader, ignore the Reserved fields. In general, they will be 0. Your reader should not fail if they are not.

Following the file header are the palette and the actual bitmap bits:

lpbi = (LPBITMAPINFO)(new char[bmfh.bfOffBits - sizeof(bmfh)]);
ReadFile(f, lpbi, bmfh.bfOffBits-sizeof(bmfh), &BytesRead, NULL);
lpBits = (LPVOID)(new char[bmfh.bfSize-bmfh.bfOffBits]);
ReadFile(f, lpBits, bmfh.bfSize-bmfh.bfOffBits, &BytesRead, NULL);

We are ready to create a DirectDraw surface. I assume that you are familiar with the process of creating offscreen surfaces:

SurfWidth = lpbi->bmiHeader.biWidth;
SurfHeight = lpbi->bmiHeader.biHeight;

DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = SurfWidth;
ddsd.dwHeight = SurfHeight;

if (FAILED(Ddfe->lpDD->CreateSurface(&ddsd, &lpFontSurf, NULL))) throw (int)5;

Note: “Ddfe” is a pointer to the font engine object that will be used to render text using the font.

Note: We do not force a memory location. “By default, DirectDraw creates a surface in display memory unless it will not fit, in which case it creates the surface in system memory.”

Calling StretchDIBits now would result in white text on a black background. In order to change the text color, we simply change the second palette entry:

DWORD *palentry = (DWORD*)&lpbi->bmiColors[1];
*palentry = TextColor;

Palette entries are stored as RGBQUADs, which can also be interpreted as DWORDs. “TextColor” was created by the TEXTCOLOR macro, which is very similar to the well-known RGB macro, except that it stores the colors in reversed order (as in RGBQUAD).

Note: This step would, of course, be skipped for a generic bitmap loader.

StretchDIBits can now blit the bitmap to the DirectDraw surface:

if (FAILED(lpFontSurf->GetDC(&SurfDC))) throw (int)6;
StretchDIBits(SurfDC,
    0, 0, SurfWidth, SurfHeight,
    0, 0, SurfWidth, SurfHeight,
    lpBits,
    lpbi,
    DIB_RGB_COLORS,
    SRCCOPY);
lpFontSurf->ReleaseDC(SurfDC);

As we do not want the font engine to blit the background pixels, we set the surface’s source color key to black, which is a binary 0 in every pixel format:

DDCOLORKEY ddck;
ddck.dwColorSpaceLowValue = ddck.dwColorSpaceHighValue = 0;
lpFontSurf->SetColorKey(DDCKEY_SRCBLT, &ddck);

Loading the font information

In this section, we will finish loading the DDF file.
Reading the font information is not a real challenge:

ReadFile(f, &TextMetrics, sizeof(TextMetrics), &BytesRead, NULL);
ReadFile(f, (LPVOID)((DWORD)ABCWidths+32*sizeof(ABC)), 224*sizeof(ABC), &BytesRead, NULL);
ReadFile(f, &LogFont, sizeof(LogFont), &BytesRead, NULL);

The next step is not a challenge either: We pre-calculate the source RECTs for the 224 characters. They will be used in calls to IDirectDrawSurface7::BltFast. As DrawText will also need the sums of abcB and abcC for each character, we calculate these as well.

CellWidth = SurfWidth >> 4;
    // (Shifting 4 bits to the right equals division by 16)
CellHeight = SurfHeight / 14;

// Pre-calculate SrcRects:
    // Fill first 32 members with zeros. They are not used.
    ZeroMemory(SrcRects, 32*sizeof(RECT));
    ZeroMemory(ABCWidths, 32*sizeof(ABC));
    ZeroMemory(BPlusC, 32*sizeof(int));

for (int c=32; c < 256; c++)
{
    SrcRects[c].left = ((c-32) % 16) * CellWidth;
    SrcRects[c].top = ((c-32) >> 4) * CellHeight;
    SrcRects[c].right = SrcRects[c].left + ABCWidths[c].abcB;
    SrcRects[c].bottom = SrcRects[c].top + CellHeight;
    BPlusC[c] = ABCWidths[c].abcB + ABCWidths[c].abcC;
}

Note: DDFs contain 224 characters. Nevertheless, a string could also contain characters with codes lower than 32. In order to prevent access violations, we’d either have to check the value of each chracter before accessing any of the arrays, or we can make the arrays large enough for all 256 possible characters. The latter method is faster. (Need I say more?)

Drawing text

Let’s take a look at the DrawText function:

HRESULT TDDFontEngine::DrawText(LPDIRECTDRAWSURFACE7 lpDDS, int x, int y,
    char *Text)
{
    LPDIRECTDRAWSURFACE7 SrcSurf = Ddf->lpFontSurf;
/*
    if (SrcSurf->IsLost() == DDERR_SURFACELOST)
    {
        SrcSurf->Restore(); Ddf->LoadFont();
    }
*/

    int StringLength=strlen(Text);
    UCHAR ch;
    for (int i=0; i < StringLength; i++)
    {
        ch = Text[i];
        x += Ddf->ABCWidths[ch].abcA;
        lpDDS->BltFast(DestX, y, SrcSurf, &Ddf->SrcRects[ch],
            DDBLTFAST_SRCCOLORKEY);
        x += Ddf->BPlusC[ch];
    }

    return DD_OK;
}

This code should be self-explanatory. Here are just a few comments:

  • The font surface is not checked for being lost (the code is commented-out). This should be done by the calling code not more often than once per frame or whenever the primary surface is lost.
  • strlen(Text) is stored in a local variable for speed reasons. Otherwise, strlen would have to be called everytime the loop counter is compared against the string length.
  • BltFast has several advantages over Blt, but it also has a disadvantage. When using software emulation, BltFast is 10% faster than Blt. BltFast does not require that a destionation RECT be passed to it. This frees us from having to assemble one, which speeds up the function. The disadvantage is that BltFast does not work when a clipper is attached to the destination surface. I chose speed. You can change this if you don’t like it.
  • In this code you can see how the BPlusC array comes in handy. A single addition is probably no big deal, but the fastest code is the one that does not get executed

Wrapping it up

Changing the text color dynamically

…is easy. The DIB remains in memory throughout the lifetime of the TDDFont object. We simply have to change the palette (just like we did in LoadFont) and call StretchDIBits again. Here’s some code from the SetTextColor method:

TextColor = textcolor;

DWORD *palentry = (DWORD*)&lpbi->bmiColors[1];
*palentry = TextColor;

HDC SurfDC;
if (!FAILED(lpFontSurf->GetDC(&SurfDC)))
{    StretchDIBits(SurfDC, ...

Once set, the text color remains the same even when the surface needs to be restored by calling LoadFont again.

Speeding up LoadFont

I told you that LoadFont does not have to read the DDF file again when re-loading the font. If lpFontSurf is a valid pointer, LoadFont has been called before. lpBits and lpbi contain valid data. Therefore, the code up to the call to StretchDIBits is contained within an “if” statement:

if (!lpFontSurf)
{
    /*
        Load font from disk
        Create DirectDraw surface
    */
}

// lpFontSurf is valid

/*
    Set text color
    StretchDIBits
*/

Finally…

That’s it. Our fast DirectDraw bitmap font engine is done. Go and use it and create some really smooth scrollers!
Our engine is not perfect, though. There is no font-smoothing yet and characters are always uniformly colored. Well, that’s something for a future tutorial… (A good moment to bookmark www.mr-gamemaker.com! 😉 )


References

[1]: “Using the GDI to Take DirectDraw Screenshots

[2]: MSDN Online Library: http://msdn.microsoft.com/library


Code archive

TrueType converter and font engine:
DDFontEngine.zip


Appendix

Font engine performance

The following table compares GDI (TextOut) and DDFontEngine performance. The figures in the table give the number of frames that the respective engine could render within 5 seconds (a benchmarking session). The percentage is the performance gain of DDFontEngine when compared to the GDI.

Tested fontNumber of frames in 5 secondsPerformance gain
GDIDDFontEngine
Arial, 24pt234224514.6%
MS Serif, 8pt269729007.5%
System, 12pt266629058.9%
Times New Roman, bold italic, 24pt2447284816.3%
Webdings, 24pt2280274020%
Mistral, bold, 36pt1923271140%

The benchmark source code is available for download.

Advertisement

Leave a Reply

Your email address will not be published. Required fields are marked *

Python and C++, GNU/Linux, computer stuff…