Author Topic: Microsoft is WRONG, WRONG, WRONG (printing from a RichEdit control)  (Read 61 times)

NoCforMe

  • Member
  • ****
  • Posts: 721
I just figured out something almost on my own (well, with help from some stuff I found on the interwebs) that might save someone else some grief, so I wanted to get it posted here. This is how to print pages from a RichEdit control using the EM_FORMATRANGE message.

The thing is, the Microsoft documentation on how to print using this is dead wrong. Couldn't be wronger. (And the really odd thing is that this page is almost exactly the same code as given as an example by the usually astute Raymond Chen.) There's just no way this code can work. And because this is given as gospel by Microsoft, the error gets propagated across the web on numerous other sites.

Details: there are two RECTs in the structure that EM_FORMATRANGE uses. One is a "rendering rectangle", fr.rect, and the other is a "page rectangle", fr.pageRect. Now, they say to do this to fill out these RECTs:

Code: [Select]
    int cxPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETX);
    int cyPhysOffset = GetDeviceCaps(hdc, PHYSICALOFFSETY);
   
    int cxPhys = GetDeviceCaps(hdc, PHYSICALWIDTH);
    int cyPhys = GetDeviceCaps(hdc, PHYSICALHEIGHT);

    // Set page rect to physical page size in twips.
    fr.rcPage.top    = 0; 
    fr.rcPage.left   = 0; 
    fr.rcPage.right  = MulDiv(cxPhys, 1440, GetDeviceCaps(hDC, LOGPIXELSX)); 
    fr.rcPage.bottom = MulDiv(cyPhys, 1440, GetDeviceCaps(hDC, LOGPIXELSY));

    // Set the rendering rectangle to the pintable area of the page.
    fr.rc.left   = cxPhysOffset;
    fr.rc.right  = cxPhysOffset + cxPhys;
    fr.rc.top    = cyPhysOffset;
    fr.rc.bottom = cyPhysOffset + cyPhys;

But that's just plain wrong. They're mixing apples and oranges here; the page RECT is getting set to dimensions measured in "twips" (a twip is 1/20th of a point, or 1/1440th of an inch), while the rendering RECT is being set to device units (from GetDeviceCaps() ), which are not the same units.

This is a glaring error; obviously nobody up there in Redmond actually checked this code. The correct thing to do is to set both RECTs in twips. That works, as I just proved, both with a physical printer and with a PDF creator.

The other error is that you need to subtract the X- and Y-offsets from the page width and height, respectively, not add them. In fact, you need to subtract twice the offset for each dimension, since there's an offset at both the left and right (and top and bottom).

Here's my code (in assembler) that works:

Code: [Select]
INVOKE GetDeviceCaps, pdex.hDC, PHYSICALOFFSETX
MOV pPhysOffsetX, EAX
INVOKE GetDeviceCaps, pdex.hDC, PHYSICALOFFSETY
MOV pPhysOffsetY, EAX

INVOKE GetDeviceCaps, pdex.hDC, PHYSICALWIDTH
MOV pPhysW, EAX
INVOKE GetDeviceCaps, pdex.hDC, PHYSICALHEIGHT
MOV pPhysH, EAX

; Set up "page rectangle":
MOV fr.rcPage.top, 0
MOV fr.rcPage.left, 0
INVOKE GetDeviceCaps, pdex.hDC, LOGPIXELSX
MOV xPPI, EAX
INVOKE MulDiv, pPhysW, 1440, EAX
MOV fr.rcPage.right, EAX
INVOKE GetDeviceCaps, pdex.hDC, LOGPIXELSY
MOV yPPI, EAX
INVOKE MulDiv, pPhysH, 1440, EAX
MOV fr.rcPage.bottom, EAX

; Now the "rendering rectangle":
INVOKE MulDiv, pPhysOffsetX, 1440, xPPI
MOV fr.rc.left, EAX
INVOKE MulDiv, pPhysOffsetY, 1440, yPPI
MOV fr.rc.top, EAX
INVOKE MulDiv, pPhysW, 1440, xPPI
MOV EDX, fr.rc.left
SHL EDX, 1
SUB EAX, EDX
MOV fr.rc.right, EAX
INVOKE MulDiv, pPhysH, 1440, yPPI
MOV EDX, fr.rc.top
SHL EDX, 1
SUB EAX, EDX
MOV fr.rc.bottom, EAX

This could probably be simplified a bit to eliminate some of those MulDiv()s, but that's icing on the cake. (Speed is of no concern here, since we're dealing with a printer.)