HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers

Hardcopy

Creating Ink-on-paper with Lazarus

file: lt3Nhardcopy.htm

This somewhat in haste... sorry... but I found a few errors in the otherwise excellent "official" guide... Freepascal.org's 'Using the printer' page... well, as it stood 21 Jul 19, when I was using Windows 10 and Lazarus 2.0.0, FPC vers 3.0.4. They were "minor" errors... but enough to be annoying stumbling blocks.

I found the page a bit daunting, at first, but it really isn't bad at all. In particular, it is well sequenced, taking you from "getting started" to "doing the fancy stuff".

Being human, I hoped I could get away with not doing the "Add Printer4Lazarus" step. I couldn't. The instructions on Freepascal.org's 'Using the printer' page worked perfectly for me. Here they are, in a more terse form. (Go to the Freepascal.org page, cited above, if in any doubt.)

(We're told: "The Printer4Lazarus package defines a basic printer and provides platform-independent printing."

I take that to be saying that Printer4Lazarus is "middleware", to make our lives easier, if we port a Lazarus program elsewhere. Note that you still need it, even if you work in only environment, say, Windows. And also note that it isn't an installed part of the "default" package, at least at 20 Jul 19, though the material was already on my machine, ready to be installed, 7/19, when it almost certainly was there from my basic Lazarus "install".

Something not on the Freepascal page!

I was puzzled by the use of "Printer", as an object, in some of the demo code from Freepascal.

It give me hives to use something I do not understand. Where did "Printer" come from?? At www.delphibasics.co.uk/Article.asp?Name=Printing we are told " The Printer object is permanently available to your code (you must put the Printers unit in your "Uses" clause to get access to its methods and fields though). With this object, printing proceeds in an orderly fashion.". (There's more good stuff there, by the way.) (There's also Good Stuff at www.delphibasics.co.uk/RTL.asp?Name=printer)

Back to a run through Freepascal's advice, with corrections

I will not fault the text from Freepascal. There were just a few "typo-like" errors that I would point out.

I started with their first example. It doesn't do much, but always best to walk before running...

I built a tiny project called syv02. It consisted of a for with just a button on it. The button's Click handler was the following, taken from Freepascal's Using The Printer page...

procedure Tsvy02f1.Button1Click(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;
    // There we go
    YPos := VerticalMargin;
    SuccessString := HEADLINE + DateTimeToStr(Now);
    Canvas.TextOut(LEFTMARGIN, YPos, SuccessString);
  finally
    EndDoc;
  end;
end;

Don't forget to add "Printers" to your project's Uses clause. (I don't know why "HEADLINE" and "LEFTMARGIN were done ALL IN UPPERCASE. Probably unnecessary, but I haven't got time to check that just now.

App compiled and ran without incident, and worked as expected, first time. (Put two lines of text at the top of a page spat out by my printer as soon as I clicked on my button. It just sends it to the current system default printer, even if that is printing across a LAN. More on this later.)

(That's fine, to push a completed page of text to your printer. (Years ago, I wrote other help pages here at "Lut" (Lazarus tUTorials), and/ or over at my Delphi tutorials about working with... or was it emulating?... line printers.)

Moving on

Fine. We can do text. Note: We are already "doing graphics"... the text is put onto a field of pixels, and that is what gets printed.

The second demo on the page....

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

... very nearly works.

(An aside: I didn't like "page" as the name for a panel, but I have left it thus.)

Be sure you set up the small list things required, explained on Freepascal's page just above the code. Not least, a panel, "page", holding some TShape object. (Make the panel it is on (Panel1) about 190x 190, so that "page" can be about 140x140, and Shape1 about 100x100. ("Panel1" is an unnecessary "frill", I think, but I am trying to stick to example).

A "Gotcha"... early in my efforts, I thought the code "wasn't working". It was... but the image on my hardcopy was so small, and in such a light shade of gray, that I was overlooking what was on the sheet of paper delivered from my printer. (Make your TShape object DARK... Shape1.brush.color:=clRed. You would probably do this with the GUI, but to underline the importance, I've done it in code, in the FormCreate subroutine, to "make it visible" for you. Line marked "Fix property 1".)

If you do what the page calls for, when you try to compile, you should get "Error: Identifier not found: MyPrinter"

The first part of the fix for that is the two lines marked "Fix 1" in the following. Don't, by the way, "put in" the other "Fix" and "Fix property" lines until they are called for, if you want to work through this story as you read along.

procedure TForm1.FormCreate(Sender: TObject);
begin
  Shape1.brush.color:=clRed; //Fix property 1
  page.color:=clMedGray; //Fix property 2
end;

procedure TForm1.Button1Click(Sender: TObject);
var MyPrinter : TPrinter; //Fix 1a
begin
  if PrintDialog1.execute then begin //Fix 2a
    MyPrinter:= printer; //Fix 1b
    MyPrinter.BeginDoc;
      //page.PaintTo(MyPrinter.Canvas, 0, 0); //Original
      page.PaintTo(MyPrinter.Canvas, 50, 50);//Fix 3
    MyPrinter.EndDoc;
  end;//Fix 2b
end; 

Note also that some inconsistencies in capitalization have been tidied up... some "myPrinter"s made "MyPrinter".

That should compile, and "run"... but when you click on the button, you may, sometimes, get...

Error... exception class 'External: SIGSEGV'.
In file 'printers.pas'...
result:=(pfPrinting in fFlags)

At the same time, the code for "printers.pas" will open in your source code editor. (Right-click on it, elect "Close page", before you inadvertently make changes!)

I have no idea what this is, where it comes from. Maybe a printer that is currently on low current standby?

But the following made it go away for me...

From the "Dialogs" tab of the component palette, add a TPrintDialog component to your project. (Not the nearby, similar, TPrinterSetupDialog.)

Put the two lines marked "Fix 2" into your code.

Run the app again. This time, just before it tries to print, you'll get the probably-familiar dialog about what printer you want to use, etc. Usually, you'd just click on the "okay" button on that to proceed.

Odd, but true... you can rem those two lines out again, once you've run the code once with them in. The app will, for now at least, still run! Best, for a host of reasons, to leave them in, methinks.

That worked BETTER... I now got a sheet out of my printer- a blank sheet.

Now... as I hinted previously, what you see on the paper may not be as large as what you see on your screen. So maybe the output is very small, and is "lost" in the margin of the page, if your printer can't print all the way to the edge of the page?

The "Fix 3" alternative to...

page.PaintTo(MyPrinter.Canvas, 0, 0);

...changes the 0,0 to 50,50. this may be a good idea, anyway... but didn't fix the "gives blank sheet" problem.

The Secret Ingredient

I have no idea why, but if you add what is marked as "Fix property 2" to the code as it now stands, the app "works".

On my system (it will vary, depending on system settings), that, from a "page" that was 140x140, results in a 6mm x 6mm hardcopy of what was on the screen. (The fix called "3" (changing the paint offset to 50,50) probably mattered, too.

Don't despair! Remember that the page this trys to complete says that this approach is just a stage on the journey. You output will not always be tiny! And we haven't far to go to reach the ultimate goal of all of this.

Re-read the "Advanced steps: printing controls" section of Freepascal.org's 'Using the printer' page. Did I miss something that would explain why the background color has to be "poked"? While the default background may well have printed as white, I can see no reason why I didn't get the image of the TShape, with it's black outline and dark center in my hardcopy until after....

page.color:=clMedGray; //Fix property 2

...was added.

The version with re-sizing....

At Freepascal.org's 'Using the printer' page, near the bottom, under "And on we go: Resizing", there is some stuff you should read.

The following is what I ended up with, when I tried to use the final bit of code (FBOC) on that page.

When you click Button1, the app takes whatever you've put on a panel. (In the FBOC, it was the panel named "page", in my code it is the panel named "Panel1" ("Panel One"). You can, of course, use all sorts of commands to put things on the panel. (As illustrated in the Click handler for Button2.)

It takes what is on that panel, and copies it onto a part of the canvas of the MyPrinter object.

It then prints the page that you prepared... It prints what's on the canvas of the MyPrinter object.

The code got a bit "messy" because I didn't want the hardcopy of what was on the screen to (*try to) go all of the way to the edges of the paper. AND I wanted to save toner, so not only did I provide margins, I also said "don't use the full width or height of the page.

Read the internal documentation about the need to keep aspect ratios constant, if you don't want the hardcopy to be a distorted image of what was on the screen.

My code derives from the FBOC on the FreePascal page. It hasn't really deviated, or been developed, very far. Some of the tweaks and gotcha-fixes explained before we got to "The version with re-sizing...." have been incorporated without repeated comment.

unit Demo3u1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
  PrintersDlgs, printers;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Panel1: TPanel;
    PrintDialog1: TPrintDialog;
    Shape1: TShape;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  Shape1.brush.color:=clRed;
  Panel1.Color:=clMedGray;
  Panel1.BorderStyle:=bsNone;//A perhaps-unnecessary "frill"
  Panel1.BorderStyle:=bsSingle;//A perhaps-unnecessary "frill"
end;

procedure TForm1.Button1Click(Sender: TObject);
  var
  myPrinter : TPrinter;
  myBitMap : TBitMap;

begin
  if PrintDialog1.execute then begin (*See notes re- the second demo...
         If this is absent, the app sometimes fails.*)
      myBitMap := TBitMap.Create;
      myBitMap.Width := Panel1.Width;
      myBitMap.Height := Panel1.Height;(*N.B.- I have dispensed with the
        "panel on a a panel" used in the FreePascal.org page *)

      Panel1.PaintTo(myBitMap.Canvas, 0, 0);//Put what's on the panel onto the bitmap

      myPrinter := Printer;
      myPrinter.BeginDoc;

        //"xx.CopyRect.." copies TO object "xx"
        //CopyRect specifies DESTINATION first, then source
        myPrinter.Canvas.CopyRect(
             //copy TO -part of- MyPrinter.canvas...
             //Without the "div 2", and margin creators, what was on
             //  Panel1 would be printed, trying to use the whole area
             //  of what the printer thinks is the paper available to it.

             //If the shape you CopyRect to has a different aspect ratio
             //  than Panel1 had, the graphics from Panel1 will be distorted.
             //  All there... but stretched in one direction or the other.
             Classes.Rect(10,10,//Create margins, to left and above
                   (myPrinter.PaperSize.Width div 2)-12, //-12: Margin on right
                   (myPrinter.PaperSize.Width div 2)-12),//-12: Margin at bottom
                     //N.B.: WIDTH on purpose... to keep printing area
                     //  square. For now, paPage and myBitMap are also square
             //copy from the following...
             myBitMap.Canvas,
             //copy the following part of myBitMap...
             Classes.Rect(0, 0, myBitMap.Width,
                                       myBitMap.Height));
        //If from/to have different aspect ratios, CopyRect will stretch
        //  an axis.

      myPrinter.EndDoc;
      myBitMap.Free;
  end;// if PrintDialog...
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
   Panel1.canvas.pen.color:=clBlack;
   Panel1.canvas.moveto(30,30);
   Panel1.canvas.lineto(60,90);
end;

end.

I hope that helped?

I hope that was useful? If so, I would be grateful for mention of this tutorial in web discussions?

If it fails to work for you, please let me know? (Contact details below.) Please cite "Lazarus tutorial lt3Nhardcopy.htm". Even better, if it doesn't QUITE work, but you solved your own problem, please let me know... to spare the next person the hassle you had?





Search across all my sites with the Google search...

Custom Search
            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"
Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Lazarus Tutorials main page
How to contact the editor of this page, Tom Boyd


Valid HTML 4.01 Transitional Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes.

AND passes... Valid CSS!

If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....