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

Printing hardcopy with Lazarus code

Page URL: lt3Nhardc-short.htm

This page is "browser friendly". Make your browser window as wide as you want it. The text will flow nicely for you. It is easier to read in a narrow window. With most browsers, pressing plus, minus or zero while the control key (ctrl) is held down will change the texts size. (Enlarge, reduce, restore to default, respectively.) (This is more fully explained, and there's another tip, at my Power Browsing page.)

Want to write a Lazarus application which will cause a printer to spit out some hardcopy?

You've come to the right place!

And if this page doesn't give you what you want, it may be worth going to an earlier essay I wrote on getting hardcopy from Lazarus. In that earlier essay I covered some details I've left out of this more streamlined page.

Start at... wiki.freepascal.org's "Using The Printer".

And yes, Virginia, you "need" the Printer4Lazarus" package. At 8 Jan 21, Win10, Lazarus vers 2.0.0 of 28 Nov 2020 I found that it was already installed on the PC I was using when preparing this tutorial.

To see if you have it: From Lazarus IDE main menu: Packages/ Install-Uninstall Packages, and scan the "install" memo.

You should see something like the following...

package install dialog

... which, in my mind, gives rise to three mysteries...

First mystery: Did I install this by hand some time ago? It would have been before I installed vers 2.0.0. Was it carried over? Or did it come "as standard" when I updated my Lazarus to vers 2.0.0? Whatever.

Second mystery: Why is the left hand list of packages, in the memo, below a panel title saying "Install"? These seem to be the already installed packages! Oh well. Whatever. My PC seems to have the package, so I won't be able to take you through how to install it.

Third mystery: What is that "printers4lazide 0.0" we can also see in the list? (I "let that one go".)

Sigh. Onward...

Remember: This is meant to be the "short form" version of how to create hardcopy from a Lazarus application. Finish this, and if you are still not where you want to be, try this tutorial's ancestor... which has some overlap, I fear, but also has details I've left out of this "concise" tour.

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)

I started with the first example on FreePascal's "Using The Printer". It doesn't do much, but always best to walk before running...

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

My little starting point had JUST what was on the FreePascal page. That threw a "SIGSEGV" error. I spent an hour Jan 2021 struggling with that before finding the answer in my own page. (Which I will give you in a moment!). I "read on the internet" that maybe you need the osPrinters package, too. At least under Windows, I couldn't find the package, but the code worked fine without it... with the "fix" I've explained below.

What you see below is VERY NEARLY the code on the FreePascal page. But I've...



Also be advised, nota bene: The following won't work unless you have added "Printer4Lazarus" package to your IDE, and added "Printers" to the project's Uses clause. (This is nothing exotic. Just a normal optional package, and use thereof.)

Obvious? The form needs a button. This is the mouse click event handler for that.

And as mentioned a moment ago, the form needs a TPrintDialog component called PrintDialog1.

procedure Tsvy002f1.Button1Click(Sender: TObject);
const
  kLeftmargin = 100;
  kHeadline = 'I Printed My Very First Text On ';
var
  YPos, LineHeight, VerticalMargin: Integer;
  SuccessString: String;
begin
  if PrintDialog1.execute then begin //SIGSEGV fix- 1 of 2 lines
  with Printer do
  try
    //Prepare the "paper"...
    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;
    //Put writing on it...
    YPos := VerticalMargin;
    SuccessString := kHeadline + DateTimeToStr(Now);
    Canvas.TextOut(kLeftmargin, YPos, SuccessString);
  finally //If all of the above completed okay,
               //"EndDoc" will trigger the actual printing...
    EndDoc;
  end; //of "try... finally..."
 end; //SIGSEGV fix- 2 of 2 lines
end; //Button1Click

App eventually compiled and ran and worked as expected: It spits a page out of my printer as soon as I click my button, and confirm which printer to send the page to. Page had two lines of text at its top. (It would have run immediately on some systems, I suspect, but mine needed the PrintDialog1.execute to prevent the SIGSEGV error.)

Digession: Here's an even SIMPLER version, with even more of the distractions cut away... You still need "Printer4Lazarus", but that's all you need aside from a button for which the following is its click event handler.

procedure TTmpHeaderHardcopyF1.Button2Click(Sender: TObject);
begin
  if PrintDialog1.execute then begin//To prevent SIGSEGV error
  with Printer do
  try
    BeginDoc;
    Canvas.Font.Name := 'Courier New';
    Canvas.Font.Size := 10;
    Canvas.Font.Color := clBlack;
    Canvas.TextOut(10,10,'xxx');
  finally
    EndDoc;
  end;//of "try.."
 end;//If Pri..
end;

(This is 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... which still have their uses... cash registers?... but are not widely used today.)

What's this "Printer" thing??

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. 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).

Paper size, etc...

With the simple code above, I hope you don't expect "everything" to be in place?

The code is only meant to Get You Started. It won't supply all wants.

But even that simple little application can cope, up to a point, with the fact that different people use different paper sizes. During your interaction with PrintDialog1.execute, you can go into the printer's properties.


Moving on

Fine. We can do text.

If you want a discussion that works more directly with the later material on the FreePascal page, then look at the earlier edition of this tutorial, mentioned previously.

In this version, I am trying to cover what's in the language, using more or less the path taken by the FreePascal page, but with coding which I think will be easier to follow.

By the way, the FreePascal page has some furhter, more esoteric ideas that may interest you once you are clear on the basic. One that fascinated me was...

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

But to come back to earth: I'm going to create something very close to the code we had a moment ago. That will then be developed into something like the second FreePascal example. Later an alternative implementation of the subsequent sample code's functionality will also be given.

----
What sort of thing did I want to do differently? One example: I didn't like "page" as the name for a panel. I would call the equivalent object in my application "paDrawHere"... but I didn't need a panel in my simple answer.



Right- Onward! Fundamental fact:

Fundamental fact: We are "doing graphics"... the text is put onto a field of pixels, and that is what gets printed. The "graphic" might look like "this is text as a graphic", but as far as the program is concerned, it's just meaningless (to the computer) squiggles.

The "TextOut" procedure is going to be important to us. It turns text into a graphic that looks (to a human) to be the text. "TextOut" is really very simple...

Printer.canvas.textout(10,20,'Hello');

..."draws" a graphic that look like 'Hello' on the canvas, 10 units from it's left edge, 20 units down from it's top edge. Of course, the "mapping" of the "canvas" to the physical sheet of paper passing through the printer, what the units are, etc, are all devilish details to master. But the basic concept is clear, I hope. There will be more on this later.

In the simple application presented above, which got us started, we simply "drew" on the canvas of the system-provided "Printer" object.

To achieve greater control over what is printed where, at what scale, we need to be more fancy. Sigh. We will come to that. Something we will not come to at this time is using TextOut except crudely.

What do I mean by "crudely"?

You should already be clear about where the upper left of the graphic of "Hello" will be. (It will be at 10,20). How far across, how far down will it go? Well... for now... as far as it needs to! The one (small) bit of good news is that at least if you "go off the page", there won't be problems. Bits will be missing from your hardcopy, but at least what CAN be printed, will be.

Also... we will only use TextOut to print a single line at a time.

These limitations do leave you with enough power to do lots of Good Stuff. And there are answers... for another time... for doing fancier things.

For now, to put a short poem on a page, you'd use....

TextOut(100,200,'A man that looks on glass');
TextOut(100,300,'On it may stay his eye.');
TextOut(100,400,'Or if he pleaseth,');
TextOut(100,500,'Though it pass,');
TextOut(100,600,'And then the heavens espy.');

(A detail: I've increased the Y value by 100 each time. TextOut doesn't care how big you've set the text to print. For a given font size, 30 may be too big... and there would be huge vertical gaps, or too small, in which case, the fourth line would overlap the 3rd, the 3rd the 2nd, and the 2nd the first.

That example drags me clicking and screaming to a topic I've been avoiding... the units of the first two parameters.

What they are is simple: They are pixels. Ah, yes, but how big is a pixel? If I put the second line 100 pixels further down the page than the first line, where does that put it?

With my laser printer, with my Windows setup... as left (for now) after MS's latest "updates", that puts the second line a bit more than 4 mm below the first. But with your printer, you may get a different result! Sigh.

The good news? When you send text to the printer with the system set out here, some of the work is done by Windows. In particular, the translation of the characters to the graphic that depicts them. If you ask the system set out here to put an "A" on the page, in Times New Roman, 12 pt, it should look just like the A of the same specs output by your wordprocessor. Hurrah!

And do you remember "Abs(Canvas.TextHeight('I')" which we met a little while back? That will be a help to you, too. There are probably other such things. (See the links regarding TextExtent, TextRect at the bottom of this page for A-level material that may interest you... someday. For now, we have easier ways to scale the cats.)

Don't try to write your own wordprocessor yet!

A Skeleton

Start a new project. To exactly match what I am doing here use the following names. (If you do that, using "copy/paste" between here and your Lazarus IDE will be easier.) (I will try... eventually... to provide the finished code... but "building it up" for yourself will probably be a big help to you in trying to fully understand what is here.)

Where have we been? Where are we going?

In the first part of this tutorial, we managed to make a Lazarus program put some text on a page.

In the second part, I was going to do that again, but a bit better. (Energy failed me, ans I didn't get quite that far! But read on! I've given you what you'd need to do it!)

Our second application will have a button and a memo box and a button. Whatever text is in the memo box will appear as hardcopy when we click the button. To emphasize that we are working with graphics, there will also be a circle on the hardcopy.

I said we'd do it better. In this second version, we will be able to "manage" things like the margins used, etc.

Note: I will be changing the names used for the equivalents of some of the things that were in the first program.

I will also be moving where some things are declared. In general, I applaud the practice of making things as local, as "not global" as possible. However, I do have reasons for most of the moves you will see in my code.

Make a start

Create the empty shell of a new application.

Put a memo on it. "memo1" is an acceptable name.

Put a button on it. Rename it buProduceHardcopy. (That will do for the button's caption, or get fancy and make the caption "Produce hardcopy", if you like.)

Those will give rise to things you will see if you run the code. Also add a TPrintDialog object. The default name, "PrintDialog1" is satisfactory.

Be sure to... add "Printers" to the application's "uses" clause.

Declare the following in the "private" block of the declaration of the TLDN162_simple_hcF1 class.

    //"PP" prefix is for "Printer Page"... the hardcopy this
    //  application generates
    iPPLeftMargin,iPPLineHeight,iPPVerticalMargin:integer;

With those changes made, the heart of creating a line of hardcopy has become...

procedure TLDN162_simple_hcF1.buProduceHardcopyClick(Sender: TObject);
var
  sTextToPrint:string;
  iPPYPos:integer;
begin
  if PrintDialog1.execute then begin //SIGSEGV fix- 1 of 2 lines for this
  with Printer do
  try
    //Prepare the "pen and paper"...
    BeginDoc;
    Canvas.Font.Name:='Courier New';
    Canvas.Font.Size:=10;
    Canvas.Font.Color:=clBlack;
    //Calculate the vertical coordinate of where the text will go.
    iPPLineHeight:=Round(1.2 * Abs(Canvas.TextHeight('I')));
    iPPYPos:=4 * iPPLineHeight;
    //Put writing on "the paper"...
    sTextToPrint:='Hello hardcopy world ';
    //The following line is an unnecessary frill... but it
    //adds a bit to help you distinguish one test print from another...
         sTextToPrint:=sTextToPrint+DateTimeToStr(Now);
    Canvas.TextOut(iPPLeftmargin,iPPYPos,sTextToPrint);
  finally //If all of the above completed okay,
               //"EndDoc" will trigger the actual printing...
    EndDoc;
  end; //of "try... finally..."
 end; //SIGSEGV fix- 2 of 2 lines for this
end; //buProduceHardcopyClick

A detail: note, within the code just presented, the creation of two local variables...

var sTextToPrint: String;
    iPPYPos:integer;

The original had comparable variables. I only mention them as...
a) I changed the names
b) I moved most variables and constants, made them global.

For those who need it: Apologies of the "unnecessary" use of some globals. I think it helps for the novice see what is going on. In general, I agree with the mantra "Use a local variable when you can." (Within reason!)

All of that merely gets us back to where we were!

If looking through that, all is VERY clear to you, and you aren't deeply interested in the first place, feel free to skip the sub-page I've written to go into parts of that in detail. There's a link there to bring you back here if you need it. In any case, the link should open the digression in a new tab (you can set your browser to switch to new tabs as soon as they are opened, you know?). Just close that tab, and you will be back here... this never went away. It was just "under" the digression.

Right! Onward!

We now have a way to put lines of text onto hardcopy.

We designate what the text is. We can set things like font family and size. We can say where the upper left of the line should go on the page, relative to the upper left of the total "can print here" area as understood by Lazarus.

As a fun (?) diversion, we'll now look briefly at putting graphics on the hard copy, just because we can. And along the way, we will cover an important general issue.

Graphics in the output

Just after the...

Canvas.TextOut(iPPLeftmargin,iPPYPos,sTextToPrint);

... in what we have, add...

canvas.brush.color:=clLime;
canvas.FillRect(150,250,500,100);
canvas.pen.color:=clBlack;
canvas.moveto(50,450);
canvas.lineto(250,450);

canvas.TextOut(iPPLeftmargin,iPPYPos+600,sTextToPrint);

There are several things you need to keep in mind...,

The following will always draw a vertical line on your hardcopy.

canvas.moveto(300,400);
canvas.lineto(300,2400);

... but where it lies on the page, and how long it will be will depend on your printer. In my case, with a modest laser printer, it produced a line about 1.3 inches long... implying that the printer runs at about 1500dpi

Note that I didn't attempt to draw from Y=0, nor along X=0. On My printer, that wouldn't have been a problem... it doesn't attempt borderless printing. But some do. (On my printer, the edge of the area used for printing is about 4.5mm from the edge of the sheet. I established this by experiment....

canvas.moveto(1000,0);
canvas.lineto(0,0);
canvas.lineto(0,2000);

If your first graphics tests "don't work", check to be sure that your numbers aren't too small... my first attempts gave rise to such tiny "drawings" that I nearly overlooked them! Or too big... maybe you've drawn everything off either the bottom or right hand edges of the sheet?

If I did a program that would be drawing graphics to be printed, I'd have two "set once and don't change" variables set up... ScaleToPrintX and ScaleToPrintY. Then, in my program, something like the "find corner" code presented a moment ago would look like...

canvas.moveto(10*ScaleToPrintX,0*ScaleToPrintY);
canvas.lineto(0*ScaleToPrintX,0*ScaleToPrintY);
canvas.lineto(0*ScaleToPrintX,20*ScaleToPrintX);

If the time came that you wanted to print with a different printer, you would only need to set ScaleToPrintX and ScaleToPrintY to new values, and all would be well!

In a simlar vein: TextOut doesn't "care" or "know" about any margins that may be inherent in your printer. Many printers are incapable of printing to the edge of the page. TextOut(0,0,'Hi') will put "Hi" as far up and as far to the left as is possible, but it may not be fully in the upper left corner of the sheet of paper. (My printer put it about 4mm from the top, 4mm from the left edge.)

You'll want margins, anyway. So to put the two lines we made a moment ago in the "upper left" of the part you are using on the sheet of paper, i.e. to "mark" where your margin ends, create two more "variables" which will be set once, and then not changed... and "add" them to everything, e.g....

TextOut((x*ScaleToPrintX)+(MargX*ScaleToPrintX),
   (y*ScaleToPrintY)+(MargY*ScaleToPrintY),
      'Testing 1,2,3');

That could, of course, be done more elegantly.

Oh! Why do I use variables for things like ScaleToPrintX when the values in them shouldn't normally change while the application is running? Why not use a constant?

The answer is in the "shouldn't normally change..."

What if you added a pair of radio buttons to the application? The application could be written so that when one was selected, your hardcopy would be formated for "letter" paper, but if the other were selected, the output would be formatted for "A4" paper. That's why I often use variables even for things that normally might not change.

Back to "work"...

Compared to what I might have gone into, I've kept this fairly simple(!)

If you read the page that set me down the road to success, mentioned earlier, wiki.freepascal.org's "Using The Printer", you will find some Good Stuff under "Advanced steps: printing controls" and "On we go: resizing", but rather complex stuff, suited for "difficult" problems.

If you want to have a "preview" on your screen, before producing the hard-copy of your work, an answer NOT in the FreePascal might appeal to you...

You could have TWO canvases, two pairs of scaling constants...

For each of the procedures you want to use... TextOut, MoveTo, LineTo, you'd create small subroutines. Taking "moveto" as an example...

procedure TLDN162_simple_hcF1.MoveToBoth(x,y:integer);
begin
  Printer.Canvas.MoveToBoth(x*ScaleToPrint,y*ScaleToPrintY);
  MyScreenCanvas.MoveToBoth(x*ScaleToScreenX,y*ScaleToScreenY);
end;

Enough?

I hope you found what you were looking for when you came here. Thank you for reading to the end! Let me know if there are things that didn't make sense, didn't get answered? Contact details below.

Oh heck!...

When I started this, I was going to show you how you might output whatever was in a memo on your screen to ink-on-paper.

Well... it seemed like a good idea at the time. Still seems like a good idea, and even do-able. But I'm afraid I'm out of time and energy, just want this "done", and I'm not going to write it up in detail. Sorry.

But you have almost everything you need!

You would merely go through the lines, one by one, and use TextOut to pass them to the Printer.Canvas. The code would be something LIKE...

procedure TLDN162_simple_hcF1.Button1Click(Sender: TObject);
var iPosX,iPosY,iCount,iLast:integer;
begin
  iPosX:=100;
  iPosY:=150;
  iCount:=0;
  iLast:=memo1.lines.count;
  repeat
    TextOut(iPosX,iPosY,memo1.lines[iCount]);
    iPosY:=iPosY+300;
    inc(iCount);
  until iCount=iLast;
end; 



For further information...

I said that for now, we would treat TextOut as a rather limited procedure, only capable of dealing with a single line. There are ways to send text with line feeds in it to the screen with just one line of code. And I funked showing you how to be sure that the line you wanted to add to the hardcopy was short enough to fit.

If you want to search out the answers for the wants implied in the above, you should look at...

TextExtent... see...
https://lazarus-ccr.sourceforge.io/docs/lcl/graphics/tcanvas.textextent.html

TextRect... see...
https://lazarus-ccr.sourceforge.io/docs/lcl/graphics/tcanvas.textrect.html

.. and the following discussion, in particular the posts near the bottom...
https://forum.lazarus.freepascal.org/index.php?topic=21118.0

Links to the above...

TextExtent
TextRect
(If you use TextRect, I think you still have the problem of "too long" lines simply being "chopped down". I don't belive there is any easy equivalent to a memo with "DoWordWrap" set to "true".)
Discussion



Work in progress

I hope you will find the above is already useful in itself? I will try to extend this... if there's any interest. A few Facebook likes, mentions at Reddit, etc, etc, would be welcome.





Footer

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

Custom Search


Use this to search THIS site.... (Go to my other sites, below, and use their search buttons if you want to search them.)

index sitemap advanced
search engine by freefind

Site Map    What's New    Search

The search engine merely looks for the words you type, so....
*    Spell them properly.
*     Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

Please also note that I have two other sites, and that this search will not include them. They have their own search buttons.

My SheepdogSoftware.co.uk site.

My site at Arunet.


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 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

To email this page's editor, Tom Boyd.... Editor's email address. Suggestions welcomed! Please cite "lt3Nhardc-short.htm".




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 .....