VFP 9.0 RTM used an unexpected default value for its DPI when providing output to a device handle of type 0 (hDC, GDI handle). When you pass this handle to the Report Engine, the Engine uses it to construct the GDI+ handle (type 1) that it needs. Windows sets a default DPI, which happens to be 96, in this situation, and the RTM Report Engine simply accepted this value without regard for the DPI of the device that would actually be the output target. Xbase code had no opportunity to provide alternative instructions about device units or resolution when it passed its other OutputPage coordinate values.
Beginning in SP1, the Report Engine has been adjusted to interrogate the handle you pass to the OutputPage method and set the DPI appropriately to the printer you're using. This makes for much simpler calculation code for most coordinate values you might use as OutputPage arguments. While the method works either way, you no longer have to adjust your code to go "back" to 96 DPI before calling OutputPage.
A simple example of the adjustment is shown in the VFP documentation for the OutputPage method, as part of BeforeReport code preceding an OutputPage call:
IF GdipCreateFromHWND( m.lH, @nG ) = 0 THIS.GP = m.nG THIS.RHeight = THIS.GetPageHeight()/10 && convert 960 DPI to 96 DPI THIS.RWidth = THIS.GetPageWidth()/10 ENDIF
This conversion, while appropriate in RTM, is unnecessary and inappropriate starting in SP1.
You will find additional information about using OutputPage to address a printer handle, in this online article. The following source code excerpt from the article demonstrates the difference in coding approach as required for the RTM and SP1-or-later versions of the base Report Engine:
#DEFINE USE_DEFAULT_DPI_GRAPHICS .T. && .T. for RTM #IF USE_DEFAULT_DPI_GRAPHICS *&* we can't work with the printer dpi, *&* VFP is working in the default when it *&* gets a Graphics object from an HDC. *&* Start by fixing page offsets. m.tl = m.tl * (DEFAULT_DPI_GRAPHICS / m.xdpi) m.tt = m.tt * (DEFAULT_DPI_GRAPHICS / m.ydpi) DO CASE CASE m.llScaleAdjust *&* fix the larger of the two dimensions *&* to fit the available space IF (m.tw > m.th) AND (m.ph > m.pw) m.tw = INT(m.th * (m.pw/m.ph) ) ELSE m.th = INT(m.tw * (m.ph/m.pw) ) ENDIF m.th = INT(m.th * (DEFAULT_DPI_GRAPHICS / m.xdpi)) m.tw = INT(m.tw * (DEFAULT_DPI_GRAPHICS / m.ydpi)) CASE THIS.ScalePages == "CLIP" *&* take what the Engine expected and fix the units m.tw = INT(m.pw * (DEFAULT_DPI_GRAPHICS / LISTENER_DPI)) m.th = INT(m.ph * (DEFAULT_DPI_GRAPHICS / LISTENER_DPI)) OTHERWISE *&* fix units to scale and stretch/fill by default m.tw = INT(m.tw * (DEFAULT_DPI_GRAPHICS / m.xdpi)) m.th = INT(m.th * (DEFAULT_DPI_GRAPHICS / m.ydpi)) ENDCASE #ELSE DO CASE CASE m.llScaleAdjust *&* fix the larger of the two dimensions *&* to fit the available space IF (m.tw > m.th) AND (m.ph > m.pw) m.tw = INT((m.th - m.tt) * (m.pw/m.ph) ) ELSE m.th = INT((m.tw - m.tl)* (m.ph/m.pw) ) ENDIF CASE THIS.ScalePages == "CLIP" *&* take what the Engine expected and fix the units m.tw = INT(m.pw * ( m.xdpi / LISTENER_DPI)) m.th = INT(m.ph * ( m.ydpi / LISTENER_DPI)) OTHERWISE *&* we're good to go to scale and *&* stretch/fill by default ENDCASE #ENDIF