Author: Eugene Mayevski
Printing something is relatively easy with Delphi. But there are times, when you
need to use the same functions for drawing on screen canvas and on printer. Did you
ever try to? And you got the printer image much smaller, than on the screen, right?
That's because you have to change coordinates passed to GDI functions or use
SetMapMode function. The article is about how to use this function and a bit more.
Suppose you need to draw a rectangle with coordinates ((0,0),(300,300)). On the
screen such rectangle will be a bit bigger than one inch (at least on 1024*768
resolution on 15' monitor). But when you call Printer.Canvas.FrameRect(Rect(0, 0,
300, 300)), you get a tiny rectangle with side length of .39 inch.
So, you need to perform transformation of coordinate system before printing.
Open MSDN, see SetMapMode, feel happy. You find, that MM_ANISOTROPIC mode is what
you need (remember, that printers have different vertical and horizontal resolution
and page size, so you need to use MM_ANISOTROPIC parameter).
// we use TmpDC to prepare an image, that will be later copied to printer
But then you need to call a couple of other functions to do the job.
These functions are SetWindowExtEx and SetViewPortExtEx. As described in
documentation, these functions let you set logical and "physical" coordinate
systems for device context. What parameters do you have to pass to it?
Logical coordinates is the size of the screen part needed to display an image in
WYSIWYG mode (to get the same size as on the screen).
Physical coordinates define the size in pixels of the device media (paper in our
We will find the real size of the paper in 0.01 mm. It will be used in further
2 // find the width of the printer page
3 MMWidth := MulDiv(GetDeviceCaps(PrinterDC, PHYSICALWIDTH), 2540,
4 GetDeviceCaps(PrinterDC, LOGPIXELSX));
5 // find the height of the printer page
6 MMHeight := MulDiv(GetDeviceCaps(PrinterDC, PHYSICALHEIGHT), 2540,
7 GetDeviceCaps(PrinterDC, LOGPIXELSY));
Now you have to set logical coordinates using SetWindowExtEx and physical
dimensions of device context (actually, paper size) using SetViewPortExtEx.
8 SetWindowExtEx(TmpDC, LogExtX, LogExtY, nil);
9 SetViewPortExtEx(TmpDC, PhExtX, PhExtY, nil);
How do we calculate LogExt* parameters?
11 ScreenDC := GetDC(0);
12 // now find logical width of the screen space, needed to display the image in
13 WYSIWYG mode
14 // Scale parameter is used to provide scaling during printing.
15 LogExtX := MulDiv(MMWidth, 100 * GetDeviceCaps(ScreenDC, LOGPIXELSX), 2540 * Scale);
16 // now find logical height of the screen space, needed to display the image in
17 WYSIWYG mode
18 // Scale parameter is used to provide scaling during printing.
19 LogExtY := MulDiv(Printer.PageHeight, 100 * GetDeviceCaps(DC, LOGPIXELSY), 2540 *
21 ReleaseDC(0, screenDC);
23 //How do we calculate PhExt* parameters?
25 PhExtX := MulDiv(Printer.PageWidth, GetDeviceCaps(Printer.PrinterDC, LOGPIXELSX),
27 PhExtY := MulDiv(Printer.PageHeight, GetDeviceCaps(Printer.PrinterDC, LOGPIXELSY),
That's all, folks :). Now you can safely draw the rectangle.
Remember to restore MapMode after you finished drawing (you can save MapMode using GetMapMode function).