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
是打印机的设备上下文。我们已经在某处有了屏幕坐标。
打印机无法打印纸张的边缘。我们可以从 PHYSICALOFFSETX
和 PHYSICALOFFSETY
.
中获取该信息
下面的例子使用了一个通用函数 paint
来完成所有的绘画。 print
不做任何绘画,它调用 paint
代替。
这假设 rc.left
和 rc.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;
}
我正在编写一个 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
是打印机的设备上下文。我们已经在某处有了屏幕坐标。
打印机无法打印纸张的边缘。我们可以从 PHYSICALOFFSETX
和 PHYSICALOFFSETY
.
下面的例子使用了一个通用函数 paint
来完成所有的绘画。 print
不做任何绘画,它调用 paint
代替。
这假设 rc.left
和 rc.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;
}