3
\$\begingroup\$

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?

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

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.)

\$\endgroup\$
6
  • \$\begingroup\$ Thanks you, that is exactly what I was looking for. Do you believe that KerningPairs are something that users would notice a lack of and should be included in the font calculation? \$\endgroup\$
    – NtscCobalt
    Commented Oct 22, 2011 at 19:23
  • \$\begingroup\$ It depends what you're going to use the text for. If this is just for debug readouts / dev consoles and the like, then I wouldn't worry about it. But if it's for in-game text and you want it to look aesthetically pleasing, then kerning is a must especially for large text (titles, headings, etc.) It's not such a big deal for small text like subtitles. \$\endgroup\$ Commented Oct 22, 2011 at 19:29
  • \$\begingroup\$ I plan on using the same system for both so I will include KerningPairs per your recommendation. Thank you very much for the quick responses. Now all I need is to figure out how to do all the same on Linux. \$\endgroup\$
    – NtscCobalt
    Commented Oct 22, 2011 at 19:36
  • \$\begingroup\$ I'm not sure if I'm getting floating point errors, my math is wrong, or my use of fractional cell height for specifying font size but somewhere I'm having a slight problem with the right overlap. It seems fairly minimal and only noticeable on fonts where an exact gap is important but after staring at fonts for 5 or so hours today it really stands out to me. Kunstler Script provides no kerning information and this small glitch even happens with Times New Roman but it is almost unnoticlbe. Here is a picture: i30.photobucket.com/albums/c308/thentsc/font_compo.jpg \$\endgroup\$
    – NtscCobalt
    Commented Oct 23, 2011 at 6:27
  • \$\begingroup\$ @NtscCobalt, if you want your game to work on both Windows and Linux why are you using fonts you find on the system? Surely it's better to pick a font with a suitable licence, preprocess it, and include it with your game? I can't tell you how many Flash games I've played on my Linux box where text was invisible because I didn't have the exact same fonts as the developers and the substitute overflowed the space allowed for it. Embedding your font in the game guarantees consistency and reduces the amount of testing needed. \$\endgroup\$ Commented Oct 23, 2011 at 7:32
2
\$\begingroup\$

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] = "\0";
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.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .