Sign in to follow this  
CGameProgrammer

Cannot reliably measure width of fonts

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this