How to Perform Printing in Delphi: Practical Examples Using TPrinter, Canvas, and TextOut
To print to a printer in Delphi, you draw text and images using the TPrinter object’s Canvas property.
This page explains in detail how to output text using TextOut and TextRect, how to print bitmap images, and how to draw page numbers, all with practical source code examples.
It also covers essential printing operations such as starting and ending a print job (BeginDoc / EndDoc) and inserting page breaks (NewPage), providing a comprehensive overview of printing in Delphi.
For information on retrieving printer details, see: Getting Printer Information in Delphi.
To work with printers, add the following units to uses:
uses Vcl.Printers, WinApi.WinSpool;
Note that the coordinate origin is not the upper-left corner of the paper itself, but the upper-left corner of the printable area.
Physical Paper Width (px)
Printable Height (px)
Useful Methods for Printer Output
TCanvas.TextWidth Method
TextWidth(const Text: string): Integer;
Returns the width of the specified string (Text) in pixels, based on the current font.
TCanvas.TextHeight Method
TextHeight(const Text: string): Integer;
Returns the height of the specified string (Text) in pixels, based on the current font.
TCanvas.TextOut Method
A simple method for drawing text.
TextOut(X, Y: Integer; const Text: string);
X and Y specify the top-left coordinate where the text will be drawn.
TCanvas.TextRect Method
A method that automatically wraps text when drawing it.
procedure TextRect(var Rect: TRect; var Text: string; TextFormat: TTextFormat = []);
This method is a wrapper around the Windows API DrawTextEx, so you may call DrawTextEx directly if preferred.
When drawing text, the method clips (wraps) the text within the rectangular area specified by Rect.
var
st: String;
rct: TRect;
begin
rct.Create(Rect(0, 0, 100, 200));
st := 'Text to output' + #13#10 + 'Line break allowed';
Printer.Canvas.TextRect(
rct, st,
[
TTextFormats.tfTop, TTextFormats.tfLeft,
TTextFormats.tfNoPrefix, TTextFormats.tfWordBreak,
TTextFormats.tfModifyString
]
);
end;
| Value | Meaning |
|---|---|
| tfLeft | Aligns the text to the left within the rectangle. |
| tfCenter | Centers the text horizontally within the rectangle. |
| tfRight | Aligns the text to the right within the rectangle. |
| tfTop | Aligns the text to the top edge of the rectangle. |
| tfVerticalCenter | Centers the text vertically (only when combined with tfSingleLine). |
| tfBottom | Aligns the text to the bottom of the rectangle (only with tfSingleLine). |
| tfWordBreak | Displays text in multiple lines, wrapping automatically when encountering line breaks or when reaching the rectangle boundary. |
| tfSingleLine | Displays the text in a single line, even if it contains line breaks. |
| tfNoClip | Disables clipping. |
| tfExternalLeading |
Includes external leading (additional line spacing) in the line height. By default, external leading is not included. |
| tfEditControl |
Calculates average character width like an edit control and hides partially visible last lines. (Matches the text rendering behavior of multi-line edit controls such as TMemo.) |
| tfWordEllipsis | If the text does not fit, it is truncated and always ends with an ellipsis (...). |
| tfEndEllipsis |
If the text does not fit, the end of the string is replaced with an ellipsis (...). Words that overflow before the end of the string are truncated without ellipsis. |
| tfPathEllipsis |
If the text does not fit, the middle portion is replaced with an ellipsis (...). For paths containing backslashes (\), as much text as possible after the last backslash is preserved. |
| tfExpandTabs |
Expands tab characters to 8 spaces (default). Cannot be combined with tfWordEllipsis, tfEndEllipsis, or tfPathEllipsis. |
| tfTabStop |
Expands tab characters to 8 spaces (default). Allows specifying tab stops (using bits 15–8). |
| tfModifyString | The string will not be modified unless this flag is specified. |
| tfNoPrefix |
Disables processing of prefix characters (&). (Normally, & hides itself and underlines the following character.) |
| tfHidePrefix |
Ignores prefix characters (&). (The following character is not underlined, but the & itself is removed.) |
| tfPrefixOnly |
Draws only the underline for prefix processing. (The character after & is underlined and hidden; the & is also hidden.) |
| tfCalcRect | Does not draw text; instead returns the required height for the given text and formatting. |
| tfRtlReading | Displays text in right-to-left reading order for languages such as Hebrew or Arabic. |
| tfNoFullWidthCharBreak |
Prevents line breaks within double‑byte character strings (DBCS). Available on Korean versions of Windows. |
Creating a Program That Prints
Drag and drop one TButton and one TPrintDialog onto the form.
Paste the following source code, then assign Button1Click to the Button1.OnClick event.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
PrintDialog1: TPrintDialog;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
// Converts a device-dependent bitmap (DDB) to a device-independent bitmap (DIB)
// and prints it inside DestRect
procedure PrintBitmap(DestRect:TRect; Bmp:TBitmap);
public
{ Public }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses Vcl.Printers, WinApi.WinSpool, System.Math, Vcl.Imaging.jpeg;
procedure TForm1.Button1Click(Sender: TObject);
var Device, Driver, Port: array[0..255] of char;//static arrays (stack memory)
DeviceModeHandle: THandle;
PMode : PDevMode;
dpi:TPoint;//resolution
paper_size:TPoint; //physical paper size (px)
enable_size:TPoint; //printable area (px)
lt_disable:TPoint; //non-printable upper-left (px)
rb_disable:TPoint; //non-printable upper-right (px)
pt:TPoint;
st:String;
rct:TRect;
jpg:TJpegImage;
bmp:TBitmap;
begin
if not PrintDialog1.Execute(handle) then exit;
TThread.CreateAnonymousThread(
procedure
begin
//Initial settings
Printer.Title:='Test File';
//Set paper size to A4
Printer.GetPrinter(Device, Driver, Port, DeviceModeHandle);
if DeviceModeHandle = 0 then exit;
PMode := GlobalLock(DeviceModeHandle);
try
PMode.dmFields := PMode.dmFields or DM_PAPERSIZE;
pMode.dmPaperSize := DMPAPER_A4;
finally
GlobalUnlock(DeviceModeHandle);
end;
Printer.SetPrinter(Device,Driver,Port,DeviceModeHandle);
//Portrait orientation
Printer.Orientation:=poPortrait;
//Font settings
Printer.Canvas.Font.Name:='MS PGothic';
//Start printing
Printer.BeginDoc();
if not Printer.Printing then exit;
//Retrieve paper information
dpi.X:=GetDeviceCaps(Printer.Handle, LOGPIXELSX);
dpi.Y:=GetDeviceCaps(Printer.Handle, LOGPIXELSY);
paper_size.X:=GetDeviceCaps(Printer.Handle, PHYSICALWIDTH);
paper_size.Y:=GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT);
enable_size.X:=Printer.PageWidth;
enable_size.Y:=Printer.PageHeight;
lt_disable.X:=GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
lt_disable.Y:=GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
rb_disable.X:=paper_size.X - enable_size.X - lt_disable.X;
rb_disable.Y:=paper_size.Y - enable_size.Y - lt_disable.Y;
//Set font size to 8mm (pixel-based)
Printer.Canvas.Font.Height:= -Round(8 / 25.4 * dpi.Y);
//Draw text inside a rectangle defined in millimeters
rct.Create(Rect(
Round( 40*dpi.X/25.4-lt_disable.X),
Round( 80*dpi.X/25.4-lt_disable.Y),
Round(100*dpi.X/25.4-lt_disable.X),
Round(145*dpi.X/25.4-lt_disable.Y)
));
st:='Write the text you want to print here.'+#13#10+
'Line breaks allowed & auto-wrapping, but text outside the rectangle is clipped.';
Printer.Canvas.TextRect(
rct, st,
[
TTextFormats.tfTop, TTextFormats.tfLeft,
TTextFormats.tfNoPrefix, TTextFormats.tfWordBreak,
TTextFormats.tfModifyString
]
);
//Draw rectangle border with 0.5mm line width
Printer.Canvas.Pen.Width:=Ceil(0.5 * dpi.X / 25.4);
Printer.Canvas.Pen.Style:=psSolid;
Printer.Canvas.Pen.Color:=clRed;
Printer.Canvas.Brush.Style:=bsClear;
Printer.Canvas.Rectangle(rct);
//Set font size to 5 mm (point-based)
Printer.Canvas.Font.Size:=Round(5 / 25.4 * 72 );
//Print page number centered horizontally, 20mm from bottom
st:=Format('%d / 2',[Printer.PageNumber]);
pt.X:=Round(
paper_size.X/2 - lt_disable.X - Printer.Canvas.TextWidth(st)/2 );
pt.Y:=Round(
paper_size.Y - 20*dpi.X/25.4 - Printer.Canvas.TextHeight(st) );
Printer.Canvas.TextOut(pt.X, pt.Y,st);
//Next page
Printer.NewPage();
//Print image.jpg at (110 mm, 40 mm), width 70 mm, keeping aspect ratio
jpg:=TJpegImage.Create;
bmp:=TBitmap.Create;
try
jpg.LoadFromFile('..\..\image.jpg');
bmp.Assign(jpg);
rct.Left :=Round(110*dpi.X/25.4)-lt_disable.X;
rct.Top :=Round( 40*dpi.Y/25.4)-lt_disable.Y;
rct.Width:=Round( 70*dpi.X/25.4);
rct.Height:=Round(rct.Width*bmp.Height/bmp.Width);
PrintBitmap(rct,bmp);
finally
jpg.Free;
bmp.Free;
end;
Print page number on second page
Printer.Canvas.Font.Size:=Round(5 / 25.4 * 72 );
st:=Format('%d / 2',[Printer.PageNumber]);
pt.X:=Round(
paper_size.X/2 - lt_disable.X - Printer.Canvas.TextWidth(st)/2 );
pt.Y:=Round(
paper_size.Y - 20*dpi.X/25.4 - Printer.Canvas.TextHeight(st));
Printer.Canvas.TextOut(pt.X, pt.Y,st);
//End printing
Printer.EndDoc();
end).Start();
end;
//Convert DDB to DIB and print bitmap
procedure TForm1.PrintBitmap(DestRect: TRect; Bmp: TBitmap);
var InfoSize,ImageSize:Cardinal;
Info:PBitmapInfo;
Image:Pointer;
begin
GetDIBSizes(Bmp.Handle, InfoSize,ImageSize);
Info:=AllocMem(InfoSize);
try
Image:=AllocMem(ImageSize);
try
GetDIB(Bmp.Handle, Bmp.Palette, Info^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
DestRect.Left, DestRect.Top, DestRect.Width, DestRect.Height,
0, 0, Info.bmiHeader.biWidth, Info.bmiHeader.biHeight,
Image, Info^,
DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(Info, InfoSize);
end;
end;
end.
Running the Program
Run the application and click Button1 to start printing.
Changing the Paper Size During Printing
To change the paper size in the middle of a print job, you can use the following approach.
Reference URL: https://www.petitmonte.com/bbs/answers?question_id=30195
procedure ChangePapersize(PaperSize:Integer=DMPAPER_A4; Orientation:Integer=DMORIENT_PORTRAIT); var Device, Driver, Port: array[0..255] of char; DeviceModeHandle: THandle; PMode : PDevMode; begin //Set paper size and orientation Printer.GetPrinter(Device, Driver, Port, DeviceModeHandle); if DeviceModeHandle = 0 then exit; PMode := GlobalLock(DeviceModeHandle); try PMode.dmFields := PMode.dmFields or DM_PAPERSIZE or DM_ORIENTATION; PMode.dmPaperSize := PaperSize; PMode.dmOrientation:=Orientation; finally GlobalUnlock(DeviceModeHandle); end; ResetDC(Printer.Handle,PMode^); Printer.Canvas.Refresh; end; procedure TForm1.Button2Click(Sender: TObject); begin if not PrintDialog1.execute then exit; Printer.Title:='New Printing'; //Set to A4 Landscape ChangePapersize(DMPAPER_A4, DMORIENT_LANDSCAPE); Printer.BeginDoc; Printer.canvas.TextOut(200,200,PChar('Page1')); EndPage(Printer.Handle); //Set to A3 Portrait ChangePapersize(DMPAPER_A3, DMORIENT_PORTRAIT); StartPage(Printer.Handle); Printer.canvas.TextOut(200,200,PChar('Page2')); Printer.EndDoc; end;
