Windows - GDI - 在不修改绘图函数的情况下将屏幕 DC 缩放为打印机 DC

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

我正在编写一个 Windows 应用程序,向用户显示文档。使用 GDI 函数绘制内容,并且所有内容都按预期显示在屏幕上。

现在我要打印这份文件。我得到了一个打印机设备上下文,并且我在屏幕上进行了完全相同的绘图。当然,打印的内容在打印页面的顶部显得很小。这种行为的原因对我来说很清楚,并在此处进行了全面解释:

https://www.codeproject.com/Articles/764057/GDI-Drawing-and-Printing

所以我需要在我的打印机 DC 上添加一个缩放视口,在 GDI 中有几个函数可以实现它。但是我对如何配置这些功能有点困惑。我尝试了在互联网上找到的各种示例,但其中 none 对我有用。

我的屏幕分辨率是 1920x1080 像素,我正在尝试在 A4 纵向页面上打印。我测试了各种配置,发现最适合打印页面的近似值如下:

::SetMapMode(hDC, MM_ISOTROPIC);
::SetWindowExtEx(hDC, 1, 1, NULL);
::SetViewportExtEx(hDC, 5, 5, NULL);
::SetViewportOrgEx(hDC, -10200, 0, NULL);

当然,由于屏幕和打印配置可能会在其他 PC 上发生变化,我需要知道如何计算上述值,但我找不到适用于我的情况的公式。特别是我不知道为什么我需要使用 SetViewportOrgEx() 函数缩放我的 canvas 原点,在我阅读的文档中没有人提到这一点。

那么计算我的打印 DC 视口的正确方式是什么,考虑到:

另外一个问题,使用图元文件来完成这种工作会更好吗?

为了将屏幕坐标映射到纸张坐标,我们需要纸张的宽度和长度。此信息在 GetDeviceCaps(hdc, PHYSICALWIDTH)GetDeviceCaps(hdc, PHYSICALHEIGHT) 中可用,其中 hdc 是打印机的设备上下文。我们已经在某处有了屏幕坐标。

打印机无法打印纸张的边缘。我们可以从 PHYSICALOFFSETXPHYSICALOFFSETY.

中获取该信息

下面的例子使用了一个通用函数 paint 来完成所有的绘画。 print 不做任何绘画,它调用 paint 代替。

这假设 rc.leftrc.right 在屏幕坐标中是 (0,0)

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

测试:

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