Search code examples
windowsprintingscaleviewportgdi

Windows - GDI - Scaling a screen DC to a printer DC without modifying the draw functions


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:

  • The exactly same painting functions will be used for both the screen and printer drawing, and I will NEVER write different functions to print on the screen and the printer
  • The screen and printer devices may be entirely configured by the user, but the printed result should always fit the document on both the screen and the printer

And as an additional question, it would be better to use a metafile to do this kind of job?


Solution

  • 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;
    }