Delphiで印刷処理を行う方法|TPrinter・Canvas・TextOutの活用例
Delphiでプリンタに印刷するには、TPrinterオブジェクトのCanvasプロパティを使って文字列や画像を描画する必要があります。
本ページでは、TextOut・TextRectによる文字列出力、Bitmap画像の印刷、ページ番号の描画など、実際のソースコードを交えて詳しく解説します。
印刷処理の開始・終了(BeginDoc/EndDoc)や改ページ(NewPage)の使い方も含め、Delphiでの印刷処理を網羅的に紹介します。
プリンタ情報の取得方法については「Delphiでプリンタ情報を取得」をご参照ください。
プリンタを扱うには Uses に Vcl.Printers, WinApi.WinSpool を追加してください。
uses Vcl.Printers, WinApi.WinSpool;
座標原点は用紙の左上ではなく、印刷可能範囲の左上であることに注意が必要です。
用紙の物理幅(px)
印刷可能高さ(px)
プリンタ出力時に便利なメソッド
TCanvas.TextWidthメソッド
TextWidth(const Text: string):Integer;
現在のフォントで指定した文字列(Text)の幅をピクセル単位で返す。
TCanvas.TextHeightメソッド
TextHeight(const Text: string):Integer;
現在のフォントで指定した文字列(Text)の高さをピクセル単位で返す。
TCanvas.TextOutメソッド
簡単に文字を出力するメソッド
TextOut(X, Y: Integer; const Text: string);
X,Yは出力したい文字列(Text)の左上座標を指定します。
TCanvas.TextRectメソッド
文字列を出力するときに自動改行してくれるメソッド
procedure TextRect(var Rect: TRect; var Text: string; TextFormat: TTextFormat = []);
があります。(DrawTextEx というWindows APIをラップしているのでDrawTextExを使っても構わない。)
この関数は文字列を出力するときに、Rectで示した四角形でクリッピング(自動改行)してくれます。
var st:String;
rct:TRect;
begin
rct.Create(Rect(0,0,100,200));
st:='出力したい文字列'+#13#10+'改行可能';
Printer.Canvas.TextRect(
rct, st,
[
TTextFormats.tfTop, TTextFormats.tfLeft,
TTextFormats.tfNoPrefix, TTextFormats.tfWordBreak,
TTextFormats.tfModifyString
]
);
end;
| 値 | 意味 |
|---|---|
| tfLeft | テキストを四角形内で左揃え |
| tfCenter | テキストを四角形内で水平方向に中央揃え |
| tfRight | テキストを四角形内で右揃え |
| tfTop | 四角形内の上端にテキストを揃える |
| tfVerticalCenter | テキストを垂直方向に中央揃え (tfSingleLineと組み合わせたときのみ) |
| tfBottom | テキストを四角形の下部に配置(tfSingleLineと組み合わせたときのみ) |
| tfWordBreak | テキストを複数行で表示し、改行文字や四角形からはみ出すと自動的に折り返す。 |
| tfSingleLine | テキストに改行が含まれていても単一行で表示 |
| tfNoClip | クリッピングをしない |
| tfExternalLeading |
行の高さの外部レディングの高さ(テキスト行間の適切な高さ)が含まれる。 デフォルトでは外部レディングはテキスト行の高さに含まれない。 |
| tfEditControl |
平均文字幅は編集コントロールと同じ方法で計算され、部分的に見えている最後の行は表示されない。 (複数行編集コントロール[TMemo]のテキスト表示特性と同じ描画) |
| tfWordEllipsis | 四角形に納まらない部分がある場合、それを切り取って必ず省略記号...を追加する。 |
| tfEndEllipsis |
文字列が四角形に収まらない場合は末尾を省略記号...に置き換え四角形に収まるようにします。 文字列の最後ではない場所の単語が長方形領域からはみ出す場合は省略記号なしで切り取られます。 |
| tfPathEllipsis |
文字列が四角形に収まらない場合は途中を省略記号...に置き換え四角形に収まるようにします。 円記号(\)が含まれているテキストの場合、最後の円記号の後ろのテキストが可能な限り保持される。 |
| tfExpandTabs |
タブ文字を8文字(デフォルト)に展開します。 tfWordEllipsis、tfEndEllipsis、tfPathEllipsis と共に指定できない。 |
| tfTabStop |
タブ文字を8文字(デフォルト)に展開します。 タブ間隔を設定する。15ビットから 8ビットでタブ間を指定する。 |
| tfModifyString | フラグを指定していない限り、文字列が変更されることはない。 |
| tfNoPrefix |
プレフィックス文字(&)の処理をオフ。 (プリフィックス文字の処理とは&の次の文字に下線、&の文字が消える) |
| tfHidePrefix |
プレフィックス文字(&)を無視します。 (&の次の文字に下線が引かれないが&の文字が消える) |
| tfPrefixOnly |
プレフィックス文字(&)処理の下線だけを描画。 (&の次の文字に下線が引かれてその文字は消える、&の文字が消える) |
| tfCalcRect | テキストは描画せず、指定された書式とテキストに必要な高さを返します。 |
| tfRtlReading | ヘブライ語かアラビア語だった場合テキストを右から左への読み取り順序で表示 |
| tfNoFullWidthCharBreak |
DBCS(ダブルワイド文字ストリング)での改行を防ぐ。 韓国語のWindowsで使用できる。 |
印刷するプログラムを作成してみる
TButton ×1個 と TPrintDialog をフォームにドラッグ&ドロップします。
以下ソースコードを貼り付けて、 Button1.OnClickイベントプロパティに「Button1Click」を設定します。
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
{ Private 宣言 }
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;//静的配列はスタックメモリ
DeviceModeHandle: THandle;
PMode : PDevMode;
dpi:TPoint;//解像度
paper_size:TPoint;//用紙サイズpx
enable_size:TPoint; //印刷可能範囲px
lt_disable:TPoint;//左上印刷不可px
rb_disable:TPoint;//右下印刷不可px
pt:TPoint;
st:String;
rct:TRect;
jpg:TJpegImage;
bmp:TBitmap;
begin
if not PrintDialog1.Execute(handle) then exit;
TThread.CreateAnonymousThread(
procedure
begin
//■■■■■■■初期設定■■■■■■■
//タイトル設定
Printer.Title:='Test File';
//用紙サイズを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);
//用紙を縦向きに設定する
Printer.Orientation:=poPortrait; //用紙縦向き
//フォント設定
Printer.Canvas.Font.Name:='MS Pゴシック';
//■■■■■■■出力の開始■■■■■■■
Printer.BeginDoc();
//「Microsoft Print to PDF」等で出力先ファイル選択ダイアログで
//キャンセルを押された場合は処理を終了する
if not Printer.Printing then exit;
//■■■■■■■用紙情報の取得■■■■■■■
//解像度の取得dpi
dpi.X:=GetDeviceCaps(Printer.Handle, LOGPIXELSX);
dpi.Y:=GetDeviceCaps(Printer.Handle, LOGPIXELSY);
//用紙サイズの取得px
paper_size.X:=GetDeviceCaps(Printer.Handle, PHYSICALWIDTH);
paper_size.Y:=GetDeviceCaps(Printer.Handle, PHYSICALHEIGHT);
//印刷可能範囲の取得px
enable_size.X:=Printer.PageWidth;
enable_size.Y:=Printer.PageHeight;
//左上印刷不可の取得px
lt_disable.X:=GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX);
lt_disable.Y:=GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY);
//右下印刷不可の取得px
rb_disable.X:=paper_size.X - enable_size.X - lt_disable.X;
rb_disable.Y:=paper_size.Y - enable_size.Y - lt_disable.Y;
//文字サイズ8mmに設定(ピクセル指定する場合)
Printer.Canvas.Font.Height:= -Round(8 / 25.4 * dpi.Y);
//用紙左上から(40mm,80mm)-(100mm,145mm)の四角形内に文字を出力します
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:='出力したい文字列をここに記述します。'+#13#10+
'改行可能&自動改行ですが四角形からはみ出した文字は出力されない。';
Printer.Canvas.TextRect(
rct, st,
[
TTextFormats.tfTop, TTextFormats.tfLeft,
TTextFormats.tfNoPrefix, TTextFormats.tfWordBreak,
TTextFormats.tfModifyString
]
);
//用紙左上から(40mm,80mm)-(100mm,145mm)の四角形を
//太さ0.5mmで縁だけ描画内します
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);
//文字サイズ5mmに設定(ポイント[1/72inch]指定する場合)
Printer.Canvas.Font.Size:=Round(5 / 25.4 * 72 );
//用紙の水平方向中央、下から20mmの位置が文字下端となるようページ番号を出力する
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);
//■■■■■■■次のページ■■■■■■■
Printer.NewPage();
//用紙左上から(110mm,40mm)を画像の左上として
//幅70mmでアスペクト比を保持して image.jpg 画像を出力する
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;
//文字サイズ5mmに設定(ポイント[1/72inch]指定する場合)
Printer.Canvas.Font.Size:=Round(5 / 25.4 * 72 );
//用紙の水平方向中央、下から20mmの位置が文字下端となるようページ番号を出力する
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);
//■■■■■■■出力終了■■■■■■■
Printer.EndDoc();
end).Start();
end;
//DDB(Device-Dependent Bitmap)デバイス依存ビットマップを
//DIB(Device Independent Bitmap)デバイス非依存ビットマップに変換して
//用紙のDestRect に Bmp を出力する関数
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.
実行する
実行してButton1を押すと印刷します。
途中で用紙サイズを変更する
途中で用紙サイズを変更するには以下のようにすればいいそうです。
参考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 //用紙サイズと方向を設定する 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'; //A4横に設定する ChangePapersize(DMPAPER_A4, DMORIENT_LANDSCAPE); Printer.BeginDoc; Printer.canvas.TextOut(200,200,PChar('1ページ目')); EndPage(Printer.Handle); //A3縦に設定する ChangePapersize(DMPAPER_A3, DMORIENT_PORTRAIT); StartPage(Printer.Handle); Printer.canvas.TextOut(200,200,PChar('2ページ目')); Printer.EndDoc; end;
