Creating Font Textures in Direct3d without D3DX

| | August 11, 2015

Update: The completed solution using Nathan Reed’s answer is posted in my answer below

A few open source programs I’ve seen that render installed fonts do something like this…

Create a texture for the font
Draw font to texture using native calls
Store the coordinates for each character
Create a vertex buffer with a quad for each character
Render the vertex buffer using the font texture

But I’m really having trouble getting this to work consistently. GetTextExtentPoint32A() for Win32 font sizes which does NOT include italic support and seems to cut off some of the stranger fonts.

Here is an example output from my code. Notice the cut off edge of the Q and a few other characters. A much more exaggerated version happens when rendering italics.

http://i30.photobucket.com/albums/c308/thentsc/font_test.jpg

The code looks something like this…

for (uint8 c = 0; c < 127 - 32; c++)
{
    str[0] = c + 32;
    SIZE size;
    GetTextExtentPoint32A(hDC, str, 1, &size);
    if (x+size.cx+1 > m_TextureSize)
    {
        x  = 0;
        // 1 pixel vertical margin
        y += size.cy + 1;
    }

    uint32 Diff;
    ExtTextOutA(hDC, x+0, y+0, ETO_OPAQUE | ETO_CLIPPED, NULL, str, 1, NULL);
    m_fTexCoords[c].Left = x/m_TextureSize;
    m_fTexCoords[c].Top = y/m_TextureSize;
    m_fTexCoords[c].Right = x+size.cx/m_TextureSize;
    m_fTexCoords[c].Bottom = y+size.cy/m_TextureSize;

    // 1 pixel horizontal margin
    x += size.cx;
}

The image above is actually just a render of the whole texture stretched from -1,-1,0 to 1,1,0 not using the stored texture coordinates to show clipping due to GetTextExtentPoint32A.

Again this works fine for some fonts but looks terrible for others and doesn’t support italics.

It is designed to work in a system like this…

cFont* g_Font;
cTextObject g_TextObject;

Setup()
{
    g_Font = SetupFont("Arial", BOLD | ITALIC, 24pt);
    g_TextObject = CreateTextObject("The quick brown fox...", g_Font, X, Y);
}

OnRender()
{
     g_TextObject->Render();
}

So in general I guess my question is what is the correct approach for this issue?

Should I just let each text object have a texture and on text object creation use the GDI to render all text to it (which would bypass the italics and font face problem)?

But then wouldn’t texture memory usage get out of hand?

2 Responses to “Creating Font Textures in Direct3d without D3DX”

  1. Alright so after about 2 hours of coding and testing here are my findings and final solution to this problem that works perfectly for every TrueType font with any font thickness and any escapement (italics). This also works with quality CLEARTYPE_QUALITY or ANTIALIASED_QUALITY without AA bleeding between characters (as far as I can physically see).

    This does not fix any problems with underline and strikeout not being contiguous between characters.

    The major ah-ha moment was when I realized that ExtTextOutA actually prints to the screen with the negative left margin (abcA) included. This can actually result in it printing characters with their left side trimmed if you try to print them at 0 for X. You have to take this into account when rendering the characters.

    Without further ado here is the solution:

    uint32 x = 0, y = 0;
    char str[2] = "";
    for (uint8 c = 0; c < MAX_FONT_CHARS; c++)
    {
        ABC* CharW = &abcWidths[c];
    
        // GetTextExtentPoint32 used for character height
        str[0] = c + 32;
        SIZE Size;
        if (!GetTextExtentPoint32A(hDC, str, 1, &Size))
        {
            Warning("GetTextExtentPoint32A failed for character '%s'", str);
        }
    
        // Left starting point is the negative left margin, it is used as an offset for ExTextOut and for calculating the Right
        uint32 Left = (CharW->abcA < 0 ? -CharW->abcA : 0);
    
        // The right is the Left, plus the character width, plus the left margin, plus the right overhang if it is positive
        uint32 Right = Left + CharW->abcB + CharW->abcA + (CharW->abcC > 0 ? CharW->abcC : 0);
    
        // Wrap around if this font would bleed off the edge
        if (x + Right > m_TextureSize)
        {
            x = 0;
            y += Size.cy;
        }
    
        // Draw a border where we expect the object to be drawn
        Rectangle(hDC, x, y, x + Right, y + Size.cy); 
    
        // Print the character
        ExtTextOutA(hDC, x + Left, y, ETO_CLIPPED | ETO_OPAQUE, NULL, str, 1, NULL);
        // Store the texture coordinates
        m_fTexCoords[c].Left = x / ((float) m_TextureSize);
        m_fTexCoords[c].Top = y / ((float) m_TextureSize);
        m_fTexCoords[c].Right = (x + Right) / ((float) m_TextureSize);
        m_fTexCoords[c].Bottom = (y + Size.cy) / ((float) m_TextureSize);
    
        // Increment by the character width and margin
        x += Right;
    }
    

    The rectangle (x, y) to (x+Right, y+Size.cy) exactly matches area written by ExTextOut. I used this function in conjunction with Photoshop and some transparency to double check.

    You still need to properly store and use the the abcA (Left Margin) and abcC (Right Margin) if you want to have proper overhangs and underhangs. They are intentionally used here to only find the drawn width.

  2. I think your problem is that GetTextExtentPoint retrieves the “advance width”, or how far the cursor would be advanced horizontally by inserting a particular bit of text. But some fonts (especially italics) contain overhangs – where the character extends a little bit further than the advance width.

    You can get more detailed information by using GetCharABCWidths. This retrieves the three widths called A, B, and C; I believe the exact width of the drawn character is the B width, which should be what you want. A and C are extra space to the left and right of the character respectively, and are negative in the case of overhangs. (The advance width is A + B + C.)

    If you’re interested in precise typography you may also want to look at GetKerningPairs, which will tell you how to adjust the spacing when specific letters are adjacent. For instance A and V are normally slid a bit closer together when appearing adjacent: compare “AV” vs “A‌ V”. See the difference? (In the second one I put in a Unicode zero-width space character that breaks the kerning without adding any space of its own.)

Leave a Reply