GDI - 在打印机设备上下文中使用时剪辑不起作用

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

我正在使用 Embarcadero RAD Studio C++ builder XE7 编译器。在一个应用程序项目中,我同时使用 Windows GDI 和 GDI+ 来绘制多个设备上下文。

我画的内容是这样的:

在上面的示例中,文本背景和用户图片是用GDI+ 绘制的。用户图片也用圆形路径裁剪。所有其他项目(文本和表情符号)均使用 GDI 绘制。

当我绘制到屏幕 DC 时,一切正常。

现在我想在打印机设备上下文上绘图。无论我用于测试的是 Windows 10 中可用的新 "Export to PDF" 打印机设备。我准备我的设备上下文以这种方式在 A4 视口上绘制:

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

在StartDoc() 和EndDoc() 函数之间执行的绘制函数与我在屏幕上绘制时使用的绘制函数完全相同。唯一的区别是我在我的整个页面上添加了一个全局剪切矩形,以避免当尺寸太大时绘图在页边距上重叠,例如当我在第一个下重复上面的图几次时。 (这是实验性的,稍后我会添加一个切页过程,但现在这不是问题)

这是我的剪辑函数:

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

如上所述,我希望在我的页面周围进行剪裁。然而结果只是一个空白页。如果我绕过裁剪功能,所有内容都可以正确打印,只是绘图可能会重叠在页边距上。另一方面,如果我在屏幕上的任意矩形上应用剪辑,一切正常。

我的剪辑有什么问题?为什么我启用后页面完全崩溃?

所以我找到了问题所在。 Niki 接近解决方案。裁剪功能似乎总是以像素为单位应用于页面,忽略视口定义的坐标系和单位。

在我的例子中,传递给 CreateRectRgn() 函数的值是错误的,因为它们仍未被视口转换,尽管在设备上下文中设置视口后应用了剪裁。

这使得问题的识别变得困难,因为在读取代码时剪辑显示为已转换,因为它是在视口之后应用的,就在处理绘图之前。

我不知道这是一个 GDI 错误还是一个希望的行为,但不幸的是,我从未在我阅读的所有关于剪辑的文档中看到这个细节。尽管在我看来知道裁剪不受视口影响很重要。