【PHP】TCPDF + FPDIでPDFを出力してみた話

# はじめに
タイトルの通りですが
**TCPDF**と**FPDI**のライブラリを使用して、PDFを出力してみました。
今回はコマンドラインから実行してみます。

# 本編
## 環境構築
お好みでPHP、Composerが使用できる環境を用意してください。

### Docker
今回私が使用したDocker環境は以下です(一部割愛)。

```yml:./docker-compose.yml
version: '3'
services:
 php:
   build: ./php
   volumes:
     - ./work:/home/work
```
```dockerfile:./php/dockerfile
FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

# Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === '795f976fe0ebd8b75f26a6dd68f78fd3453ce79f32ecb33e7fd087d39bfeb978342fb73ac986cd4f54edd0dc902601dc') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer
```
```ini:./php/php.ini
date.timezone = "Asia/Tokyo"
```
### Composer
立ち上げが完了したらphpのコンテナへ入り、
Composerでライブラリのインストールを行います。

また、ライブラリの他にautoloadの記載も追加しています。

```json:/home/work/src/composer.json
{
    "require": {
        "setasign/fpdi": "^2.3",
        "tecnickcom/tcpdf": "^6.3"
    },

    "autoload": {
        "psr-4": {
            "Classes\\" : "classes/"
        }
    }
}
```
インストールが終われば完了です。

``` 
# cd /home/work/src
# composer install
```

## 実装
### ファイル構成
```terminal
# cd /home/work/

# ls /home/work/output/pdf/fileYYYYmmdd-HHiiss #PDF出力ファイル
# ls /home/work/src/resources/font/ipam.ttf #フォントの格納先
# ls /home/work/src/resources/pdf/templete.pdf #PDFテンプレートの格納先
# ls /home/work/bin/make-pdf.php #コマンドラインで実行するソース
# ls /home/work/src/bootstrap.php # 起動用ファイル(割愛)
# ls /home/work/src/classes/MyTcpdf.php # TCPDF利用クラス
# ls /home/work/src/classes/common/DynamicProperty.php # 動的クラス(割愛)
# ls /home/work/src/classes/MyTcpdf/FontColor.php # フォントカラークラス(割愛)
```

### テンプレート
至ってシンプルなPDFテンプレートファイルです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338551/f6ff6368-9464-06ef-580b-f64770f2b7ea.png)


### ソース
```php:/home/work/bin/make-pdf.php
<?php

require_once __DIR__ . '/../src/bootstrap.php';

use Classes\MyTcpdf;
use Classes\MyTcpdf\FontColor;

// テンプレートを使用せずに出力
$myTcpdf = new MyTcpdf();
$myTcpdf->addPage();
$myTcpdf->setText('テンプレートを使用せずに出力', 10, 20, new FontColor(0, 255, 0));
$myTcpdf->outputPdfFile();

// テンプレートを使用して出力
$myTcpdfTmp = new MyTcpdf();
$myTcpdfTmp->addPageWithTemplete(1);
$myTcpdfTmp->setText('テンプレートを使用して出力', 10, 20, new FontColor(255, 0, 0));
$myTcpdfTmp->outputPdfFile();
```

```php:/home/work/src/classes/MyTcpdf.php
<?php

namespace Classes;

use TCPDF_FONTS;
use setasign\Fpdi\Tcpdf\Fpdi;
use Classes\MyTcpdf\FontColor;

class MyTcpdf
{
    // 出力ファイル
    const OUTPUT_FORMAT_CHAR = 'F';
    const OUTPUT_FILE_DIR = __DIR__.'/../../output/pdf/';
    const OUTPUT_FILE_NAME = 'file';

    // PDF
    const PDF_ORIENTATION = 'P';
    const PDF_UNIT = 'mm';
    const PDF_SIZE = 'A4';
    const PDF_TEMPLETE = __DIR__.'/../resources/pdf/templete.pdf';

    // フォントファイル
    const FONT_FILE_NAME = __DIR__.'/../resources/font/ipam.ttf';

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $this->fpdi = new Fpdi(self::PDF_ORIENTATION, self::PDF_UNIT, self::PDF_SIZE);
        $this->fpdi->SetMargins(0, 0, 0);
        $this->fpdi->SetFont($this->getFont(), '', 32);
    }

    /**
     * 出力文字を設定する
     * @param _text 文字列
     * @param _x X座標
     * @param _y Y座標
     * @param _h 高さ
     */
    public function setText(string $_text, int $_x, int $_y, FontColor $_fontColor, int $_h = 0)
    {
        $this->fpdi->SetTextColor($_fontColor->R, $_fontColor->G, $_fontColor->B);
        $this->fpdi->SetXY($_x, $_y);
        $this->fpdi->Write($_h, $_text);
    }

    /**
     * 追加するフォントを取得する
     */
    private function getFont()
    {
        return TCPDF_FONTS::addTTFfont(self::FONT_FILE_NAME);
    }

    /**
     * 出力するファイル名を取得する
     * @return string ファイル名
     */
    private function getOutputFilename()
    {
        return self::OUTPUT_FILE_DIR . self::OUTPUT_FILE_NAME . date("Ymd-His"). '.pdf';
    }

    /**
     * ページを追加する
     */
    public function addPage()
    {
        $this->fpdi->AddPage();
    }

    /**
     * テンプレートベースのページを追加する
     */
    public function addPageWithTemplete($_pageNum)
    {
        $this->fpdi->SetSourceFile(self::PDF_TEMPLETE);
        $page = $this->fpdi->importPage($_pageNum);
        $this->fpdi->AddPage();
        $this->fpdi->useTemplate($page, null, null, null, null, true);
    }

    /**
     * PDFファイル出力
     */
    public function outputPdfFile()
    {
        $this->fpdi->Output(
            $this->getOutputFilename(),
            self::OUTPUT_FORMAT_CHAR
        );
    }
}
```

### 実行コマンド
```terminal
php /home/work/bin/make-pdf.php
```

## 実行結果

以下のようにファイルが出力された。

|テンプレートなし|テンプレートあり|
|:-:|:-:|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338551/c8794dec-a7b3-f368-c0a0-90a5d2aa0923.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/338551/6fc27d30-1cdf-abb8-5dbf-9420dbf6fbb1.png)|

## 終わりに
PDFのテンプレートの上に文字を出力できるので、フォーマットを変えずに必要な情報だけを出力できますね。
勤務表や請求書・領収書など、特定の部分だけが変動するファイルに活用できそう。