Recent

Author Topic: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue  (Read 1011 times)

AgriMensor

  • Jr. Member
  • **
  • Posts: 63
I'm using EM_FORMATRANGE to print multiple TRichMemo controls (plain and RTF text) to a printer (physical or virtual PDF) in a multi-column layout on a single page. The first column prints correctly, but columns 2 and 3 have text rendering increasingly to the left, overlapping the previous column's content.

Environment:
Windows 10
Lazarus 2.2.6 /FPC 3.2.2 with TRichMemo component
Printer DPI: X=768, Y=600 (fall back)
Converting pixel coordinates to twips: MulDiv(pixels, 1440, DPI)
Using WS_EX_TRANSPARENT for background transparency (there are yellow 'notes' forming the background to each column of text)
Printing 3 columns of RTF controls at different X positions on the same page, and 2 rows of these per (usually Landscape) page

Code:
The whole program is around 6,000 lines of code, so here is an extract from the relevant Helper routine (RTF_Print_Helper):

Code: Pascal  [Select][+][-]
  1. // Convert rectangle from pixels to twips
  2. RectTwips.Left := MulDiv(ARect.Left, 1440, LogPixelsX);
  3. RectTwips.Top := MulDiv(ARect.Top, 1440, LogPixelsY);
  4. RectTwips.Right := MulDiv(ARect.Right, 1440, LogPixelsX);
  5. RectTwips.Bottom := MulDiv(ARect.Bottom, 1440, LogPixelsY);
  6.  
  7. // Set up FORMATRANGE
  8. FormatRange.hdc := PrinterCanvas.Handle;
  9. FormatRange.hdcTarget := PrinterCanvas.Handle;
  10. FormatRange.rc := RectTwips;
  11. FormatRange.rcPage.Left := 0;
  12. FormatRange.rcPage.Top := 0;
  13. FormatRange.rcPage.Right := MulDiv(HORZRES, 1440, LogPixelsX);
  14. FormatRange.rcPage.Bottom := MulDiv(VERTRES, 1440, LogPixelsY);
  15.  
  16. // Render
  17. SendMessage(RichMemo.Handle, EM_FORMATRANGE, 1, LPARAM(@FormatRange));

What I've tried:
Fixed pixel offsets (works for column 0 only)
PHYSICALOFFSETX compensation (adding/subtracting)
SetViewportOrgEx viewport origin adjustment
Proportional offsets based on X coordinate
Setting rcPage to full page vs rendering rectangle position

Questions:
1. Is there a known issue with EM_FORMATRANGE and absolute positioning in multi-column layouts?
2. What's the correct way to handle coordinate calculations for RTF controls positioned at different X coordinates on the same page?

Any help much appreciated!  :-)

rvk

  • Hero Member
  • *****
  • Posts: 6953
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #1 on: February 09, 2026, 09:32:39 pm »
And this is under Windows?
I've never used multi-column in rtf but I do use tabs.
My experience is that a lot of different versions of RICHED20.DLL (and successor) can have lots of different quarks.

Also you don't show what arect etc is.
When you pass a value that's too small you can get unexpected results.
See what it does if you make FormatRange.rc the same as FormatRange.rcPage.

Or are you using FormatRange.rc to do the column layout (multiple times).

Maybe you can post an example rtf and show the problem (so we can test with different versions).

AgriMensor

  • Jr. Member
  • **
  • Posts: 63
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #2 on: February 10, 2026, 06:11:02 pm »
Hi Rik!
Thanks for grabbing this one and for your successful assistance with other (unrelated) problems in the past!

My response:
Yes, it is Windows 10 (see under the "Environment" heading on my original e-Mail for full platform description).
I tested setting FormatRange.rc equal to FormatRange.rcPage (full page area) as suggested. Result: No difference in output - text still renders at the same wrong positions.  :'(
It seems to me that EM_FORMATRANGE appears to be ignoring the rc parameter entirely. Whether I pass the specific column rectangle or the full page area, text renders at the same incorrect positions:
Debug evidence:
Column 0: Original rc.Left would be 662 twips, set to 0 (full page) → no change in rendering
Column 1: Original rc.Left would be 4528 twips, set to 0 (full page) → no change in rendering
Column 2: Original rc.Left would be 8394 twips, set to 0 (full page) → no change in rendering
Also, the rendering is significantly slower now (suggesting it's processing the full page area), but the text still appears at the wrong positions, not at (0,0) as expected.

Question:
Could this be related to:
  • RichMemo/TRichEdit control state that overrides rc?
    A Windows message or property affecting rendering position?
    RICHED20.DLL version quirk?
What controls where EM_FORMATRANGE actually renders, if not the rc parameter?

Regarding using Tabs as an alternative approach, this would mean a very large redesign and refactoring as:
  • Major architectural change - I'd need to combine all notes into one RTF document
    Complex RTF generation - managing tab stops, line breaks, formatting per note
    Loss of individual note control - can't easily draw borders around each note
    Yellow backgrounds per note - would be very difficult to achieve
    Doesn't match the established design architecture - we have separate TRichMemo controls per note

Thanks!
Steve

rvk

  • Hero Member
  • *****
  • Posts: 6953
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #3 on: February 10, 2026, 06:39:30 pm »
Debug evidence:
Column 0: Original rc.Left would be 662 twips, set to 0 (full page) → no change in rendering
Column 1: Original rc.Left would be 4528 twips, set to 0 (full page) → no change in rendering
Column 2: Original rc.Left would be 8394 twips, set to 0 (full page) → no change in rendering
Also, the rendering is significantly slower now (suggesting it's processing the full page area), but the text still appears at the wrong positions, not at (0,0) as expected.
So what did you mean by multi-column layout???
You have several RTF parts which you print separately (as in columns).
Or do you really have one RTF with actual columns?

If the first (separate RTF's) then I use this often in my program.
I also just do this:
Code: Pascal  [Select][+][-]
  1. Range.rcPage := r;
  2. Range.Rc := r;
So I never use rc different from rcPage.

You do need to set the rect correctly.
I normally use CmToTwips for this (where value is in cm).
Code: Pascal  [Select][+][-]
  1. function CmToTwips(Value: double): integer;
  2. const
  3.   CCMPerInch = 2.540;
  4.   CTwipsPerInch = 1440;
  5. begin
  6.   Result := Round((Value * CTwipsPerInch) / CCMPerInch);
  7. end;

Don't forget to set the HDC and hdcTarget correctly (but you already do).

Then I do:
Code: Pascal  [Select][+][-]
  1. LastChar := SendMessage(Rich2.Handle, EM_FORMATRANGE, 1, Longint(@Range));

And did you also include flushing?
Code: Pascal  [Select][+][-]
  1. SendMessage(Rich2.Handle, EM_FORMATRANGE, 0, 0); { flush buffer }

If you are really using columns in one rtf (which I'm not sure RICHED20.DLL can do) you need to show some rtf examples.

Can you show images (and/or code) of the problem?

AgriMensor

  • Jr. Member
  • **
  • Posts: 63
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #4 on: February 10, 2026, 08:54:50 pm »
Hi Rik!
Thanks for this.
We have several separate RTF parts printed in columns (the first scenario you mentioned). Each note is a separate TRichMemo control, and we print them side-by-side in a grid layout - 3 columns per row, 2 rows per (usually Landscape) page. We call PrintRichMemoToRect() separately for each note, passing different rectangles for each column position (e.g., column 0 at X=343, column 1 at X=2209, column 2 at X=4075). We do NOT have one RTF document with actual column formatting.

I've implemented your approach exactly as you described:
Code: Pascal  [Select][+][-]
  1. // Convert pixels to twips using printer DPI
  2. function PixelsToTwips(Pixels: Integer; DPI: Integer): Integer;
  3. const
  4.   CTwipsPerInch = 1440;
  5. begin
  6.   Result := MulDiv(Pixels, CTwipsPerInch, DPI);
  7. end;
  8.  
  9. // In the printing function:
  10. RectTwips.Left := PixelsToTwips(ARect.Left, LogPixelsX);
  11. RectTwips.Top := PixelsToTwips(ARect.Top, LogPixelsY);
  12. RectTwips.Right := PixelsToTwips(ARect.Right, LogPixelsX);
  13. RectTwips.Bottom := PixelsToTwips(ARect.Bottom, LogPixelsY);
  14.  
  15. // Set rc = rcPage as rvk does
  16. FormatRange.rcPage := RectTwips;
  17. FormatRange.rc := RectTwips;
  18.  
  19. // Call EM_FORMATRANGE
  20. LastChar := SendMessage(RichMemo.Handle, EM_FORMATRANGE, 1, LPARAM(@FormatRange));
  21.  
  22. // Flush buffer
  23. SendMessage(RichMemo.Handle, EM_FORMATRANGE, 0, 0);[code=pascal]
[/code]

Unfortunately, despite using your exact approach, the text still renders incorrectly:
Column 0 (first note): Text aligns slightly to the left
Column 1 (second note): Text renders far to the LEFT of where it should be, overlapping column 0
Column 2 (third note): Text renders even further LEFT, overlapping column 1
It's as if EM_FORMATRANGE is ignoring the rc.Left coordinate entirely and always rendering at some fixed position regardless of what rectangle we pass.

From my debug log for a 3-column print:
Column 0: Input Rect (pixels): L=343, T=2420, R=2179, B=4261
          Converted Rect (twips): L=662, T=5808, R=4085, B=10226
          rc = rcPage = (662,5808,4085,10226) twips
          Result: Text renders slightly to the left

Column 1: Input Rect (pixels): L=2415, T=2420, R=4251, B=4261
          Converted Rect (twips): L=4528, T=5808, R=7971, B=10226
          rc = rcPage = (4528,5808,7971,10226) twips
          Result: Text renders FAR LEFT (overlaps column 0)

Column 2: Input Rect (pixels): L=4477, T=2420, R=6313, B=4261
          Converted Rect (twips): L=8394, T=5808, R=11837, B=10226
          rc = rcPage = (8394,5808,11837,10226) twips
          Result: Text renders EVEN FURTHER LEFT (overlaps column 1)

Printer DPI: X=768, Y=0 (falls back to 600)

Tests We've Tried:
  • Setting rc = rcPage (your approach) - no effect
    Setting rc to full page area (0,0,PageWidth,PageHeight) - no effect, text still renders at same wrong positions
    Various pixel offsets and adjustments - no improvement
    Using SetViewportOrgEx with PHYSICALOFFSETX - made it worse
    Creating temporary invisible RichEdit controls positioned at print coordinates - caused access violation crash

The critical finding:
When we tested setting rc to the full page area instead of the column-specific rectangle, the text rendered at exactly the same wrong positions. This would seem to prove that EM_FORMATRANGE is completely ignoring the rc parameter we pass to it.

Questions:

  • Could this be a printer driver quirk? Note that our Y DPI returns 0 (we fall back to 600). Could this indicate a driver issue (Epson ET-4850 Series) that's also affecting EM_FORMATRANGE? I've tried using the standard Microsoft "Print to PDF" driver as well, and the displacements are even more to the left.
    Are we missing something about the DC state? Do we need to set any specific DC properties before calling EM_FORMATRANGE?
    Could the on-screen RichEdit control's window position be interfering? The TRichMemo controls are visible on-screen at various positions. Could EM_FORMATRANGE be using the window's screen position instead of the rc we provide?
    Does your approach work with landscape orientation? We're printing in landscape mode with a 3x2 grid.

rvk

  • Hero Member
  • *****
  • Posts: 6953
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #5 on: February 10, 2026, 09:20:06 pm »
Unfortunately, despite using your exact approach, the text still renders incorrectly:
Column 0 (first note): Text aligns slightly to the left
Column 1 (second note): Text renders far to the LEFT of where it should be, overlapping column 0
Column 2 (third note): Text renders even further LEFT, overlapping column 1
It's as if EM_FORMATRANGE is ignoring the rc.Left coordinate entirely and always rendering at some fixed position regardless of what rectangle we pass.
Looking at this description, I think you are working with a wrong value for LogPixelsX and LogPixelsY.
Try setting a hard value like 1200. How did you come to LogPixelsX and Y and did you actually set the resolution of the printer.

My CRM system uses this heavily. For every element (like adres, header for offer/subject, header text, text, footer text and custom bloks) I use a different RTF and it takes it's coordinates out of a layout table (so the customer can change it's layout themselves). It works on sub-mm precision.

The Bottom and Right values for the range are not really needed unless you have a right aligned text in your RTF. Otherwise they maybe very big, they may overlap and that doesn't matter. The Left and Top are the most important.

But you're saying the first block is slightly to the left. The second more to the left (then intended) and the third a lot the the left from where intended. That makes me believe you have an invalid factor for calculation (and that's the LogPixelsX and Y).

  • Could this be a printer driver quirk? Note that our Y DPI returns 0 (we fall back to 600).
YEAH. There you are going wrong. You assume it's 600 while it's not.
Here is my code for LogPixelX and Y.

I use this in a different place because my print module only prints to a canvas. And that canvas can be a real printer or a canvas from SynPDF to print to PDF.
But the value for LOGPIXELSX and LOGPIXELSY may NEVER be 0.

[/list]
Code: Pascal  [Select][+][-]
  1.   ppSize.PixelsX := GetDeviceCaps(ppHandle, LOGPIXELSX);
  2.   ppSize.PixelsY := GetDeviceCaps(ppHandle, LOGPIXELSY);
  3.   ppSize.LeftSpace := GetDeviceCaps(ppHandle, PHYSICALOFFSETX);
  4.   ppSize.TopSpace := GetDeviceCaps(ppHandle, PHYSICALOFFSETY);
  5.   ppSize.MaxBottom := GetDeviceCaps(ppHandle, PHYSICALHEIGHT);
  6.   ppSize.MaxRight := GetDeviceCaps(ppHandle, PHYSICALWIDTH);
  7.   ppSize.ScalingX := GetDeviceCaps(ppHandle, SCALINGFACTORX);
  8.   ppSize.ScalingY := GetDeviceCaps(ppHandle, SCALINGFACTORY);

BTW. I also correct with LeftSpace (left margin for printer) when I pass my cm value but you can see if that's needed for you.
I actually don't use ScalingX/Y and assume it's 1 (which it is in most cases).

When you get 0 for LOGPIXELSX... did you use the correct Printer.Handle?
I've never seen a printerdriver return 0 for this.

rvk

  • Hero Member
  • *****
  • Posts: 6953
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #6 on: February 10, 2026, 09:31:40 pm »
Btw. Another thing is... What do you expect L=2415 to be?
Where did you get those values?
Are those screen coordinates.
If they are, you also need to convert those with 96 dpi.
Or maybe you have dpi at 125% then you then need to recalculate that.

I use the cm value the user gave me so I don't use screen coordinates.

AgriMensor

  • Jr. Member
  • **
  • Posts: 63
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #7 on: February 11, 2026, 06:50:07 pm »
Hi Rik!
Thanks for the latest e-Mails!
I forgot to attach the requested image of some sample output, so I'm attaching a .jpg screenshot of the printer output now. Output to the pdf virtual printer is similar but much worse - most of it ends up in one or other of the yellow "Notes", but still too far to the left and overlapping. It should say "Chapter. . ." in all three columns but it ends up as repeating each letter three times.
I've implemented the device capabilities logging you suggested and have some interesting findings.

Device Capabilities Results:
Testing on both the Epson ET-4850 and Microsoft PDF printer shows identical behaviour:
Code: Pascal  [Select][+][-]
  1. LOGPIXELSX: 768
  2. LOGPIXELSY: 0          ← Consistently 0 on both printers
  3. HORZRES: 6874
  4. VERTRES: 4820
  5. PHYSICALWIDTH: 7014
  6. PHYSICALHEIGHT: 4960
  7. PHYSICALOFFSETX: 70
  8. PHYSICALOFFSETY: 70
  9. SCALINGFACTORX: 0
  10. SCALINGFACTORY: 0

Key observations:
  • LOGPIXELSY returns 0 on BOTH printers (physical Epson and Microsoft PDF virtual printer). Since both printers exhibit this, it appears to be a Lazarus/Free Pascal issue rather than a driver quirk.
    Coordinates are definitely printer pixels, not screen pixels:
    Printer.PageWidth: 6874 matches HORZRES: 6874
    Printer.PageHeight: 4820 matches VERTRES: 4820
    Calculated from Printer.PageWidth and Printer.PageHeight
    I'm now using LOGPIXELSX (768) for both X and Y conversion as you suggested.

Twips Calculations (appear correct):
Column 0: Input pixels L=353  → Twips L=662   (353 * 1440 / 768)
Column 1: Input pixels L=2415 → Twips L=4528  (2415 * 1440 / 768)
Column 2: Input pixels L=4477 → Twips L=8394  (4477 * 1440 / 768)

The Problem Persists:
Despite using your exact approach (rc = rcPage, correct DPI conversion), the text still renders incorrectly:

As you will see from the attached image file:
Column 0: Renders  just to the left of the first Notes (in yellow) left boundary OR correctly inside boundary if there is only one Note per row.
Column 1: Renders far to the left (overlaps column 0)
Column 2: Renders even further left (overlaps column 1)
The Body Text is not correctly spaced on the third and fourth lines (but this is another issue)

Critical Questions:
DC Coordinate System: When you print multiple RTF blocks to different positions on the same page, are you using:
- The same DC for all blocks?
- Separate DCs for each block?
- Any DC origin translation between blocks?
Does EM_FORMATRANGE expect rc.Left/Top to be:
- Absolute page coordinates (e.g., column 1 at 4528 twips from page left)?
- Relative to the DC's current origin (always starting from 0,0)?
Are you calling EM_FORMATRANGE multiple times on the same DC without resetting anything between calls?
Do you use any of these between RTF blocks:
- SetViewportOrgEx?
- SaveDC / RestoreDC?
- Any other DC state manipulation?

My Current Approach:
Code: Pascal  [Select][+][-]
  1. // For each note in the grid:
  2. FormatRange.hdc := Printer.Canvas.Handle;      // Same DC for all notes
  3. FormatRange.hdcTarget := Printer.Canvas.Handle;
  4. FormatRange.rcPage := RectTwips;               // e.g., (4528,4538,7971,7989)
  5. FormatRange.rc := RectTwips;                   // Same as rcPage per your advice
  6. SendMessage(RichMemo.Handle, EM_FORMATRANGE, 1, LPARAM(@FormatRange));
  7. SendMessage(RichMemo.Handle, EM_FORMATRANGE, 0, 0); // Flush

Theory:
Since the first column renders correctly but subsequent columns don't, I suspect EM_FORMATRANGE might be treating rc.Left/Top as relative to the DC origin rather than absolute page coordinates. Since we're reusing the same DC, it always renders at the same position.

Does your implementation differ in how you handle the DC between multiple RTF blocks on the same page?

I really appreciate the time you have spent on this so far, but any further insights would be greatly appreciated!

Thanks!

Steve

rvk

  • Hero Member
  • *****
  • Posts: 6953
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #8 on: February 11, 2026, 07:54:30 pm »
Device Capabilities Results:
Testing on both the Epson ET-4850 and Microsoft PDF printer shows identical behaviour:
Code: Pascal  [Select][+][-]
  1. LOGPIXELSX: 768
  2. LOGPIXELSY: 0          ← Consistently 0 on both printers
  3. HORZRES: 6874
  4.  
What did you use for Printer.Handle? Because you need the real printer handle. Not the handle of a Canvas (and that's what you are using, I think).
Besides that... you can also use Printer.XDPI and Printer.YDPI.
But remove the GetDeviceCaps for LOGPIXELSX because you can't do that from a Canvas.

( And if you are using osprinters from the printer4Lazarus package you can use TWinPrinter(Printer).Handle to use the correct handle for these things )

My Current Approach:
Code: Pascal  [Select][+][-]
  1. // For each note in the grid:
  2. FormatRange.hdc := Printer.Canvas.Handle;      // Same DC for all notes
  3. FormatRange.hdcTarget := Printer.Canvas.Handle;
For me, I use the actual HDC handle from the printer. Not the handle from the canvas. Not sure if that makes any difference.

Coordinates are definitely printer pixels, not screen pixels:
No, I meant how did you come by the starting pixel of 343, 2415 and 4477 for the columns?
Are those pixel values you use for the screen?

--
But... I might have been wrong earlier. When using a Richedit component and using EM_FORMATRANGE and Range you don't need the resolution of the printer. The Richedit just uses 1440 dots per inch (hence the * 1440). I only use the earlier given function with printer resolution to correct the Range because that PHYSICALOFFSETX and PHYSICALOFFSETY is given in printer coordinates and they need to be converted to twips using the correct printer resolution. (Otherwise you end up with the incorrect startingpoint PHYSICALOFFSETX pixels shifted the wrong way)

But... if the earlier coordinates are screen coordinates, you do need to correct them first for Screen.PixelsPerInch before using them.

So:
1) What is the setting of your Screen.PixelsPerInch?

2) How did you draw the yellow target boxes? With what coordinates?

3) How did you come by the starting pixel of 343, 2415 and 4477 for the columns?

« Last Edit: February 11, 2026, 08:05:04 pm by rvk »

AgriMensor

  • Jr. Member
  • **
  • Posts: 63
Re: TRichMemo EM_FORMATRANGE Multi-Column Printing - Text Misalignment Issue
« Reply #9 on: February 12, 2026, 08:50:07 pm »
SOLVED! Thank you rvk!

Your diagnosis was spot-on. The issue was that I was using GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSX/Y) instead of accessing the printer DPI correctly.

The Solution:
Changed from:
LogPixelsX := GetDeviceCaps(APrinterCanvas.Handle, LOGPIXELSX);
LogPixelsY := GetDeviceCaps(APrinterCanvas.Handle, LOGPIXELSY);
To:
LogPixelsX := Printer.XDPI;
LogPixelsY := Printer.YDPI;

Results:
- Printer.YDPI now returns 600 instead of 0
- Multi-column RTF printing alignment is perfect
- All RTF formatting (bold, italic, strikeout, underline) preserved
- Yellow background renders correctly

Answers to your questions:
Screen.PixelsPerInch: 96 (standard)
Yellow boxes: Drawn with Printer.Canvas.FillRect using printer pixel coordinates
Column coordinates (350, 2455, 4560): Calculated from Printer.PageWidth:

Code: Pascal  [Select][+][-]
  1. PageMargin := Printer.PageWidth div 20;
  2. ColWidth := (Printer.PageWidth - (PageMargin * 2)) div 3;
  3. NoteX := PageMargin + (Col * ColWidth);

These are printer canvas pixels, not screen pixels.

At the end of the day, the key point was that I couldn't use GetDeviceCaps with the Canvas handle - I needed to use Lazarus's Printer.XDPI/YDPI properties directly.

Thank you for your patience and friendly expertise! Your CRM system's printing approach worked beautifully. This will be the second time that you are deservedly mentioned in one of our program's credits  O:-)

Steve

 

TinyPortal © 2005-2018