[Up] | Reference for unit 'Printers' (#lcl)

## Using the Printer

The Basic Steps

You must do the following to be able to use the Printer object found in the printers.pas unit:

- Add the Printer4Lazarus package to your project requirements.
- Add the Printers unit to the uses section of your unit.
- Use the existing Printer object.

Adding the Printer4Lazarus Package to a Project

The Printer4Lazarus package defines a basic printer and provides platform independent printing. The following can thus be used on various platforms.

In the Lazarus IDE, do the following:

- In the Project menu, click Project Inspector. A window is shown with a tree view, one of the branches is named Required Packages. By default, the Required Packages branch shows the LCL package.
- Click the Add button, the button with the plus sign at the top of the window.
- Open the New Requirements page.
- From the Package Name list box, select Printer4Lazarus.
- Click OK.

Printer4Lazarus is now shown in the Required Packages for the project.

Adding the printers Unit to the uses section of your unit

This step is simple and the result could look like this:

    unit MainUnit;

    {$mode objfpc}{$H+}

    interface

    uses
      Classes, SysUtils, Forms, Printers;

Using the existing Printer object

Let's assume you want to click a button to print a text. On your form, put a button called PrintBtn and in the OnClick event you can now use the following:

    procedure TForm1.PrintBtnClick(Sender: TObject);
    const
      LEFTMARGIN = 100;
      HEADLINE = 'I Printed My Very First Text On ';
    var
      YPos, LineHeight, VerticalMargin: Integer;
      SuccessString: String;
    begin
      with Printer do
      try
         BeginDoc;
         Canvas.Font.Name := 'Courier New';
         Canvas.Font.Size := 10;
         Canvas.Font.Color := clBlack;
         LineHeight := Round(1.2 * Abs(Canvas.TextHeight('I')));
         VerticalMargin := 4 * LineHeight;
         YPos := VerticalMargin;
         SuccessString := HEADLINE + DateTimeToStr(Now);
         Canvas.TextOut(LEFTMARGIN, YPos, SuccessString);
      finally
        EndDoc;
      end;
    end;

While the above example seems somewhat complex, it demonstrates basic text output using formatting for your text. These are some the actions performed in the code:

Printer.BeginDoc starts the printing process. However, nothing is sent to the printer device until the Printer.EndDoc statement is used.

The Printer uses a Canvas to draw its output. It is this drawing that ends up on the printed page. Canvas.Font has the font object used to output text on the canvas. That is, the TextOut call we use later will output text using the settings of the font object of that moment.

Everything drawn on the canvas must be positioned using coordinates. So, we calculate a LineHeight to position text vertically. You could do the same for the horizontal position, which I left here to be LEFTMARGIN.

The text is drawn with the TextOut call.

This result is sent to the printer when Printer.EndDoc is executed.

In some forums it is suggested that the use of PrintDialog (the printer selection dialog) is required for good functioning, but I did not find this to be required (any more).

Next Steps

After the preceding basic steps, you can perform more complicated or creative tasks. Like:

- Draw on the page using the drawing methods in Canvas, like: Arc, Ellipse, FillRect, FloodFill, GradientFill, Pie, Polygon, Rectangle, Polyline, PolyBezier, and others.
- Format text using the TextStyle for the Canvas. or Color for the canvas Font.
- Select another printer and compare results.

In the Lazarus distribution there is an example project that uses Raw printing to the printer. You can find this example with the following location $(lazarusdir)\components\printers\samples\rawmode\rawmodetest.lpi.

Another sample shows how to select another printer: $(lazarusdir)\components\printers\samples\dialogs\selectprinter.lpi.

Advanced Steps: Printing Controls

The printer object primarily allows you to draw on a canvas and send that canvas image to the printer. If we continue on the path shown above, then you would use the Printer canvas methods to draw text, ellipses, diamonds and what not.

However, this can hardly be interesting to any serious programmer. You are working on a more perfect CAD program or yet another image file sorter and want to print the wonderful result of your program to your printer. Trying to translate the perfect picture into canvas method calls is not the way: you already have the picture.

Each and every control you put on a form, also draws a picture on a TCanvas object just like the printer. We can use that to bring the picture from the screen to the printer.

Imagine you are going to make a preview program. You create a form and on this form you put a TPanel. This panel will provide the nice grayish background of the preview. On the panel, you put another TPanel object called page. This page will be white and represents the paper. You can nicely size the page.

On this page we put a TShape object, for example a nice red, rounded rectangle. Now try the following in the PrintBtnClick event method:

    MyPrinter.BeginDoc;
    page.PaintTo(myPrinter.Canvas, 0, 0);
    MyPrinter.EndDoc;

What happens:

BeginDoc starts the printing (but nothing is sent yet).

page.PaintTo sends the output of our TPanel object that represents the page to the canvas of the printer.

Note the following:

You can use the PaintTo method found in any control in the control hierarchy. You could also send the output of the whole window to the printer if desired.

You can send the output of any control with a PaintTo method to the printer, so you can be creative. To send the output of your image sorter to the printer you may send the output of the TImage to the printer.

TCanvas has a method to copy rectangles from another canvas. However, you can only do that if an object really draws to a canvas. I think most controls rely on containers to provide a real canvas, so you cannot copy rectangles from just any control. It depends on the implementation for a specific control.

Make sure the control you want to paint to the printer is visible. If the control is not visible, nothing will be painted not even to the printer.

EndDoc sends the drawing to the printer.

More Steps: Resizing

The Printer uses a lot more pixels per inch on paper than the monitor uses pixels per inch on the screen. As a result, the output that is redirected from the screen to the printer ends up rather smallish on the paper. Scaling and controlling the layout is important for good looking output. It would be nice if you can have an exact sized copy of what you see on the screen.

For the moment we are not striving for the ideal, just for the idea. As said before, controls do not have their own canvas but rely on the canvas of a container or owner. However, there are components that have their own canvas. I chose TBitMap and then it works as follows.

    procedure TForm1.PrintBtnClick(Sender: TObject);
    var
      MyPrinter : TPrinter;
      myBitMap : TBitMap;
    begin
        myBitMap := TBitMap.Create;
        myBitMap.Width := page.Width;
        myBitMap.Height := page.Height;
        page.BorderStyle:=bsNone;
        page.PaintTo(myBitMap.Canvas, 0, 0);
        page.BorderStyle:=bsSingle;
        //
        MyPrinter := Printer;
        MyPrinter.BeginDoc;
        //page.PaintTo(myPrinter.Canvas, 0, 0);
        //myPrinter.Canvas.Draw(0,0, myBitMap);
        myPrinter.Canvas.CopyRect(Classes.Rect(0, 0, myPrinter.PaperSize.Width, myPrinter.PaperSize.Height),
           myBitMap.Canvas, Classes.Rect(0, 0, myBitMap.Width, myBitMap.Height));
        MyPrinter.EndDoc;
        myBitMap.Free;
    end;

For this to work, do not use the Windows unit. The Windows unit has other definitions for Rect. What you see in the example is the following:

A bitmap is created and made the same size as the page control.

To avoid the border to print, the BorderStyle of the page is switched off before painting it to the bitmap and set back to its old value afterwards.

Then printing is started and the BitMap canvas content is copied to the printer canvas.

But notice that in the process page is magnified. The Printer.papersize is considerably bigger than the size of the bitmap, but the copy process fills the destination rectangle nicely. So, when we want to do this, we must make sure that the page dimensions have the same ratio as the paper dimension. You can figure out how to do it.

A problem with this way of working is of course that the pixels of the screen will show on the printed paper. As said, it is not ideal but it shows a principle. Controls do not have their own canvas; to print controls we first paint them on an object that does have its own canvas: the TBitMap. Now you know how it works, you can figure out a way to create fine artwork or documents. This will need objects that draw themselves properly in a TPanel with low resolution, but also on a TBitMap with high resolution. But, that is food for another article.

Common Tasks

Keep in mind that the Printer variable is a singleton. It's a single object that serves all printers installed in the system. A particular system implementation (WinAPI, Cocoa, etc) should implement the object.

Enumerating Available Printers

The list of system printers is available via Printer.Printers property. Printers property is TStrings containing the name of each printer.

    procedure TForm1.FormShow(Sender: TObject);
    begin
      ListBox1.Items.AddStrings(Printer.Printers);
    end;

A printer name given by one of the lines in the property can be used to change the active printer via the SetPrinter() method.

Internals: any class implementing TPrinter class should implement DoEnumPrinters() method. DoEnumPrinters should populate the passed Lst parameters with the name of printers. It's expected that the name of the printer device is unique.

DoEnumPrinters() should return the system default printer as the first entry (index 0) in the list.

Selecting or Changing the Printer

By default, Printer should pre-select the "system default" printer.

When needed, the printer can be changed by the user either the PrinterDialogs or in program code. To change the printer programmatically either call the SetPrinter method or assign a new value to the PrinterIndex property. Both methods are based on the printers available in the Printers property.

PrinterIndex uses the printer name found at the indicated position in Printers, and calls the SetPrinter method to make the printer with the given name the active one.

    procedure TForm1.Button2Click(Sender: TObject);
    begin
      Printer.SetPrinter(ListBox1.ItemIndex);
    end;

If Index specified is -1 then the default printer is selected.

The SetPrinter method verifies whether the requested name exists in Printers property list.

    procedure TForm1.Button2Click(Sender: TObject);
    begin
      Printer.SetPrinter(ListBox1.GetSelectedText);
    end;

Note, that there's a special name used for SetPrinter method. If name specified as '*' the first (default) printer available is selected.

Either of the methods can raise an exception, if specified name or index doesn't exist.

Internals: The TPrinter deriving class should implement DoSetPrinter() method using aName parameter as the name of the selected printer. The method doesn't need to do any special handling for '*' name, as it being take care at the higher level of TPrinter.

Preparing Paper Size

Note: if you're using multiple printer (especially if you're using specialized printers using non-standard paper size, such as "A4" (European) or "US Letter" (USA)), you always want to prepare the paper size. It is possible that a newly selected printer doesn't support the paper that has been supported by the previously selected printer. The paper size is not reset automatically and you want to do it explicitly.

There are three properties of PaperSize that can be called in to order to reset and prepare paper size:

PaperName
  Returns currently selected paper name of the selected printer.
DefaultPaperName
  Returns the default paper name for the selected printer.
SupportedPapers
  Returns the list of paper sizes supported on the selected printer.

Here's an example of code that re-reads the list of selected papers:

    procedure TForm1.ListBox1SelectionChange(Sender: TObject; User: boolean);
    begin
      // printer changed. But the selected paper size might to be the right one
      Printer.PrinterIndex := ListBox1.ItemIndex;
      ListBox2.Clear;
      // calling SupportedPapers forcing validation of the paper size
      ListBox2.Items.AddStrings(Printer.PaperSize.SupportedPapers);
    end;

Here's an example of resetting paper size, using default paper name property.

    procedure TForm1.ListBox1SelectionChange(Sender: TObject; User: boolean);
    begin
      // printer changed. But the selected paper size might to be the right one
      Printer.PrinterIndex := ListBox1.ItemIndex;
      // calling DefaultPaperName forcing validation of the paper size
      Printer.PaperSize.DefaultPaperName;
    end;

Internals: A derived TPrinter class should properly implement :

DoEnumPapers() method

The method could use PrinterIndex or PrinterName property to determine the currently selected printer to which the paper sizes needs to be enumerated. If the internal method returns no paper sizes, TPrinter class substitutes a "default" paper sizes (assumed to be supported by most of printers).

Default sizes are: Letter, A4, Legal. However, a good TPrinter class implementation should populate the page sizes.

DoGetPaperRect() method

The method returns the physical size of the paper specified by the name for the current printer. The name of the paper is passed via aName property. The returned value should be populated to TPaperRect structure.

Two fields should be populated:

PhysicalRect
  The actual size of the entire page. The size is given in dots. Left and Top are expected to be zeroes.
WorkRect
  The rectangle where the printer can actually print (due to some mechanical limitations of the printer itself). The rectangle should always be the same or less than PhysicalRect. Left and Top are offsets from the top-left corner of the physical rect. The units are printer dots.

Number of dots is driven by the printer currently selected DPI. (Note that there are two different DPI for horizontal and vertical measurements).

Here's the table showing the difference in A4 paper size, using different DPI size values:

DPI | Size returned in TPaperRect
--- | ---
72 | 595 x 842
300 | 2480 x 3507
600 | 4960 x 7014
1200 | 9920 x 14028

DoGetDefaultPaperName()

The method should return the default paper size name of the currently selected printer.

DoGetPaperName

The method should return the currently selected paper size name of the currently selected printer.

DoSetPaperName

The method should set the paper size by its name for the selected printer.