トップへ(mam-mam.net/)

How to Perform Printing in Delphi: Practical Examples Using TPrinter, Canvas, and TextOut

Japanese

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 Height (px)

Physical Paper Width (px)

↓ Non‑printable upper‑left area (X px, Y px)
Origin
Printable 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;
Values Available for TextFormat (can be combined)
ValueMeaning
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;