【PHP】TCPDFで動的な変数を入力しつつ差し込み印刷をする

PHPを使って、html形式などをPDF形式で出力できるTCPDF。これを使って差し込み印刷ができる機能が必要になったので、作ってみました。

#ポイント
- 表や書状などのテンプレートを用意して画像化し、PDFの背景画像とする。
- 背景画像を元にhtmlを構築し、データの出力位置、印字を調整する。
- 差し込み部分は変数で渡す。
- PDFを複数枚印刷できるようにループさせる。

以上の流れとなります。

#テンプレートの画像化
テンプレートを画像化する際、必ず指定サイズ（A4など）に収まるようにして選んでください。表を画像化する場合は一旦PDF化して、それを画像化するとちょうどA4のサイズの背景画像を作成できます。

PowerPointで原稿を作成し、PDF形式で保存。それを[reneePDF Aide](https://freesoft-100.com/review/renee-pdf-aide.html)などで画像変換すると一番手っ取り早いと思います。

※縁取りするためにあえて灰色のレイヤーを敷いています。
![haikei.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272191/f47c785c-9d18-fcdc-3811-618539ab9a87.png)

#プログラム
プログラムはこちらです（かなりミスがあったのでプログラムを修正しています。現在は動作確認済）。TCPDFはライブラリなので、外部からインストールする必要があります。

参考リンク

- [PHPでTCPDFを使ってHTMLをPDF化する](https://qiita.com/noratmt/items/d0bc4bc95eaf92d07ca6)

```PHP:PHP
 	require_once("{$_SERVER['DOCUMENT_ROOT']}/tcpdf/tcpdf.php"); //tcPDFライブラリ読込
        //日付指定の定数
	define("DATEY",date('Y'));
	define("DATEM",date('m'));
	define("DATED",date('d'));
        //tcPDFの設定用定数
	define("MY_PDF_PAGE_ORIENTATION"   , "P");  // P:Portrait(縦向き), L:Landscape(横向き)
	define("MY_PDF_FONT_NAME"          , "kozminproregular");  // 使用するフォント
	define("MY_PDF_FONT_SIZE"          , 10); //デフォルトのフォントサイズ
	define("MY_PDF_UNIT"               , "mm"); //用紙の単位
	define("MY_PDF_PAGE_FORMAT"        , "A4"); //用紙のサイズ
	define("MY_PDF_IMAGE_SCALE_RATIO"  , 1); //比率
	define("MY_PDF_MARGIN_HEADER"      , 0); //ヘッダに対するマージン 
	define("MY_PDF_MARGIN_FOOTER"      , 0); //フッタに対するマージン
	define("MY_PDF_MARGIN_TOP"         , 10); //全体に対する上余白
	define("MY_PDF_MARGIN_LEFT"        , 15); //全体に対する左余白
	define("MY_PDF_MARGIN_RIGHT"       , 15); //全体に対する右余白
	define("MY_PDF_MARGIN_BOTTOM"      , 20); //全体に対する下余白
	define("BACKGROUND_IMAGE_PATH"     , "img/background.png"); //表を画像化したもの（背景画像）
       //ヘッダの制御（注１）
	class MYPDF extends TCPDF {
	    public function Header(){
		$this->SetAutoPageBreak(false, 0); //ページを表示させない
		$this->Image(BACKGROUND_IMAGE_PATH, 0, 0, 210, 297, '', '', '', false, 300, '', false, false, 0); //背景画像を設定
	    }
	}
	//対象のデータ(これが差し込み用のデータとなります)
	$lists = [
		"桜井","藤原","野田","桑田","清水","深瀬","斎藤","大橋","草野","山口","森内","片岡","川上"
	];

     //オブジェクト生成
     $pdf = new MYPDF(MY_PDF_PAGE_ORIENTATION, MY_PDF_UNIT, MY_PDF_PAGE_FORMAT, true, 'UTF-8', false);
     //必要ページ分のpdfデータ準備////////////////////////////////
     foreach ($lists as $data ){
                        $con = new Contents($data);
			// ページ文の追記
			$pdf -> SetTitle('PDFの差し込み印刷');
			$pdf -> SetMargins(MY_PDF_MARGIN_LEFT, MY_PDF_MARGIN_TOP, MY_PDF_MARGIN_RIGHT);
			$pdf -> setPrintHeader(true); //ページもヘッダで制御する
			$pdf -> setPrintFooter(false); //フッタは使わない
			$pdf -> setImageScale(MY_PDF_IMAGE_SCALE_RATIO);
			$pdf -> SetFont(MY_PDF_FONT_NAME, "", 10);
			$pdf -> AddPage($data); //ページの始まり
                        $html = $con -> build_contents(); //差し込み用のテーブル作成
			// PDFに変換するHTML 
			$pdf -> writeHTML($html, true, false, true, false, '');
			$pdf -> lastPage(); //ページの終わり
    }
    $fileData = $pdf-> Output(null,'S'); // デバッグ
    $data = null;
    $fileName = "テスト.pdf";
    $data = $pdf->Output(null,'S');     // PDFドキュメントを文字列として返却

    if ($data != null) {
        // ブラウザにプレビュー表示
        header('Content-Type: application/pdf');
        header('Content-Disposition: inline; filename="'.basename($fileName).'"');
        // ダウンロードする場合は下のコメントアウトを外す
        /*
        header('Content-Type: application/octet-stream', false);
        header('Content-Disposition: attachment; filename="'.basename($fileName).'"');
        */
    echo $data;
    }

     //差し込み印刷部分のテーブル制御クラス
    class Contents
    {
        private $data;
        public function __construct($data){
            $this -> data = $data;
        }
        
        //コンテンツの構築
        public function build_contents(){
            /*中身は後述*/
        }
        //関数の文字列埋め込み処理用
        private function c($v){ return $v; }
    }
```

### 注意点
ページ番号の調整をヘッダで指定しているのは、背景をヘッダに設定しているためです。もし、フッタでページ番号を調整しようとするとフッタ分のマージンができてしまい、大幅に印字がずれてしまうことになります。

#差し込み印刷部分のレイアウト構築について
まず、注意しておくとCSSはあまり使えません。特に**位置を制御するCSSは全くない**といっていいと思います。ただ、paddingプロパティだけは使えるので、それで全コンテンツの位置を調整します。また、文字を装飾するcssはほとんど使用可能です。

コンテンツ内の構成は**テーブルタグ**を使って作成します（**divタグなどは使えません**）ので、構築中はテーブルの線を見えるようにしておくとやりやすいです。そしてmargin※やpositionが使えない以上、微妙な位置の調整などはpaddingやtext-alignなどを駆使することになります。また、**文字の入らない空白セルには`&nbsp;`を代入する**ことを忘れないでください。なお、差し込みのデータ部分は変数で渡します。

marginは別途制御できるメソッドがあるので、そこで全体を調整します。

```PHP:PHP
    define("MY_PDF_MARGIN_TOP"         , 10); //全体に対する上余白
    define("MY_PDF_MARGIN_LEFT"        , 15); //全体に対する左余白
    define("MY_PDF_MARGIN_RIGHT"       , 15); //全体に対する右余白
    define("MY_PDF_MARGIN_BOTTOM"      , 20); //全体に対する下余白
```

```php:php
public function build_contents(){
    $data = $this -> data;
    return $html = <<< EOM
    <style>
    /*コンテンツに必要なCSS情報はここに記述*/
    .year{
        letter-spacing: 1.5em;
	text-align: right;
    }
    .name{
        font-size: 20px;
	text-align: left;
    }
    .title{
        text-align: center;
        font-size: 20px;
    }
    </style>
        <table width="100%" border="1"><!-- デバッグが終わったら、0にする -->
-->
          <tr>
               <td width="33%" height="20px">&nbsp;</td>
               <td width="33%" class="title">&nbsp;</td>
               <td width="34%">&nbsp;</td>
          </tr>
          <tr>
               <td class="name">&nbsp;<u>{$data} 様</u></td><!-- 参加者氏名 -->
               <td>&nbsp;</td>
               <td>&nbsp;</td>
          </tr>
          <tr>
               <td>&nbsp;</td>
               <td>&nbsp;</td>
               <td class="year">{$this -> c(DATEY)} 年{$this -> c(DATEM)}月{$this -> c(DATED)}日</td><!-- 日付-->
          </tr> 
        </table>
EOM;

}
```

#プレビュー結果
プログラムを読み込むと、このようにブラウザにプレビューされます。データが代入されているか、そして位置やフォントを調節していきます（テストサンプルなので、フォントや配置は割といい加減です…）。

![pdf_border.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272191/9d1d483c-b8b9-dd29-42f7-e41a36d4a926.jpeg)

調節が終わったところで、テーブルタグの**borderプロパティを"0"**にし、境界線を非表示にします。

![pdf_nonB.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272191/5432a4c7-8c00-58e4-7f40-c390c4aae53a.jpeg)


複数枚指定した場合、実際はこのように文字（変数$dataの値)や日付（PHPから取得した定数）が順番に代入されていくので、差し込み印刷ができます。
![sasikomi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272191/54760417-a9bb-6cb6-9057-092c4fda004d.jpeg)


これで差し込み印刷の準備完了です。PDFをダウンロードする場合は、コメントアウトしているヘッダを表示してください。

#印刷する場合の注意
ブラウザの印刷プレビューだと恐ろしく時間がかかります（枚数が多いと、下手したらPCがフリーズします）。ですので、複数枚綴りの1PDFファイルで保存しておいて、PDFの専用ビューアを使って、1ページずつ印刷するといいでしょう。

#実務で使う場合
結局は、コンテンツに対する上下左右の余白など最終的にはプリンタの性能に依存しますが、コンテンツそのものはテーブルで作成する限り中身が崩れたりはしないので、それなりの品質を維持できるはずです。