如何使用 SetViewportExtEx() 和打印机确定设备单位的水平和垂直范围?

How to determine horizontal and vertical extents in device units with SetViewportExtEx() and printer?

我正在尝试使用 Windows GDI API 进行打印,并且一直在做一些实验来尝试理解翻译以及 window 和视口范围的工作方式。

我发现的示例是使用 GetDeviceCaps() 来获取 HORZRES 和 VERTRES 维度(尽管众所周知,它们可能不可靠且不准确),然后将这些值与 SetViewportExtEx() 一起使用,但是它们将GetDeviceCaps() 返回的值乘以两个。

为什么 cxpagecypage 值减半?我如何预测要使用的值以及对打印输出的影响?这是因为使用MM_ISOTROPIC作为映射模式吗?

示例使用如下代码:

int cxpage = GetDeviceCaps (hDC, HORZRES);
int cypage = GetDeviceCaps (hDC, VERTRES);
SetMapMode (hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);

在我的实际测试程序中,当我的主要 Windows 消息处理程序看到用户从测试的文件菜单中选择打印时生成的消息 IDM_PRINT 时,我有以下功能来打印页面应用。处理程序使用 PrintDlg() 获取设备上下文 (hDC) 的句柄,然后调用此函数来执行打印。

int PrintMyPages (HDC hDC)
{
    int cxpage = GetDeviceCaps (hDC, HORZRES);
    int cypage = GetDeviceCaps (hDC, VERTRES);

    // When MM_ISOTROPIC mode is set, an application must call the
    // SetWindowExtEx function before it calls SetViewportExtEx. Note that
    // for the MM_ISOTROPIC mode certain portions of a nonsquare screen may
    // not be available for display because the logical units on both axes
    // represent equal physical distances.
    SetMapMode (hDC, MM_ISOTROPIC);

    // Since mapping mode is MM_ISOTROPIC we need to specify the extents of the
    // window and the viewport we are using to see the window in order to establish
    // the proper translation between window and viewport coordinates.
    SetWindowExtEx(hDC, 1500, 1500, NULL);
    SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
    SetViewportOrgEx(hDC, 0, 0, NULL);

    // figure out the page size in logical units for the loop that is printing
    // out the pages of output. we must do this after setting up our window and
    // viewport extents so Windows will calculate the DPtoLP() for the specified
    // translation correctly.
    RECT pageRect = {0};
    pageRect.right = GetDeviceCaps (hDC, HORZRES);
    pageRect.bottom = GetDeviceCaps (hDC, VERTRES);
    DPtoLP(hDC, (LPPOINT)&pageRect, 2);

    // create my font for drawing the text to be printed and select it into the DC for printing.
    HFONT DisplayFont = CreateFont (166, 0, 0, 0, FW_DONTCARE, false, false, false, DEFAULT_CHARSET,
                                      OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
                                      DEFAULT_PITCH | FF_DONTCARE, _T("Arial Rounded MT Bold"));
    HGDIOBJ hSave = SelectObject (hDC, DisplayFont);

    POINT ptLine = {300, 200};  // our printer line cursor for where printing should start.

    static DOCINFO di = { sizeof (DOCINFO), TEXT ("INVOICE TABLE : Printing...")};
    StartDoc (hDC, &di);
    StartPage (hDC);

    for (int i = 1; i < 30; i++) {
        TCHAR xBuff[256] = {0};
        swprintf (xBuff, 255, _T("This is line %d of my text."), i);
        TextOut (hDC, ptLine.x, ptLine.y, xBuff, _tcslen(xBuff));
        // get the dimensions of the text string in logical units so we can bump cursor to next line.
        SIZE  lineSize = {0};
        GetTextExtentPoint32(hDC, xBuff, _tcslen(xBuff), &lineSize);
        ptLine.y += lineSize.cy;    // bump the cursor down to the next line of the printer. X coordinate stays the same.
        if (ptLine.y + lineSize.cy > pageRect.bottom) {
            // reached the end of this page so lets start another.
            EndPage (hDC);
            StartPage (hDC);
            ptLine.y = 200;
        }
    }

    // end the final page and then end the document so that physical printing will start.
    EndPage (hDC);
    EndDoc (hDC);

    // Release the font object that we no longer need.
    SelectObject (hDC, hSave);
    DeleteObject (DisplayFont);

    return 1;
}

当我将 SetViewportExtEx() 的调用从 SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);(下图右侧输出)修改为 SetViewportExtEx(hDC, cxpage, cypage, NULL);(下图左侧输出)时,打印文本的高度和宽度似乎几乎翻了一番。

关于范围和映射模式的补充说明

Charles Petzold 编程 Windows 第 5 版(第 5 章 - 基本绘图,第 180 页)写道:

The formulas also include two points that specify "extents": the point (xWinExt, yWinExt) is the window extent in logical coordinates; (xViewExt, yViewExt) is the viewpoort extent in device coordinates. In most mapping modes, the extents are implied by the mapping mode and cannot be changed. Each extent means nothing by itself, but the ratio of the viewport extent to the window extent is a scaling factor for converting logical units to device units.

For example, when you set the MM_LOENGLISH mapping mode, Windows sets xViewExt to be a certain number of pixels and xWinExt to be the length in hundredths of an inch occupied by xViewExt pixels. The ratio gives you pixels per hundredths of an inch. The scaling factors are expressed as ratios of integers rather than floating point values for performance reasons.

Petzold 然后在第 187 页继续讨论 MM_ISOTROPICMM_ANISOTROPIC

The two remaining mapping modes are named MM_ISOTROPIC and MM_ANISOTROPIC. These are the only two mapping modes for which Windows lets you change the viewport and window extents, which means that you can change the scaling factor that Windows uses to translate logical and device coordinates. The word isotropic means "equal in all directions"; anisotropic is the opposite - "not equal." Like the metric mapping modes shown earlier, MM_ISOTROPIC uses equally scaled axes. Logical units on the x-axis have the same physical dimensions as logical units on the y-axis. This helps when you need to create images that retain the correct aspect ratio regardless of the aspect ratio of the display device.

The difference between MM_ISOTROPIC and the metric mapping modes is that with MM_ISOTROPIC you can control the physical size of the logical unit. If you want, you can adjust the size of the logical unit based on the client area. This lets you draw images that are always contained within the client area, shrinking and expanding appropriately. The two clock programs in Chapter 8 have isotropic images. As you size the window, the clocks are resized appropriately.

A Windows program can handle the resizing of an image entirely through adjusting the window and viewport extents. The program can then use the same logical units in the drawing functions regardless of the size of the window.

... why do so many examples use SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL); where cxpage and cypage are GetDeviceCaps(hDC, HORZRES) and GetDeviceCaps(hDC, VERTRES) respectively[?]

我怀疑 MM_ISOTROPIC 通常用于绘制原点位于页面中心而不是角落的图形。如果我们采用您的代码并对其进行调整以将原点移动到可打印区域的中心,如下所示:

SetWindowExtEx(hDC, 1500, 1500, NULL);
SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);
SetViewportOrgEx(hDC, cxpage/2, cypage/2, NULL);

然后您可以使用范围从 -1500 到 +1500 的逻辑坐标进行绘图。 (您可能还想翻转其中一个 y 范围的符号以获得正数 "up"。)

对于文本输出,我认为将视口范围减半没有任何好处,我会保留左上角的原点。