Click here to Skip to main content
Click here to Skip to main content

Width of text in italic font

By , 23 Aug 2006
 

Introduction

Our purpose is to measure accurately the width of text written in italic font.

Using the code

The GetTextExtentPoint32 Win32 API function or the DrawText Win32 API function with the DT_CALCRECT flag, will -not- tell us the correct width of text in italic font, only the calculated height is correct. For most of the italic fonts, using these two functions, the calculated width is too narrow and the displayed text looks right-trimmed.

We could try using the GetCharABCWidths or GetCharABCWidthsFloat Win32 API functions. These functions will give us information about the underhang and the overhang of each character of the text we're interested in.

Please check these MSDN articles for more details about using the underhang and overhang values:

This updated version of the EXE includes a new checkbox, to see how adding the 'overhang of the last character' will help getting a more accurate result.

However, in this article, I am suggesting another approach:

  1. Paint the text in black colour, in a memory device context filled with white colour, like this:
    SIZE sizeText;
    //Calculate the width of the text, by using the classic method
    GetTextExtentPoint32(hDCMem,szText,lstrlen(szText),&sizeText);
    
    //Calculate the width of the last character, as suggested by 'oupoi'
    SIZE sizeLastCharacter;
    GetTextExtentPoint32(hDCMem,&szText[-1+lstrlen(szText)],1,&sizeLastCharacter);
    
    //Set a bounding rectangle wide enough to fit the painted text
    RECT rect={0,0,sizeText.cx+sizeLastCharacter.cx,sizeText.cy};
    
    //Fill the background with white colour then paint the text in black colour
    FillRect(hDCMem,&rect,(HBRUSH)GetStockObject(WHITE_BRUSH));
    DrawText(hDCMem,szText,-1,&rect, 
             DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
  2. Then, scan the colour of the pixels in the memory device context, from right-to-left, like this:
    int iXmax=0;
    BOOL bFound=FALSE;
    for(int x=rect.right-1; x>=0 && !bFound; x--)
    {
       for(int y=0; y<=rect.bottom-1 && !bFound; y++)
       {
          COLORREF rgbColor=GetPixel(hDCMem,x,y);
          if(rgbColor!=RGB(255,255,255))
          {
              //found a non-white pixel, save the horizontal position 
              //and exit the loops. Job finished.
              iXmax=x;
              bFound=TRUE;
          }
       }
    }
    
    //this is the width of the text painted in italic font!
    LONG lWidthOfText=iXmax+1;//+1 because we use 0-based indexes

A few comments:

  • Painted text can be narrower than text calculated with GetTextExtentPoint32. See, for example, Verdana 12 Italic.
  • In a dialog box, the edit controls have left/right and up/down margins to take into account. I have not used edit controls in this sample.
  • In a dialog box, the label controls seem to be +1 pixel wider, unless the SS_SIMPLE style is used. See the image below.

History

  • Version 1.0 [July 23, 2006] - Created.
  • Version 1.1 [August 6, 2006] - New checkbox to see how adding the 'overhang of the last character' to the classic method of calculating the text width will help getting a more accurate result.

    Like this:

    SIZE sizeText; 
    GetTextExtentPoint32(hDCMem, szText, lstrlen(szText), &sizeText);
    LONG lWidthOfText= sizeText.cx;
                        
    ABCFLOAT WidthsABC[256];
    GetCharABCWidthsFloat(hDCMem, 0, 255, WidthsABC); 
    
    // overhang of the last character
    double dOverhangTrailing = WidthsABC[szText[lstrlen(szText)-1]].abcfC;
    
    if(dOverhangTrailing<0)
    {
       //if the overhang is negative then adjust 
       //the calculated width of the text
       lWidthOfText-=dOverhangTrailing;
    }

    But as already mentioned above, this article is suggesting another method, which doesn't require using any of these underhang and overhang values.

  • Version 1.2 [August 17, 2006] - Adjustment to the right-to-left pixel colour scanning algorithm.

    Set the right-limit of the bounding rectangle to be scanned to just 'sizeText.cx+widthOfTheLastCharacter', as suggested by 'oupoi'.

    We now also have a new checkbox to test if using the Mihai Nita's trick will help getting a faster calculation and at the same time, of course, an accurate result.

    Unless I've made a mistake somewhere, I'm not satisfied by the precision of the result by using the Mihai Nita's trick.

    Like this:

    //fill with white
    FillRect(hDCMem,&rect,(HBRUSH)GetStockObject(WHITE_BRUSH));
    
    // Added this by Mihai Nita - August 17, 2006
    // trick, set text background colour to black, before painting!
    SetBkColor(hDCMem,RGB(0,0,0)); 
    
    //reality shows that DrawText fails to properly 
    //paint the surrounding rectangle (??)
    DrawText(hDCMem,szText,-1, &rect, 
             DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
    
    int iXmax=0;
    BOOL bFound=FALSE;
    int iYmed=(rect.bottom+rect.top)/2; // middle
    
    for(int x=rect.right-1;x>=0 && !bFound ;x--)
    {
      COLORREF rgbColor=GetPixel(hDCMem,x,iYmed);
      if(rgbColor!=RGB(255,255,255))
      {
        iXmax=x;
        bFound=TRUE;
      }
    }

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Alexandru Matei
Web Developer
Romania Romania
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralAbout text width [modified]memberKsuyong19 May '08 - 2:06 
Hi, Alexandru Matei.
 
I've been read and reread your article.
It's very helpful to my programming.
 
But I have a question:
In your demo project, how do you process unicode characters (for example written in Japanese)?
How can I get a text width written in Japanese or Chinese?
 
Can you give me the answer?
Please help me as soon as possible.
 
Hope your reply.
Best regards.
 
modified on Monday, May 19, 2008 8:25 AM

GeneralRe: About text widthmemberAlexandru Matei19 May '08 - 2:18 
Hello,
 
- I would need a screen capture of the application running on your computer.
 
- Unfortunately I can't do a test myself using the two fonts you've mentioned above (PMingLiU, Calibri)
 
- It is possible that the algorithm described in the article fails for Japanese characters, I can't say.
GeneralInterestingmemberkjsfuture16 May '08 - 18:20 
I read your article.
Your article is useful.
I'm a VC++ programming beginner.
I need your advice on a personal matter.
I understood your program.
But I have a questions.
If I click the "Font to use" button , and then select the name, size, style of Font.
I guess your program will send this information to LOGFONT structure.
At that time, You will change the font size to tmheight of LOGFONT.
How do you translate the information?
If you select the font name "Arial", font size "17", what value is tmheight ?
help me.
GeneralHelp mememberkjsfuture16 May '08 - 17:19 
I have been read your article.
It's interesting.
I need your advice on a personal matter.
I'm a VC++ Programming Beginner.
In your Project Demo, If I click the "Font to Use" button, FontSelection Dialog is appeared.
I select the name, size , color of Font, and then click "OK" button.
I guess that your program do mapping the font information to LOGFONT struct.
And then will use the GetTextExtentpoint32 function.
Here's my questions.
1. How do you send the Font information to LOGFONT struct?
2. What relation is between the Font size and tmheight of LOGFONT.
 
My master gives a Task.
I have to carry my Task.
help me.
Regard. Smile | :)
GeneralRe: Help me [modified]memberAlexandru Matei16 May '08 - 20:22 
1) Declare a LOGFONT structure
 

LOGFONT logfOut;

 
Then initialize the fields of the structure with some values:
 

logfOut.lfHeight = -21;
logfOut.lfWidth = 0;
logfOut.lfEscapement = 0;
logfOut.lfOrientation = 0;
logfOut.lfWeight = FW_NORMAL;
logfOut.lfItalic = (BYTE)1;
logfOut.lfUnderline = (BYTE)0;
logfOut.lfStrikeOut = (BYTE)0;
logfOut.lfCharSet = (BYTE)DEFAULT_CHARSET;
logfOut.lfOutPrecision = (BYTE)OUT_STROKE_PRECIS;
logfOut.lfClipPrecision = (BYTE)CLIP_STROKE_PRECIS;
logfOut.lfQuality =(BYTE)DRAFT_QUALITY;
logfOut.lfPitchAndFamily =(BYTE)34;
lstrcpyn(logfOut.lfFaceName, _T("Tahoma"), sizeof(logf.lfFaceName));
 

 
2) Then open the font selection common dialog box
 

CHOOSEFONT cf;
ZeroMemory(&cf,sizeof(cf));
cf.lStructSize = sizeof(CHOOSEFONT);
 
cf.hwndOwner = hWndParent;
cf.hDC = NULL;
cf.lpLogFont = &logfOut; //the selected font LOGFONT structure
cf.iPointSize = 0;
cf.Flags = CF_ENABLEHOOK|CF_INITTOLOGFONTSTRUCT|CF_EFFECTS|CF_SCREENFONTS;
cf.rgbColors = rgbInit; //the selected font color
cf.lCustData = (LPARAM)szTitle ;
cf.lpfnHook = ChooseFontHookProc ;
cf.lpTemplateName = NULL;
cf.hInstance = NULL;
cf.lpszStyle = NULL;
cf.nFontType = 0;
cf.nSizeMin = 0;
cf.nSizeMax = 0;
 

BOOL result = ChooseFont(&cf);
 
if(!result)
{
return FALSE;
}
 
UINT CALLBACK ChooseFontHookProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
 
if (message == WM_INITDIALOG)
{
CHOOSEFONT* pCf = (CHOOSEFONT*)lParam;
if(pCf->lCustData)
SetWindowText(hDlg, (TCHAR*)(pCf->lCustData));
return 1;
}
 
return 0;
}

 
You can see that before and after calling ChooseFont, the font information is set in the cf.lpLogFont field, which is a pointer to a LOGFONT structure.
 

cf.lpLogFont = &logfOut;

 

 
When you close the dialog box with OK, you'll automatically have the selected font in the same cf.lpLogFont variable and the selected font color in cf.rgbColors;
 
3)
TEXTMETRIC tm;
HFONT hFontOld = (HFONT)SelectObject(hDC,hFont);
GetTextMetrics(hDC, &tm);

int cyChar = tm.tmHeight+tm.tmExternalLeading;
int cxChar = tm.tmAveCharWidth;
SelectObject(hDC,hFontOld);

 
After you select a font in a device context, you can obtain TEXTMETRIC information. The fields of the TEXTMETRIC structure and their meaning can be found in the MSDN and by searching the web.
 
Read the "Programming Windows" book, written by Charles Petzold:
 
http://www.microsoft.com/mspress/books/sampchap/2344a.aspx[^]
 
modified on Saturday, May 17, 2008 2:29 AM

GeneralInterestingmemberwaldermort24 Aug '06 - 22:33 
This is a similar to what I have just coded. (I just submited an article). I wanted to draw vertical text for which there are no magic getCharABCD() functions. My approach was to create a bitmap large enough for a single character and scan the bits. i.e. not use GetPixel(). When the mods have finished with my article you will see what I mean.
GeneralNice, but...memberoupoi16 Aug '06 - 18:18 
As I see, the width of text string in italic font is usually a bit wider than "sizeText.cx". But the difference is not more than width of the ending character.
 
Rectangle of width "2*sizeText.cx" seems to be too large and brings more works to do.
GeneralRe: Nice, but...memberAlexandru Matei16 Aug '06 - 20:40 
- Yes, that's correct.
 
- I will modify the right-limit of the rectangle to be scanned to just 'sizeText.cx+widthOfTheLastCharacter'.
Like this:
SIZE sizeLastCharacter;
GetTextExtentPoint32(hMemDC, &szText[-1+lstrlen(szText)], 1, &sizeLastCharacter);
 
//enough to fit the text
RECT rect={0,0,sizeText.cx+sizeLastCharacter.cx,sizeText.cy};
Thank you very much.

GeneralIt seems quite inefficientmemberMihai Nita16 Aug '06 - 9:41 
An improvement would be to do a SetBkColor first, then search for that color on a horizontal line only.

GeneralRe: It seems quite inefficient [modified]memberAlexandru Matei16 Aug '06 - 10:14 
Please give more details about your idea. What do you mean by "a horizontal line only" and how would it be possible?
 
In what conditions did you find the calculation is not efficient enough?
 
The article is also presenting how to use the GetCharABCWidthsFloat with references to MSDN articles.
 
Feel free to choose whatever method you prefer.
 

 
-- modified at 16:16 Wednesday 16th August, 2006

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 23 Aug 2006
Article Copyright 2006 by Alexandru Matei
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid