title: How to determine horizontal and vertical extents in device units with SetViewportExtEx() and printer?
body: <p>I am experimenting with using the Windows GDI API for printing and have been doing a few experiments to attempt to understand the translation and how window and viewport extents work.</p>
  
  <p>Examples I have found are using <code>GetDeviceCaps()</code> to get the HORZRES and VERTRES dimensions (despite the known fact they can be unreliable and inaccurate) and then using these values with <code>SetViewportExtEx()</code> however they divide the values returned by <code>GetDeviceCaps()</code> by two.</p>
  
  <p>Why is the <code>cxpage</code> and the <code>cypage</code> values halved and how can I predict the values to use and the effect on the printed output? Is this due to using <code>MM_ISOTROPIC</code> as the mapping mode?</p>
  
  <p>Examples use something like the following code:</p>
  
  <pre><code>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);
  </code></pre>
  
  <p>In my actual test program I have the following function to print a page when my main Windows message handler sees the message <code>IDM_PRINT</code> generated when the user selects Print from the File menu of the test application. The handler uses <code>PrintDlg()</code> to get a handle to a Device Context (hDC) then calls this function to exercise the printing.</p>
  
  <pre><code>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)&amp;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, &amp;di);
      StartPage (hDC);
  
      for (int i = 1; i &lt; 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), &amp;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 &gt; 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;
  }
  </code></pre>
  
  <p>When I modify the call of <code>SetViewportExtEx()</code> from <code>SetViewportExtEx(hDC, cxpage/2, cypage/2, NULL);</code> (output on the right in the image below) to <code>SetViewportExtEx(hDC, cxpage, cypage, NULL);</code> (output on the left in the image below) the printed text seems almost double in height and width.</p>
  
  <p><a href="https://i.stack.imgur.com/d9Qhq.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/d9Qhq.jpg" alt="enter image description here"></a></p>
  
  <p><strong>Additional Notes on Extents and Mapping Modes</strong></p>
  
  <p>Charles Petzold Programming Windows 5th Edition (Chapter 5 - Basic Drawing, page 180) writes:</p>
  
  <blockquote>
    <p>The formulas also include two points that specify "extents": the point
    (<em>xWinExt</em>, <em>yWinExt</em>) is the window extent in logical coordinates;
    (<em>xViewExt</em>, <em>yViewExt</em>) 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.</p>
    
    <p>For example, when you set the MM_LOENGLISH mapping mode, Windows sets
    <em>xViewExt</em> to be a certain number of pixels and <em>xWinExt</em> to be the length in hundredths of an inch occupied by <em>xViewExt</em> 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.</p>
  </blockquote>
  
  <p>Petzold then goes on to discuss <code>MM_ISOTROPIC</code> and <code>MM_ANISOTROPIC</code> on page 187.</p>
  
  <blockquote>
    <p>The two remaining mapping modes are named <code>MM_ISOTROPIC</code> and
    <code>MM_ANISOTROPIC</code>. 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 <em>isotropic</em> means "equal in
    all directions"; <em>anisotropic</em> is the opposite - "not equal." Like the
    metric mapping modes shown earlier, <code>MM_ISOTROPIC</code> 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.</p>
    
    <p>The difference between <code>MM_ISOTROPIC</code> and the metric mapping modes is
    that with <code>MM_ISOTROPIC</code> 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.</p>
    
    <p>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.</p>
  </blockquote>