Using SetMapMode during printing (Views: 100)


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
// canvas.

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 case).

We will find the real size of the paper in 0.01 mm. It will be used in further calculations:

// find the width of the printer page
MMWidth := MulDiv(GetDeviceCaps(PrinterDC, PHYSICALWIDTH), 2540,
  GetDeviceCaps(PrinterDC, LOGPIXELSX));
// find the height of the printer page
MMHeight := MulDiv(GetDeviceCaps(PrinterDC, PHYSICALHEIGHT), 2540,
  GetDeviceCaps(PrinterDC, LOGPIXELSY));

Now you have to set logical coordinates using SetWindowExtEx and physical dimensions of device context (actually, paper size) using SetViewPortExtEx.

SetWindowExtEx(TmpDC, LogExtX, LogExtY, nil);
SetViewPortExtEx(TmpDC, PhExtX, PhExtY, nil);

How do we calculate LogExt* parameters?

ScreenDC := GetDC(0);
// now find logical width of the screen space, needed to display the image in WYSIWYG mode
// Scale parameter is used to provide scaling during printing.
LogExtX := MulDiv(MMWidth, 100 * GetDeviceCaps(ScreenDC, LOGPIXELSX), 2540 * Scale);
// now find logical height of the screen space, needed to display the image in WYSIWYG mode
// Scale parameter is used to provide scaling during printing.
LogExtY := MulDiv(Printer.PageHeight, 100 * GetDeviceCaps(DC, LOGPIXELSY), 2540 *
ReleaseDC(0, screenDC);

How do we calculate PhExt* parameters?

PhExtX := MulDiv(Printer.PageWidth, GetDeviceCaps(Printer.PrinterDC, LOGPIXELSX),
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).

<< Back to main page