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

プリンタに印刷 ~Delphiソースコード集

プリンタに印刷 ~Delphiソースコード集

TPrinterクラスのインスタンスであるPrinterオブジェクトを使用するとプリンタに印刷することが出来ます。
プリンタを扱うには Uses に Vcl.Printers, WinApi.WinSpool を追加してください。
uses Vcl.Printers, WinApi.WinSpool;
現在のプリンタの情報(印刷不可範囲、印刷範囲など)を取得する方法は
こちらhttps://mam-mam.net/delphi/vcl_printer_info.html
を参照してください。

座標原点は用紙の左上ではなく、印刷可能範囲の左上であることに注意が必要です。

用紙の物理高さ(px)

用紙の物理幅(px)

↓左上印刷不可(X px, Y px)
原点
印刷可能幅(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;
TextFormatに指定できる値(組み合わせる)
意味
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;