Search code examples
printinggdi+clippinggdi

GDI - Clipping is not working when used on a printer device context


I'm using the Embarcadero RAD Studio C++ builder XE7 compiler. In an application project, I'm using the both Windows GDI and GDI+ to draw on several device contexts.

My drawing content is something like that:

enter image description here

On the above sample the text background and the user picture are drawn with GDI+. The user picture is also clipped with a rounded path. All the other items (the text and the emojis) are drawn with the GDI.

When I draw to the screen DC, all works fine.

Now I want to draw on a printer device context. Whichever I use for my tests is the new "Export to PDF" printer device available in Windows 10. I prepare my device context to draw on an A4 viewport this way:

HDC GetPrinterDC(HWND hWnd) const
{
    // initialize the print dialog structure, set PD_RETURNDC to return a printer device context
    ::PRINTDLG pd  = {0};
    pd.lStructSize = sizeof(pd);
    pd.hwndOwner   = hWnd;
    pd.Flags       = PD_RETURNDC;

    // get the printer DC to use
    ::PrintDlg(&pd);

    return pd.hDC;
}

...

void Print()
{
    HDC hDC = NULL;

    try
    {
        hDC = GetPrinterDC(Application->Handle);

        const TSize srcPage(793, 1123);
        const TSize dstPage(::GetDeviceCaps(hDC, PHYSICALWIDTH), ::GetDeviceCaps(hDC, PHYSICALHEIGHT));
        const TSize pageMargins(::GetDeviceCaps(hDC, PHYSICALOFFSETX), ::GetDeviceCaps(hDC, PHYSICALOFFSETY));

        ::SetMapMode(hDC, MM_ISOTROPIC);
        ::SetWindowExtEx(hDC, srcPage.Width, srcPage.Height, NULL);
        ::SetViewportExtEx(hDC, dstPage.Width, dstPage.Height,  NULL);
        ::SetViewportOrgEx(hDC, -pageMargins.Width, -pageMargins.Height, NULL);

        ::DOCINFO di = {sizeof(::DOCINFO), config.m_FormattedTitle.c_str()};

        ::StartDoc (hDC, &di);

        // ... the draw function is executed here ...

        ::EndDoc(hDC);
        return true;
    }
    __finally
    {
        if (hDC)
            ::DeleteDC(hDC);
    }
}

The draw function executed between the StartDoc() and EndDoc() functions is exactly the same as whichever I use to draw on the screen. The only difference is that I added a global clipping rect on my whole page, to avoid the drawing to overlaps on the page margins when the size is too big, e.g. when I repeat the above drawing several times under the first one. (This is experimental, later I will add a page cutting process, but this is not the question for now)

Here are my clipping functions:

int Clip(const TRect& rect, HDC hDC)
{
    // save current device context state
    int savedDC = ::SaveDC(hDC);

    HRGN pClipRegion = NULL;

    try
    {
        // reset any previous clip region
        ::SelectClipRgn(hDC, NULL);

        // create clip region
        pClipRegion = ::CreateRectRgn(rect.Left, rect.Top, rect.Right, rect.Bottom);

        // select new canvas clip region
        if (::SelectClipRgn(hDC, pClipRegion) == ERROR)
        {
            DWORD error = ::GetLastError();
            ::OutputDebugString(L"Unable to select clip region - error - " << ::IntToStr(error));
        }
    }
    __finally
    {
        // delete clip region (it was copied internally by the SelectClipRgn())
        if (pClipRegion)
            ::DeleteObject(pClipRegion);
    }

    return savedDC;
}

void ReleaseClip(int savedDC, HDC hDC)
{
    if (!savedDC)
        return;

    if (!hDC)
        return;

    // restore previously saved device context
    ::RestoreDC(hDC, savedDC);
}

As mentioned above, I expected a clipping around my page. However the result is just a blank page. If I bypass the clipping functions, all is printed correctly, except that the draw may overlap on the page margins. On the other hands, if I apply the clipping on an arbitrary rect on my screen, all works fine.

What I'm doing wrong with my clipping? Why the page is completely broken when I enables it?


Solution

  • So I found what was the issue. Niki was close to the solution. The clipping functions seem always applied to the page in pixels, ignoring the coordinate system and the units defined by the viewport.

    In my case, the values passed to the CreateRectRgn() function were wrong, because they remained untransformed by the viewport, although the clipping was applied after the viewport was set in the device context.

    This turned the identification of the issue difficult, because the clipping appeared as transformed while the code was read, as it was applied after the viewport, just before the drawing was processed.

    I don't know if this is a GDI bug or a wished behavior, but unfortunately I never seen this detail mentioned in all the documents I read about the clipping. Although it seems to me important to know that the clipping isn't affected by the viewport.