|
Dear Alexandru,
Can I ask you to upload the full project with all sources including GUI-sources too?
Thank you very much!
|
|
|
|
|
Hi,
I'm developing programs with C#. Now, I need to calculate Text Width.
TextRenderer.MeasureText and Graphics.MeasureString aren't work correctly.
For this, I start to search solution and found you.
I'm using 'GetTextExtentPoint32' but still isn't work correctly. Everytime, my program calculate wrongly small character measure. But big characters and number are calculating correctly. Please help me.
I know C++ little bit. So, with your help, I can make DLL with C++ and use that DLL in C#...
Good day...
|
|
|
|
|
Maybe a little bit late since the project is from 2006, but I tried out your code recently (as a pretty good base for my own project, thanks for that!) and found out, that when it comes to the width of the text in pixels you can actually see, than you still have to subtract the space from the very left of the rect (of the virtual bitmap) until the writing really begins. DT_LEFT still lets some space on the left. So the same procedure from the very left to the first pixel of the first letter would be necessary.
I checked it by simply making a screenshot, zoomed in and counted the pixels
I’m not completely sure if it has something to do with the internal processing of the different operating systems or whatever, but on my system the output (width in pixels) seem to be incorrect. By the way, so does the height when it’s calculated with GetTextExtentPoint32(). Its result is far away (value is too high) from the real height in pixel.
Still a pretty good work!
Dai Fei
|
|
|
|
|
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
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
|
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. 
|
|
|
|
|
1) Declare a LOGFONT structure
<br />
LOGFONT logfOut; <br />
Then initialize the fields of the structure with some values:
<br />
logfOut.lfHeight = -21;<br />
logfOut.lfWidth = 0;<br />
logfOut.lfEscapement = 0;<br />
logfOut.lfOrientation = 0;<br />
logfOut.lfWeight = FW_NORMAL;<br />
logfOut.lfItalic = (BYTE)1;<br />
logfOut.lfUnderline = (BYTE)0;<br />
logfOut.lfStrikeOut = (BYTE)0;<br />
logfOut.lfCharSet = (BYTE)DEFAULT_CHARSET;<br />
logfOut.lfOutPrecision = (BYTE)OUT_STROKE_PRECIS;<br />
logfOut.lfClipPrecision = (BYTE)CLIP_STROKE_PRECIS;<br />
logfOut.lfQuality =(BYTE)DRAFT_QUALITY;<br />
logfOut.lfPitchAndFamily =(BYTE)34;<br />
lstrcpyn(logfOut.lfFaceName, _T("Tahoma"), sizeof(logf.lfFaceName));<br />
<br />
2) Then open the font selection common dialog box
<br />
CHOOSEFONT cf;<br />
ZeroMemory(&cf,sizeof(cf));<br />
cf.lStructSize = sizeof(CHOOSEFONT);<br />
<br />
cf.hwndOwner = hWndParent;<br />
cf.hDC = NULL;<br />
cf.lpLogFont = &logfOut;
cf.iPointSize = 0;<br />
cf.Flags = CF_ENABLEHOOK|CF_INITTOLOGFONTSTRUCT|CF_EFFECTS|CF_SCREENFONTS;<br />
cf.rgbColors = rgbInit;
cf.lCustData = (LPARAM)szTitle ;<br />
cf.lpfnHook = ChooseFontHookProc ;<br />
cf.lpTemplateName = NULL;<br />
cf.hInstance = NULL;<br />
cf.lpszStyle = NULL;<br />
cf.nFontType = 0;<br />
cf.nSizeMin = 0;<br />
cf.nSizeMax = 0;<br />
<br />
<br />
BOOL result = ChooseFont(&cf);<br />
<br />
if(!result)<br />
{<br />
return FALSE;<br />
}<br />
<br />
UINT CALLBACK ChooseFontHookProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) <br />
{<br />
<br />
if (message == WM_INITDIALOG)<br />
{<br />
CHOOSEFONT* pCf = (CHOOSEFONT*)lParam;<br />
if(pCf->lCustData)<br />
SetWindowText(hDlg, (TCHAR*)(pCf->lCustData));<br />
return 1;<br />
}<br />
<br />
return 0;<br />
}<br />
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.
<br />
cf.lpLogFont = &logfOut;<br />
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)
<br />
TEXTMETRIC tm;<br />
HFONT hFontOld = (HFONT)SelectObject(hDC,hFont);<br />
GetTextMetrics(hDC, &tm);<br />
<br />
int cyChar = tm.tmHeight+tm.tmExternalLeading;<br />
int cxChar = tm.tmAveCharWidth;<br />
SelectObject(hDC,hFontOld);<br />
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
|
|
|
|
|
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.
|
|
|
|
|
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.
|
|
|
|
|
- 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);
RECT rect={0,0,sizeText.cx+sizeLastCharacter.cx,sizeText.cy}; Thank you very much.
|
|
|
|
|
An improvement would be to do a SetBkColor first, then search for that color on a horizontal line only.
|
|
|
|
|
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
|
|
|
|
|
I've sent to the site administrators an updated version of the right-to-left pixel colour scanning algorithm.
The changed algorithm is written below and to me it feels like it uses fewer iterations:
int iXmax=0;
BOOL bFound=FALSE;
for(int i=rect.right-1;i>=0 && !bFound ;i--)
{
for (int j=0;j<=rect.bottom-1 && !bFound;j++)
{
COLORREF rgbColor=GetPixel(hMemDC,i,j);
if(rgbColor!=RGB(255,255,255))
{
iXmax=i;
bFound=TRUE;
}
}
}
LONG lWidthOfText=iXmax+1; Thank you very much.
-- modified at 2:49 Thursday 17th August, 2006
|
|
|
|
|
The idea is:
FillRect(hMemDC,&rect,(HBRUSH)GetStockObject(WHITE_BRUSH));
SetBkColor(hMemDC,RGB(0,0,0));
DrawText(hMemDC,szText,-1, &rect, DT_LEFT|DT_TOP|DT_SINGLELINE|DT_NOPREFIX);
int iXmax=0;
int iYmed=(rect.bottom+rect.top)/2;
BOOL bFound=FALSE;
for(int i=rect.right-1;i>=0 && !bFound ;i--)
{
COLORREF rgbColor=GetPixel(hMemDC,i,iYmed);
if(rgbColor!=RGB(255,255,255))
{
iXmax=i;
bFound=TRUE;
}
}
LONG lWidthOfText=iXmax+1;
So instead of checking Width x Height pixels, you only check Width pixels.
For a 10 pixel font, this is 10 times faster.
And the code can be optimized a bit more:
int iXmax;
int iYmed=(rect.bottom+rect.top)/2;
for( iXmax = rect.right-1; iXmax >= 0; --iXmax )
if( GetPixel(hMemDC,iXmax,iYmed) != RGB(255,255,255) )
break;
LONG lWidthOfText = iXmax+1;
But the big improvement is (as usual) from improving the alghorithm (first trick, the SetBkColor).
========================================
About the GetCharABCWidthsFloat: never use any of the GetCharABCWidths for strings.
The width a character depends on where it is in the string:
- width("AAAAVVVV") != width("AVAVAVAV") because of kerning
- width("fi") != width("f")+width("i") because of ligatures (present only in some fonts)
- if you have complex scripts (Arabic, Thai, Hindi, etc.) it gets even worse
|
|
|
|
|
The trick of setting the background color to black before painting the text, is working well, but not in all cases.
I've found a few counterexamples that do not work properly. Please try these strings:
'k6' or 'c6' or 'a6' or 'c7' or 'c9' or 'x9' or 'c5' or 'v5' with Tahoma and Microsoft Sans Serif and Lucida Console (italic, of course).
What you're actually suggesting, is to get a fully filled rectangle surrounding the text and for this reason, scanning a single line would be enough.
That's a smart idea, but I don't know why, for the counterexamples above, the surrounding rectangle drawn by the DrawText function is not fully filled(??)
I will post an update to the article and code. The update will show us the value of the calculated width of the text, when your idea is used. That will take a day or two.
>About the GetCharABCWidthsFloat: never use any of the >GetCharABCWidths for strings.
>The width a character depends on where it is in the string:
I don't understand what you're talking about here. I've checked the MSDN articles and the overhang of the last character is quite useful.
Thank you very much.
-- modified at 6:13 Thursday 17th August, 2006
|
|
|
|
|
Alexandru Matei wrote: I've found a few counterexamples that do not work properly. Please try these strings:
'k6' or 'c6' or 'a6' or 'c7' or 'c9' or 'x9' or 'c5' or 'v5' with Tahoma and Microsoft Sans Serif and Lucida Console (italic, of course).
It works ok in my tests. It would help if you post your test application (in fact, others also suggested this).
Alexandru Matei wrote: I don't understand what you're talking about here. I've checked the MSDN articles and the overhang of the last character is quite useful.
Nope. The MSDN articles are not the Bible.
All GetCharABC... familty of functions are bad for internationalization.
You should NEVER compute the width of strings based on width of stand-alone characters.
Example: the Romanian character "tz" can be represented as one character (U+021B, latin small letter t with comma below) or as t with combining comma below (U+0074 U+0326).
But in the proposed alghorithm, the last char is the combining character, with width ZERO.
Same kind of problems where the the characters change shape depending on the context (what is before or after).
The t with comma is just an example to help understand the issues, the typical usa in Romanian is with U+021B (typical, but not mandatory )
But there are scripts where combining characters and complex shaping are the norm (Arabic, Vietnamese, Thai, most Hindic scripts).
|
|
|
|
|
1. The next EXE, posted together with the article and relevant source code, will hopefully allow you to see what I see.
Please check Windows XP with themes enabled.
Your trick is very good but I've found it not working well for all strings, as explained already. The DrawText function doesn't always fully fill the rectangle. I don't know why.
And as said, please check Windows XP with themes enabled.
The updated article should be online on Monday evening or Tuesday morning.
2. >of strings based on width of stand-alone characters.
The MSDN references mentioned in the article, learned me useful things. I'm very satisfied with how the overhang of the last character of the string, is helping to calculate an accurate width for the whole string.
I've explained this detail at the bottom of the article and the attached ZIP file (EXE and relevant source code) shows how it's working in practice.
http://support.microsoft.com/kb/74298/en-us[^]
Thank you very much.
-- modified at 1:45 Monday 21st August, 2006
|
|
|
|
|
Interesting technique. I gave up using GDI fonts about 1 1/2 years ago in favor of GDI+ fonts but your article nevertheless caught my interest...
Use GetCharABCWidths to find the overhang at the front and the back of the string.
Just add the abcA spacing for the first character in the string to the left rectangle coordinate and subtract the abcC spacing for the last character in the string to the right rectangle coordinate. This seems to work for negative or positive values.
/*
Drop this messy snippet into an MFC SDI project. Take wizard defaults for simplicity.
I may have missed something as I rushed through this so don't crucify me if I mucked it up somehow. Using VC++ 6.0
*/
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CString sOutput="This seems to work well enough";
int nCount=sOutput.GetLength();
CFont font;
LOGFONT lf={0}; // Zero out the structure
lf.lfHeight=50;
lf.lfItalic=TRUE;
lf.lfQuality=ANTIALIASED_QUALITY|PROOF_QUALITY;
lf.lfOutPrecision=OUT_TT_PRECIS;
_tcscpy(lf.lfFaceName,"Tahoma");
font.CreateFontIndirect(&lf);
pDC->SelectObject(&font);
// Populate the ABC widths array
ABC pWidthsABC[256];
pDC->GetCharABCWidths(0,255,pWidthsABC);
CRect rect(50,50,300,100);
pDC->DrawText(sOutput,rect,DT_TOP|DT_LEFT|DT_SINGLELINE|DT_NOCLIP ); // Draw it
pDC->DrawText(sOutput,rect,DT_TOP|DT_LEFT|DT_SINGLELINE|DT_CALCRECT); // Calculate
// Adjust for any overhang
rect.left+=pWidthsABC[sOutput[0]].abcA; // First character
rect.right-=pWidthsABC[sOutput[nCount-1]].abcC; // Last character
// Show the rectangle for demo
pDC->SelectStockObject(BLACK_PEN);
pDC->SelectStockObject(NULL_BRUSH);
pDC->Rectangle(rect);
// Show the correct width
TRACE("Width of string = %d\n",rect.Width());
}
-- modified at 0:31 Monday 31st July, 2006
|
|
|
|
|
Yes, for Tahoma, your code certainly works very good.
For this font, the values of the ABC structure are:
Tahoma 16, Italic
abcA abcB abcC
for character 'J': 0 13 -4
for character '6': 2 12 -3
Given the text 'July 2006', the overhang of the last character('6'), consists of 3 pixels. By adding this amount in absolute value, you get a more accurate result.
However I think that care must be taken with the arithmetic of these values of the 'underhang of the first character' and 'overhang of the last character'.
I feel that if someone goes your way, it will be safer using:
rect.left-=abs(pWidthsABC[sOutput[0]].abcA); // always increase width (First character)
rect.right+=abs(pWidthsABC[sOutput[nCount-1]].abcC); // always increase width(Last character)
Why that? I've noticed that some fonts may have unexpected values or I don't know how to interpret them correctly.
Example 1: Verdana 16, Italic
Example 2: Times New Roman 16, Italic
Example 3: Microsoft Sans Serif 16, Italic
Example 4: Courier New 16, Italic
Take the same 'July 2006' text:
The values of the ABC structure data members are:
Example 1:Verdana 16, Italic
abcA abcB abcC
for character 'J': -1 11 0
for character '6': 1 11 1
Example 2: Times New Roman 16, Italic
abcA abcB abcC
for character 'J': -1 12 -2
for character '6': 1 10 0
Example 3: Microsoft Sans Serif 16, Italic
abcA abcB abcC
for character 'J': 2 12 -3
for character '6': 2 13 -3
Example 4: Courier New 16, Italic
abcA abcB abcC
for character 'J': 2 13 -2
for character '6': 3 10 0
We can easily see that the underhang of '6' or 'J' can be a positive number for italic font and for this reason the statement:
rect.left+=pWidthsABC[sOutput[0]].abcA; // First character
is making the width smaller and that *may* cause the text to not fit anymore...
Also we can easily see that the overhang of '6' can be a positive number for italic font and for this reason the statement:
rect.right-=pWidthsABC[sOutput[nCount-1]].abcC; // Last character
is making the width smaller and that *may* cause the text to not fit anymore...
If you agree I will update the article's EXE and add this 3rd method too.
-- modified at 6:03 Monday 31st July, 2006
|
|
|
|
|
Obviously you have not tried the snippet.
-- modified at 9:18 Monday 31st July, 2006
Run the snippet with the adjustments and without and see what happens to the rectangle.
|
|
|
|
|
I wish I hadn't but I obviously have...
There is something I have to figure out. You and me get different results using the GetCharABCWidths function.
I've changed your code like this:
CString sOutput="July 2006";
lf.lfHeight=-21;
lf.lfItalic=TRUE;
lf.lfQuality=PROOF_QUALITY;//|ANTIALIASED_QUALITY;
lf.lfOutPrecision=OUT_TT_PRECIS;
lf.lfQuality=PROOF_QUALITY;
lf.lfPitchAndFamily=34;
lf.lfCharSet=DEFAULT_CHARSET;
lf.lfWeight=FW_NORMAL;
_tcscpy(lf.lfFaceName,"Tahoma");
that is Tahoma 16, Italic. By tracing *your* code, I get these results:
Tahoma 16, Italic
abcA abcB abcC
for character 'J': 0 14 -5
for character '6': 1 14 -4
And as already presented, using *my* code, I get these results:
Tahoma 16, Italic
abcA abcB abcC
for character 'J': 0 13 -4
for character '6': 2 12 -3
God knows why is that happening. My code is as simple as written below and you can paste it at the end of your OnDraw handler.
HDC hDC=::GetDC(m_hWnd);
HDC hDCMem=CreateCompatibleDC(hDC);
::ReleaseDC(m_hWnd,hDC);
HFONT hFontOld=(HFONT)SelectObject(hDCMem,(HFONT)font);
GetCharABCWidths(hDCMem,0,255,pWidthsABC);
SelectObject(hDCMem,hFontOld);
DeleteDC(hDCMem);
Just check please, why are these two structures pWidthsABC['J'] and pWidthsABC['6'] different, when using my calculation and your calculation ??
-- modified at 14:19 Monday 31st July, 2006
|
|
|
|
|
I'm not sure. I get the same results for both. (This is using your code snippets inserted into the OnDraw function at appropriate locations commenting out whatever code needed to be commented out)
J 0 13 -4
6 2 12 -3
J 0 13 -4
6 2 12 -3
|
|
|
|
|
I don't know the reason yet, but what doesn't work is using GetCharABCWidths in a memory device context(?)
If I use the device context provided by the OnDraw handler, like this:
HFONT hFontOld=(HFONT)SelectObject(pDC->m_hDC,(HFONT)font);
GetCharABCWidths(pDC->m_hDC,0,255,pWidthsABC);
SelectObject(pDC->m_hDC,hFontOld);
then my results are like yours:
Tahoma 16, Italic
abcA abcB abcC
pWidthsABC['J'] -> 0 14 -5
pWidthsABC['6'] -> 1 14 -4
Thank you very much for providing your sample code.
Still I am not satisfied until I will find out why, as explained already, the simple code below gives different results(??):
HDC hDC=::GetDC(m_hWnd);
HDC hDCMem=CreateCompatibleDC(hDC);
::ReleaseDC(m_hWnd,hDC);
HFONT hFontOld=(HFONT)SelectObject(hDCMem,(HFONT)font);
GetCharABCWidths(hDCMem,0,255,pWidthsABC);
SelectObject(hDCMem,hFontOld);
DeleteDC(hDCMem);
|
|
|
|
|
this does the same but there's no need to create a memdc and scan it
SIZE GetTextExtent(LPSTR str,HFONT hFont)
{
HDC hDC=::GetDC(NULL);
HFONT hOldFont = (HFONT)::SelectObject(hDC,hFont);
RECT rect = {0};
::DrawText(hDC,str,-1,&rect,DT_LEFT|DT_SINGLELINE|DT_CALCRECT);
::SelectObject(hDC,hOldFont);
::ReleaseDC(NULL,hDC);
SIZE retsize = {rect.right,rect.bottom};
return retsize;
}
|
|
|
|
|