I have printing code that draws grid on the paper.
Grid has 4 columns, and they have equal horizontal length. Height of the cell is tenth of the paper size. Total number of rows is unknown but I know for a fact that there will be at least one row.
Each cell has same physical size-> width is quarter of the paper width, and height is one tenth of the paper height. Maximum number of characters that can fit into cell is 50.
The problem I face is choosing proper font size so text of maximum length can fit into cell.
Browsing through MSDN documentation and WinAPI
examples, I saw that they use GetTextExtPoint32
for similar purposes, but this works only if font already exists and is selected into device context, which is not the case here.
The only thing that crossed my mind was to create "dummy font", see if the example text can fit into cell, and then adjust it's size if the test fails. I have also found this blog that recommends interesting approach to this problem, but being inexperienced I can't decide if "this is the proper way to go".
Can you recommend a correct solution for my problem?
EDITED on June, 30th 2014:
Below is the sample function that draws grid and paints upper left cell in light gray since that cell will contain sample text. That way we can visually validate the success of our drawing code:
// hWnd is the window that owns the property sheet.
HRESULT GDI_PRINT(HWND hWnd)
{
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges)
return E_OUTOFMEMORY;
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWnd;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
if ( ( hResult == S_OK ) && ( pdx.dwResultAction == PD_RESULT_PRINT ) )
{
// User clicked the Print button,
// so use the DC and other information returned in the
// PRINTDLGEX structure to print the document.
//======= Various initializations ==========//
DOCINFO diDocInfo = {0};
diDocInfo.cbSize = sizeof( DOCINFO );
diDocInfo.lpszDocName = L"Testing printing...";
int pageWidth = GetDeviceCaps( pdx.hDC, HORZRES ),
pageHeight = GetDeviceCaps( pdx.hDC, VERTRES );
//===================== IMPORTANT !!! ==========================//
// Must test this on real printer !!! //
// For now testing is done in XPS and MS OneNote2007 //
//==============================================================//
//================== end of initialization =====================//
if( StartDoc( pdx.hDC, &diDocInfo ) > 0 )
{
if( StartPage( pdx.hDC ) > 0 )
{
//===== creating red pen that will draw grid =====//
LOGBRUSH lb;
lb.lbColor = RGB( 255, 0, 0 );
lb.lbHatch = 0;
lb.lbStyle = BS_SOLID;
HPEN hPen = ExtCreatePen( PS_COSMETIC | PS_SOLID, 1, &lb, 0, NULL);
HGDIOBJ oldPen = SelectObject( pdx.hDC, hPen );
// create test font
HFONT font, oldFont;
long lfHeight = -MulDiv( 14,
GetDeviceCaps( pdx.hDC, LOGPIXELSY ),
72 );
font = CreateFont( lfHeight, 0, 0, 0,
FW_BOLD, TRUE, FALSE, FALSE,
0, 0, 0,
0, 0, L"Microsoft Sans Serif" );
oldFont = SelectFont( pdx.hDC, font );
SetBkMode( pdx.hDC, TRANSPARENT );
SetTextColor( pdx.hDC, RGB( 255, 0, 0 ) );
// testing rectangle -> top left cell of the grid
RECT rcText;
rcText.left = 0;
rcText.top = 0;
rcText.right = pageWidth / 4;
rcText.bottom = pageHeight / 10;
// fill destination rectangle with gray brush
// so we can visually validate rectangle coordinates
FillRect( pdx.hDC, &rcText, (HBRUSH)GetStockObject(LTGRAY_BRUSH) );
// implement solution mentioned in the comment to this question
SIZE s;
::GetTextExtentPoint32( pdx.hDC,
L"Хидрогеотермална енергија Хидрогеотермална енерги",
wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ),
&s );
// select old font back and dispose test font
SelectObject( pdx.hDC, oldFont );
DeleteObject( font );
// adjust font height
lfHeight *= s.cy / ( rcText.bottom - rcText.top );
// now we can create proper font
font = CreateFont( lfHeight, 0, 0, 0,
FW_BOLD, TRUE, FALSE, FALSE,
0, 0, 0,
0, 0, L"Microsoft Sans Serif" );
oldFont = SelectFont( pdx.hDC, font );
// draw text in test rectangle
DrawTextEx( pdx.hDC,
L"Хидрогеотермална енергија Хидрогеотермална енерги",
wcslen( L"Хидрогеотермална енергија Хидрогеотермална енерги" ),
&rcText, DT_CENTER | DT_WORDBREAK | DT_NOCLIP, NULL );
//============== draw a testing grid ===============//
// draw vertical lines of the grid
for( int i = 0; i <= pageWidth; i += pageWidth / 4 )
{
MoveToEx( pdx.hDC, i, 0, NULL );
LineTo( pdx.hDC, i, pageHeight );
}
// draw horizontal lines of the grid
for( int j = 0; j <= pageHeight; j += pageHeight / 10 )
{
MoveToEx( pdx.hDC, 0, j, NULL );
LineTo( pdx.hDC, pageWidth, j );
}
// no need for pen anymore so delete it
SelectObject( pdx.hDC, oldPen );
DeleteObject( hPen );
// no need for font, delete it
SelectFont( pdx.hDC, oldFont );
DeleteFont( font );
if( EndPage( pdx.hDC ) < 0 )
// for now pop a message box saying something went wrong
MessageBox( hWnd, L"EndDoc failed!", L"Error", MB_OK );
}
EndDoc( pdx.hDC );
}
}
if (pdx.hDevMode != NULL)
GlobalFree(pdx.hDevMode);
if (pdx.hDevNames != NULL)
GlobalFree(pdx.hDevNames);
if (pdx.lpPageRanges != NULL)
GlobalFree(pPageRanges);
if (pdx.hDC != NULL)
DeleteDC(pdx.hDC);
return hResult;
}
To use this function, just launch it on button press/menu selection or whatever.
The results in XPS seem consistent, but I get strange results in MS OneNote 2007 which following images illustrate:
Font size is 14 :
Font size is 20 :
Font size is 20, but scaling from the above function was applied :
END OF EDIT
EDITED on July, 6th 2014:
The third picture from above edit was the result of GDI
using default height value because the result of my mathematical adjustment for font height was 0
. Once zero is passed to CreateFont mentioned behavior is expected.
After performing proper casting from double
to int
I got nearly perfect output -> last letter in the string barely exceeds the limit. I will continue to try improving this formula since I believe is promising. If anybody has another mathematical solution feel free to post it.
END OF EDIT
If further info / edit is required, leave a comment and I will react as soon as possible.
There are multiple issues involved.
The biggest problem I see is in this line:
These are all integers. In C and C++, division with integers results in truncation toward zero. So if the result of the division "should" be 3.7, you'll end up with 3, which can be a pretty crude approximation.
Another problem is that GetTextExtentPoint32 does not wrap text, but DrawText does. So you're measuring the text as though you're going to print it as a single line, and you actually draw it as multiple lines. Instead of using GetTextExtendPoint32, you can measure the height with DrawText by DT_CALCRECT flag.
Putting these together, you want to measure your text like this:
Okay. Basically, I start off with the suggested pointSize (14 in your code) and try to draw the text using the supplied bounding rect. If the text is too large, I go into an iterative loop that decreases the pointsize and measures again until the text will fit into the bounding rect.
If, on the other hand, the text is 'too small' I go into a loop that gradually increases it's size until it is too large. Once I reach this point, I decrease the point-size by 2 and return.
The reduction by 2 is a kludge or hack. I noticed that at times the size was reported as being equal to or smaller than the reported size of the bounding rect, yet still some characters would protrude past the edge of the bounding rect.
A better solution would make use of the DrawTextEx function to both calculate the size and draw the text. This would be better since you could make use of the iLeftmargin and iRightMargin members of the DRAWTEXTPARAMS struct that is passed to that function. Whether you wished to have a margin on each side, or simply wanted to add a single character's width, that you then halved when drawing the text would depend entirely on the desired outcome. I also added the DT_EXTERNALLEADING flag to obtain a small margin above/below the text, though there isn't one for vertical padding, so you'd have to make use of the margin attributes I mention.
Since the DT_VCENTER flag doesn't work with multi-line text, you'd also need to vertically offset the text yourself if you wished it to be vertically centered. You'd just have to offset the rect used for actually drawing the text by half of the difference between the area bounding rect's height and the text bounding rect's height.
I could have used a function like this for a few projects, so thanks for the impetus to actually exercise the grey matter and work it out!
Lastly, I used an interactive demo - one that responded to the WM_PAINT message of a (empty) dialog box. Since a HDC can be treated more-or-less the same whether it be for a printer or the screen, it provided a much quicker way of investigating the result.
Output when plugged into your code: (via cutePDF virtual printer)
Code: