Solved

VC++: Windows Font Rendering

Posted on 2004-07-30
11
912 Views
Last Modified: 2013-12-03
Hello experts,
in my C++ program (MFC)  I need to detect for a given character set
the information of the number of pixels required to render each of the existing characters.

A sample could be: arial.
How can I get the number of pixels and the size of the letter I, W (as samples).

If you know how to do this
please supply an appropriate snippet

Thank you for any help.

   HStrix
0
Comment
Question by:HStrix
  • 5
  • 5
11 Comments
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 11676383
Arial is a TrueType font that can be sized as you want, so a 'character set' - better known as font family or face name - isn't associated to a fixed number of pixels beside of some fixed-size system fonts.

If you create the font yourself you may use CFont::CreateFontIndirect member function. I takes a LOGFONT struct

typedef struct tagLOGFONT { // lf
   LONG lfHeight;
   LONG lfWidth;
   LONG lfEscapement;
   LONG lfOrientation;
   LONG lfWeight;
   BYTE lfItalic;
   BYTE lfUnderline;
   BYTE lfStrikeOut;
   BYTE lfCharSet;
   BYTE lfOutPrecision;
   BYTE lfClipPrecision;
   BYTE lfQuality;
   BYTE lfPitchAndFamily;
   TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;

where you can set all font characteristics. However, by choosing the face name many other attributes are already determined.

If you don't create the font yourself but wamts to know the characteristics of an existing font you can call CFont::GetLogFont() member function. You should consider that it is a 'logical' or better device-independent font till now. To get a device-dependent view on the font you need a device context DC, e. g. the screen DC, that could be constructed in any member function of a CWnd derived class (CDialog, CView, CListbox, CEdit, ...)

   CClientDC dc(this);

With that  you can get the current font characteristics by

  TEXTMETRIC tm;
  dc.GetTextMetrics(&tm);


The TEXTMETRIC struct has physical attributes rather than logical as LOGFONT

typedef struct tagTEXTMETRIC {  /* tm */
    int  tmHeight;
    int  tmAscent;
    int  tmDescent;
    int  tmInternalLeading;
    int  tmExternalLeading;
    int  tmAveCharWidth;
    int  tmMaxCharWidth;
    int  tmWeight;
    BYTE tmItalic;
    BYTE tmUnderlined;
    BYTE tmStruckOut;
    BYTE tmFirstChar;
    BYTE tmLastChar;
    BYTE tmDefaultChar;
    BYTE tmBreakChar;
    BYTE tmPitchAndFamily;
    BYTE tmCharSet;
    int  tmOverhang;
    int  tmDigitizedAspectX;
    int  tmDigitizedAspectY;
} TEXTMETRIC;


Regards, Alex



0
 
LVL 86

Expert Comment

by:jkr
ID: 11677419
>>How can I get the number of pixels and the size of the letter I, W (as samples).

The metrics of individual letters in a font is available via 'GetTextExtentPoint32()'  - see e.g. http://support.microsoft.com/default.aspx?scid=kb;en-us;125681 ("How To Calculate Dialog Base Units with Non-System-Based Font"):

SIZE size;
hFontOld = SelectObject(hdc,hFont);
GetTextExtentPoint32(hdc,"I",1,&size);

0
 

Author Comment

by:HStrix
ID: 11691120
Thank you,
I created the following code:
---
CString getSizes(SIZE psize)
{
      CString cstr1 = " ";
      CString cstr2 = " ";      
      DWORD dw1 = psize.cx;
      DWORD dw2 = psize.cy;
      int     decimal,   sign;
      int     precision = 10;
      // first get precision (== number of existing digits)
      cstr1 = _ecvt( dw1, precision, &decimal, &sign  );
      precision=decimal;  // set target digits to number of existing digits
      // second get proper digit string
      cstr1 = _ecvt( dw1, precision, &decimal, &sign  );
      // first get precision (== number of existing digits)
      cstr2 = _ecvt( dw2, precision, &decimal, &sign  );
      precision=decimal;  // set target digits to number of existing digits
      // second get proper digit string
      cstr2 = _ecvt( dw2, precision, &decimal, &sign  );
      CString cstrSites = "cx=" + cstr1 + " - cy=" + cstr2;
      return (cstrSites);
}

                HDC hdc    = ::GetDC(NULL);
      CFont font;
      VERIFY(font.CreateFont(
      12,                                         // nHeight
      0,                                           // nWidth
      0,                                           // nEscapement
      0,                                           // nOrientation
      FW_NORMAL,                          // nWeight
      FALSE,                                    // bItalic
      FALSE,                                    // bUnderline
      0,                                           // cStrikeOut
      ANSI_CHARSET,                      // nCharSet
      OUT_DEFAULT_PRECIS,           // nOutPrecision
      CLIP_DEFAULT_PRECIS,           // nClipPrecision
      DEFAULT_QUALITY,                 // nQuality
      DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
      "Arial"));                                 // lpszFacename
      HANDLE hFontOld;
      TEXTMETRIC tm;
      GetTextMetrics(hdc, &tm);
      SIZE size;
      hFontOld = SelectObject(hdc,font);
      GetTextExtentPoint32(hdc,"I",1,&size);
      CString I_Size = "I - " + getSizes(size);
      GetTextExtentPoint32(hdc,"W",1,&size);
      CString W_Size = "W - " + getSizes(size);
      CString cstrAll = I_Size + "\r\n" + W_Size;
---
and I get as result:
---
I - cx=3 - cy=12
W - cx=9 - cy=12
---
That looks OK.

But it is still unclear to me
how I can use an existing <any>.ttf (or <any>.fon) file in that code.
Can you help me modifying above's code accordingly?

  HStrix
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 11691289
The main input isn't the .ttf or .fon filename but the lpszFaceName. The valid names you may find e. g. by MS Word or Notepad or VS Resource Editor. They are in the combo box when selecting a new font.

You also may use EnumFontFamiliesEx() to get all existing face names .

// that callback function will be called for any font family available
// the lParam argument is a pointer to the class that collects all names and characteristics

int CALLBACK EnumFontFamExProc(
  ENUMLOGFONTEX *lpelfe,    // pointer to logical-font data
  NEWTEXTMETRICEX *lpntme,  // pointer to physical-font data
  int FontType,             // type of font
  LPARAM lParam             // application-defined data
)
{
      CMyDialog* pDlg = (CMyDialog*)lParam;
      pDlg->addFontToFontList(lpelfe, lpntme, FontType);
}

// invokes enumeration of all font family names
void CMyDialog::getAllFontFaceNames(HDC hdc)
{
     LOGFONT lf;
     lf.lfCharset     = DEFAULT_CHARSET;
     lf.lfFaceName = "";   // empty string
     lf.lfPitchAndFamily = 0;
     EnumFontFamiliesEx(hdc, &lf, EnumFontFamExProc, this);
}

Regards, Alex
0
 

Author Comment

by:HStrix
ID: 11691692
Thank you Alex,
but I do not understand how to setup the following for further use:
---
     CFont font;
     VERIFY(font.CreateFont(
     12,                                         // nHeight
     0,                                           // nWidth
     0,                                           // nEscapement
     0,                                           // nOrientation
     FW_NORMAL,                          // nWeight
     FALSE,                                    // bItalic
     FALSE,                                    // bUnderline
     0,                                           // cStrikeOut
     ANSI_CHARSET,                      // nCharSet
     OUT_DEFAULT_PRECIS,           // nOutPrecision
     CLIP_DEFAULT_PRECIS,           // nClipPrecision
     DEFAULT_QUALITY,                 // nQuality
     DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
     "Arial"));                                 // lpszFacename
---
I coded:
---
      //get logfont info:
      LOGFONT lf;
      //to style and charset for a selected font (Arial):
      lf.lfFaceName[0] = '\0';
      lf.lfCharSet = DEFAULT_CHARSET;
      HRESULT hr;
      //
      //to set the Arial font:
      hr = StringCchCopy( (LPSTR)&lf.lfFaceName, 6, "Arial" );
      //
      // get related Device Context (DC)
      CFont font;
---
Now it is unclear to me, how to continue.
I mean, I need to code "HDC hdc = ???;"

After this I'ld like to use the code
---
      TEXTMETRIC tm;
      GetTextMetrics(hdc, &tm);
      //
      SIZE size;
      HANDLE hFontOld = SelectObject(hdc,font);
      GetTextExtentPoint32(hdc,"I",1,&size);
---
Is this a proper way?

  HStrix



0
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 11692104
>> I mean, I need to code "HDC hdc = ???;"

A HDC is a handle to a device context, e. g. to the screen or a printer device. You'll get a screen device by

   CClientDC dc(this);
   HDC hdc = dc.GetSafeHdc( );

   ...

for printer devices you'll get an CDC object for example with CView::OnPrint(CDC* pDC, CPrintInfo* pInfo).

Regards, Alex


0
 

Author Comment

by:HStrix
ID: 11692246
Thank you Alex,
now my code is as follows:
---
      LOGFONT lf;
      lf.lfFaceName[0] = '\0';
      lf.lfCharSet = DEFAULT_CHARSET;        HRESULT hr;
      //
      hr = StringCchCopy( (LPSTR)&lf.lfFaceName, 6, "Arial" );
      //
      CFont font;
               // do I need to do here something concerning variable font (base on lf)?
      CWnd* edit1A = m_pMainWnd->GetDescendantWindow( IDC_EDIT1 );
      //CClientDC dc(this);     // <== error "C2664: cannot convert parameter 1 to CWnd" occurs
      CClientDC dc(edit1A);
      HDC hdc = dc.GetSafeHdc();
                //
      TEXTMETRIC tm;
      GetTextMetrics(hdc, &tm);
      //
      HANDLE hFontOld = SelectObject(hdc,font);
      GetTextExtentPoint32(hdc,"I",1,&size);
      CString I_Size = "I - " + getSizes(size);
      GetTextExtentPoint32(hdc,"W",1,&size);
      CString W_Size = "W - " + getSizes(size);
      //
      CString cstrAll = I_Size + "\r\n" + W_Size;
      //
      CWnd* edit1 = m_pMainWnd->GetDescendantWindow( IDC_EDIT1 );
        edit1->SetWindowText(cstrAll);
---
It seems working, but the result has changed (perhaps I need to do something as above mentioned):
---
I - cx=4 - cy=16
W - cx=14 - cy=12
---

Can you explain this?

   HStrix


0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 11692318
You have to 'create' the font prior to 'selecting' it.

     CFont font;
     font.CreateFontIndirect(&lf);

But check LOGFONT members before. You should set all LOGFONT data members to the appropriate default value.

Regards, Alex
0
 

Author Comment

by:HStrix
ID: 11692681
Thanks again Alex,
it is working.
Do I understand it right, that the statement
    CClientDC dc(edit1A); // as above
ensures that the field IDC_EDIT1
is setup for the selected font?

   HStrix
0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 500 total points
ID: 11692744
>> Do I understand it right, that the statement
>>    CClientDC dc(edit1A); // as above
>> ensures that the field IDC_EDIT1
>> is setup for the selected font?

A CClientDC is a device context (dc) that works on the client area of the CWnd* object that is given as input. So, if you select a new font, brush, or pen and use any GDI functions like DrawText, Move, ... you would write to the client area of the edit field using the GDI objects selected.
 

>> //CClientDC dc(this);     // <== error "C2664: cannot convert parameter 1 to CWnd" occurs

Normally, you use CClientDC in a member function of your CDialog or CView derived class. Then, 'this' would work. You may use any other CWnd derived object, e. g. m_pMainWnd, or the edit field  like you did it in your code.

>> It seems working, but the result has changed

If you need the sizes of a currently used font, e. g. the font used in an edit field, you have to retrieve the font from the edit field using GetFont() rather than to create a new font.

     CWnd* pEdit = m_pMainWnd->GetDescendantWindow( IDC_EDIT1 );
     CFont* pFont = pEdit->GetFont();

     CClientDC dc(pEdit);
     HDC hdc = dc.GetSafeHdc();
 
     GetTextExtentPoint32(hdc,"I",1,&size);
     CString I_Size = "I - " + getSizes(size);
     GetTextExtentPoint32(hdc,"W",1,&size);
     CString W_Size = "W - " + getSizes(size);
     
You don't have to 'select' the font as it already is 'selected'. If you want to know the characteristics of another font than the current one, you could do it like that:

     CFont font;

     LOGFONT lf;
     // set LOGFONT parameters
     ....
     font.CreateFontIndirect(&lf);

     CWnd* pEdit = m_pMainWnd->GetDescendantWindow( IDC_EDIT1 );
     CFont* pOldFont = pEdit->SetFont(&font);

     // Now the new font should be selected
     // To make sure we could repeat the selection

     CClientDC dc(pEdit);
     HDC hdc = dc.GetSafeHdc();
 
     // To make sure we could repeat the selection
     CFont* pDCFont = (CFont*)dc.SelectObject(&font);

     GetTextExtentPoint32(hdc,"I",1,&size);
     CString I_Size = "I - " + getSizes(size);
     GetTextExtentPoint32(hdc,"W",1,&size);
     CString W_Size = "W - " + getSizes(size);
     
     dc.SelectObject(pDCFont);
     pEdit->SetFont(pOldFont);

Regards, Alex
0
 

Author Comment

by:HStrix
ID: 11692923
OK Alex,
I do very appreciate your help and your explanations!
Thank you very much.

    HStrix
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Errors will happen. It is a fact of life for the programmer. How and when errors are detected have a great impact on quality and cost of a product. It is better to detect errors at compile time, when possible and practical. Errors that make their wa…
C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.ht…
The viewer will learn how to pass data into a function in C++. This is one step further in using functions. Instead of only printing text onto the console, the function will be able to perform calculations with argumentents given by the user.
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now