Controlling Margins when printing RichEdit contents

How can I control margins when printing text that I have loaded into a TRichEdit?


Printing from a RichEdit is really rather easy - all you need to do is to call the Print method:

RichEdit1.Print(const Caption: string);

The Caption parameter shown here specifies the title that appears in the Print queue.   Therefore you would, for example, call something like:

RichEdit1.Print(MyAppName+': '+DocumentTitle);

From this call the system will carefully format and print the contents of the RichEdit automatically over the required number of pages.   That is all well and good, but it does have an unwanted side effect - there is no control over the margins whatsoever.  It will simply use the default margins as reported by the Printer when it returns its printable page size.

In order to be able to define the margins you need to know a number of things... The available space on the page, the unprintable area of the page, whether to calculate the margins as Inches or Centimetres, and so on.  Much of this can be retrieved from the Printer object, and the rest we will need to provide in our code.

The first thing to decide is how to determine the measurements.  You could pass this as a String (e.g. 'inches' or 'centimetres'), you could pass it as a number (e.g. 1 for inches, 2 for centimetres) but perhaps the easiest way is to declare your own data type that will make the code easier to read.  For this example I have defined TRichTextMeasurements in the Interface section of the unit:

type TRichTextMeasurements = (rtmNone, rtmInches, rtmCentimetres);

Now we can determine whether to use the default margins (rtmNone) or to create our own margins in the selected measurement.

Now all we need is the code to implement the margins if needed.  This example function accepts parameters to describe the margins required, the type of measurements to be used for creating margins and the number of copies to be printed.

function PrintRichText(RTLeftMargin, RTRightMargin, RTTopMargin, RTBottomMargin: Extended;
RichTextMeasurement: TRichTextMeasurements; Copies: Integer): Boolean;
var
  PixelsX, PixelsY, LeftSpace, TopSpace: Integer;
  LeftMargin, RightMargin, TopMargin, BottomMargin: Extended;
begin
  Result := False; // default return value
  if RichTextMeasurement <> rtmNone then
    begin
      // get pixels per inch
      PixelsX := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
      PixelsY := GetDeviceCaps(Printer.Handle, LOGPIXELSY);

      // get non-printable margins
      LeftSpace := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
      TopSpace := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);

      LeftMargin := RTLeftMargin;
      RightMargin := RTRightMargin;
      TopMargin := RTTopMargin;
      BottomMargin := RTBottomMargin;

      // If the measurement is set in Centimetres, recalculate
      if RichTextMeasurement = rtmCentimetres then
        begin
          LeftMargin := LeftMargin / 2.54;
          RightMargin := RightMargin / 2.54;
          TopMargin := TopMargin / 2.54;
          BottomMargin := BottomMargin / 2.54;
        end;

        // Set the Margins
        R.Left := Round(PixelsX * LeftMargin) - LeftSpace;
        R.Right := Printer.PageWidth - Round(PixelsX * RightMargin) - LeftSpace;
        R.Top := Round(PixelsY * TopMargin) - TopSpace;
        R.Bottom := Printer.PageHeight - Round(PixelsY * BottomMargin) - TopSpace;
        RichEdit1.PageRect := R;
        Application.ProcessMessages;
    end;

  // Print the required number of copies
  while Copies > 0 do
    begin
      RichEdit1.Print('MyApp: Copy '+IntToStr(Copies));
      Dec(Copies);
      Application.ProcessMessages;
    end;
  Result := True;
end;

To call the example code to print one copy  with a 1 inch margin all round, you could do something like this:

procedure TForm1.PrintButtonClick(Sender: TObject);
begin
  if PrintRichText(1, 1, 1, 1, rtmInches, 1) then
    ShowMessage('Printing was successful')
  else
    ShowMessage('Printing failed');
end;

Note that if you want to use the default margins you still have to pass a number for each margin in the function call.  What that number is does not matter as they are all ignored, but perhaps for clarity using a zero would be best:

procedure TForm1.PrintButtonClick(Sender: TObject);
begin
  if PrintRichText(0, 0, 0, 0, rtmNone, 1) then
    ShowMessage('Printing was successful')
  else
    ShowMessage('Printing failed');
end;

This example can easily be extended to provide visual print progress feedback, or to add margin measurements in millimetres.   However, it will not replace a full implementation as provided by commercial Word Processing products.  On the other hand, for many purposes it will more than suffice.