I'm writing a Windows application showing a document to the user. The content is painted using the GDI functions, and all appears as expected on the screen.
Now I want to print this document. I get a printer device context, and I do the exact same drawing as I do on the screen. Of course the printed content appears tiny on the top of the printed page. The reason of this behavior is clear for me, and is fully explained here:
https://www.codeproject.com/Articles/764057/GDI-Drawing-and-Printing
So I need to add a scaled viewport on my printer DC, and there are several functions to achieve that in the GDI. However I'm a little puzzled about HOW to configure these functions. I tried various examples found on the internet, but none of them worked for me.
My screen resolution is 1920x1080 pixels, and I'm trying to print on an A4 portrait page. I tested various configurations, and I found that the best approximation to fit on my printed page is the following:
::SetMapMode(hDC, MM_ISOTROPIC);
::SetWindowExtEx(hDC, 1, 1, NULL);
::SetViewportExtEx(hDC, 5, 5, NULL);
::SetViewportOrgEx(hDC, -10200, 0, NULL);
As the screen and print configurations may, of course, change on other PC, I need to know how the above values may be calculated, but I cannot find a formula that works in my case. Especially I don't know why I need to scale my canvas origin using the SetViewportOrgEx() function, nobody mentioned that on the documents I read.
So what is the correct manner to calculate my print DC viewport, considering that:
And as an additional question, it would be better to use a metafile to do this kind of job?
In order to map the screen coordinates to paper coordinates, we need the width and length of the paper. This information is available in GetDeviceCaps(hdc, PHYSICALWIDTH)
and GetDeviceCaps(hdc, PHYSICALHEIGHT)
, where hdc
is printer's device context. We already have the screen coordinates somewhere.
The printer cannot print on the edges of the paper. We can get that information from PHYSICALOFFSETX
and PHYSICALOFFSETY
.
The example below uses uses a common function paint
which does all the painting. print
doesn't do any painting, it calls paint
instead.
This assumes that rc.left
and rc.right
is (0,0)
in screen coordinates.
void paint(HDC hdc, RECT rc)
{
HBRUSH brush = GetSysColorBrush(COLOR_WINDOWTEXT);
InflateRect(&rc, -10, -10);
FrameRect(hdc, &rc, brush);
DrawText(hdc, L"hello world", -1, &rc, 0);
}
void print(HWND hWnd, RECT rc)
{
PRINTDLG pd = { sizeof(pd) };
pd.hwndOwner = hWnd;
pd.Flags = PD_RETURNDC;
if(!PrintDlg(&pd))
return;
HDC hdc = pd.hDC;
DOCINFO doc = { sizeof(doc) };
StartDoc(hdc, &doc);
StartPage(hdc);
SetMapMode(hdc, MM_ISOTROPIC);
SetWindowExtEx(hdc, rc.right, rc.bottom, NULL);
SetViewportExtEx(hdc,
GetDeviceCaps(hdc, PHYSICALWIDTH), GetDeviceCaps(hdc, PHYSICALHEIGHT), NULL);
SetViewportOrgEx(hdc,
-GetDeviceCaps(hdc, PHYSICALOFFSETX), -GetDeviceCaps(hdc, PHYSICALOFFSETY), NULL);
paint(hdc, rc);
EndPage(hdc);
EndDoc(hdc);
DeleteObject(hdc);
}
Testing:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
paint(hdc, rc);
EndPaint(hwnd, &ps);
break;
}
case WM_LBUTTONDOWN:
{
RECT rc;
GetClientRect(hwnd, &rc);
print(hwnd, rc);
break;
}