Cannot reliably measure width of fonts

Started by
4 comments, last by CGameProgrammer 14 years, 3 months ago
I'm rendering a font to a texture so I can compose strings using polygonal meshes. In order to support italic fonts (where the characters need to be closer together than the actual widths of the glyphs) and fonts with characters that overhang the left or right edges, I have determined after much research that the Win32 API function GetCharABCWidths is the best one to use. It returns a structure containing three values: the offset added to the caret before rendering begins, the actual width of the character, and the offset added after rendering to indicate where the next glyph should be drawn. Testing with 18-point italic Impact, the letter "O" returns {2, 14, -3}. That means drawing starts two pixels to the right of the caret, the italic O should be exactly 14 pixels wide (with no antialiasing) and then the caret should be moved three pixels to the left before drawing the next glyph, or 2+14-3 = 13 pixels to the right of the original location. That works very well. The problem is it returns incorrect values for certain fonts, even without italics. For example 18-point regular Microsoft Sans Serif returns {1, 15, 1} but the actual rendered width of the O character is 17 pixels, not 15. Why are different fonts inconsistent? What is a 100% reliable (for TrueType fonts) method of determining character width and spacing?
~CGameProgrammer( );Developer Image Exchange -- New Features: Upload screenshots of your games (size is unlimited) and upload the game itself (up to 10MB). Free. No registration needed.
Advertisement
It could be that you need to apply kerning values.

http://msdn.microsoft.com/en-us/library/dd144895(VS.85).aspx
You have to set a current font. Asking Windows to build your fonts doesn't mean that will be the current font, and all string measurements use the current font.

YourCurrentFont = CreateFont( ..... );
SelectObject(win_state.hDC, YourCurrentFont);

Sorry, I've just read through. Anyway, maybe you didn't set/update the current truetype font.
Quote:Original post by CGameProgrammer

That works very well. The problem is it returns incorrect values for certain fonts, even without italics. For example 18-point regular Microsoft Sans Serif returns {1, 15, 1} but the actual rendered width of the O character is 17 pixels, not 15.


That 'two pixels too wide' issue sounds like subpixel-rendering.
Try to use GetTextExtentPoint32(). It is sufficient in most cases.
There was no subpixel rendering since I was using non-antialiased fonts. But in fact I solved the problem - the font height passed in to CreateFont was incorrect. I'm using C# and was using the 'Height' member of the C# Font object, which on a 96-DPI display is 28 pixels for an 18-pt font. But it seems this is wrong. The actual correct value to pass in is -24 in this case. The code, found on MSDN somewhere, is:

int pixels = -MulDiv( points, GetDeviceCaps(hdc, LOGPIXELSY), 72);

...where points=18 in this case. 72 is how many points there are per inch and this is universal, not a display setting. It's not the same as dots per inch.

Anyway, this works. The completed test code is:

#include <Windows.h>#include <stdio.h>ABC Measure ( int size, const char* type, bool italic, char ascii ){   HWND window = GetDesktopWindow();   HDC dc = GetDC( window );      int fontHeight = -MulDiv( size, GetDeviceCaps(dc, LOGPIXELSY), 72 );   HFONT font = CreateFont( fontHeight,                            0,                            0,                            0,                            400, // weight = normal                            italic ? 1 : 0,                            0,                            0,                            ANSI_CHARSET,                            OUT_TT_ONLY_PRECIS,                            CLIP_DEFAULT_PRECIS,                            NONANTIALIASED_QUALITY,                            0,                            type );   HGDIOBJ old = SelectObject( dc, font );   ABC abc;   GetCharABCWidths( dc, (UINT)ascii, (UINT)ascii, &abc );   SelectObject( dc, old );   ReleaseDC( window, dc );      return abc;}int main ( ){   ABC abcO = Measure( 18, "Microsoft Sans Serif", false, 'O' );      printf( "O: %i, %i, %i\n", abcO.abcA, abcO.abcB, abcO.abcC );      return 0;}


Output is {1,17,1} on a 96-DPI display.
~CGameProgrammer( );Developer Image Exchange -- New Features: Upload screenshots of your games (size is unlimited) and upload the game itself (up to 10MB). Free. No registration needed.

This topic is closed to new replies.

Advertisement