html+cssでデザインしたPDFにデジタル署名を発行する

# 目次
[1.はじめに](#1-はじめに)
[2.wkhtmltopdfとTCPDFの準備](#2-wkhtmltopdfとTCPDFの準備)
[3.PDFをhtml+cssでデザイン](#3-PDFをhtml+cssでデザイン)
[4.3をwkhtmltopdfでPDF化](#4-3をwkhtmltopdfでPDF化)
[5.4にTCPDFでデジタル署名](#5-4にTCPDFでデジタル署名)
[6.おわりに](#6-おわりに)

# 1. はじめに

PDF用のライブラリはいろいろありますが、レイアウトにこだわりつつ、簡単にデザインできて、かつ、デジタル署名の発行までおこなうにはどれを使うのが一番よいのでしょう？

[Digital Signature Additions #4286](https://github.com/wkhtmltopdf/wkhtmltopdf/issues/4286)
*意訳）*
> wkhtmltopdfはデジタル署名の発行ができない
> TCPDFはCSSのサポートがひどい
> そしたらwkhtmltopdfで作成したPDFにTCPDFで署名を発行すればいいじゃないか

いいとこどりですね。
大賛成です。

そんなわけで早速いってみましょう。

# 2. wkhtmltopdfとTCPDFの準備

導入環境: CentOS7
```linux
cd /usr/local/src/
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.centos7.x86_64.rpm
sudo yum localinstall wkhtmltox-0.12.6-1.centos7.x86_64.rpm
wkhtmltopdf --version
    wkhtmltopdf 0.12.6 (with patched qt)

mkdir -p ~/projects/pdf/public_html/works/
cd ~/projects/pdf/
composer require mikehaertl/phpwkhtmltopdf
composer require knplabs/knp-snappy
ls vendor/autoload.php
	vendor/autoload.php

git clone https://github.com/tecnickcom/TCPDF.git
wget --no-check-certificate https://www.setasign.com/downloads/2921760/FPDI-2.3.6.zip
unzip FPDI-2.3.6.zip
mv FPDI-2.3.6/src/ TCPDF/FPDI/
ls TCPDF/tcpdf.php
	TCPDF/tcpdf.php
ls TCPDF/FPDI/autoload.php
	TCPDF/FPDI/autoload.php

cd public_html/
```
ちょいちょい出てくるlsは存在確認用です。
導入環境やバージョンの違いはうまく吸収していただくとして、はい、準備ができました。

# 3. PDFをhtml+cssでデザイン

```html:wkhtmltopdf.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>wkhtmltopdf用html</title>
<style>
.page {
  padding:2em;
  margin:1em auto;
  border:2em solid black;
  text-align:center;
  height:100%;
}
dt{
  color:red;
}
table {
  border-collapse: collapse;
}
table th {
  background-color:#eee;
}
table th, table td {
  border:1px solid gray;
  padding:5px 10px;
}
</style>
</head>
<body>
<h1>Portable Document Format</h1
<div class="page">
<strong>Portable Document Format</strong>（ポータブル・ドキュメント・フォーマット、PDF）は、デジタルデバイス上でアプリケーションやOS、ハードウェアに依存せず文章や図版を表示するために開発され、ISO 32000[2]で国際標準化された電子文書ファイル形式である。<br>PostScriptをベースにAdobeが開発し、1993年にAdobe Acrobatで初めて採用された。
</div>
<div class="page">
<dl>
<dt>概要</dt>
<dd>特定の環境に左右されずに全ての環境でほぼ同様の状態で文章や画像等を閲覧できる特性を持っている。</dd>
</dl>
</div>

<hr>

<table>
<thead>
  <tr><th>バージョン</th><th>仕様書</th><th>発行年</th></tr>
</thead>
<tbody>
  <tr><td>1.0</td><td>Portable Document Format Reference Manual</td><td>1993</td></tr>
  <tr><td>1.1</td><td>First, revised</td><td>1996</td></tr>
  <tr><td>1.2</td><td>First, revised</td><td>1996</td></tr>
  <tr><td>1.3</td><td>...</td><td>...</td></tr>
</tbody>
</table>
</body>
</html>
```

html+cssでデザインしました。フロントエンドコーディングのナレッジがそのまま活用できるのは嬉しいですね。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282332/4bbae943-c2b9-7aa1-2e81-6a57f3c0ff77.png)

# 4. 3をwkhtmltopdfでPDF化

```php:wkhtmltopdf.php
<?php
include realpath(dirname(__FILE__) . '/../vendor/autoload.php');

// 3. PDFをhtml+cssでデザイン
$origin = realpath(dirname(__FILE__) . '/wkhtmltopdf.html');

// 4. 3をwkhtmltopdfでPDF化
$result = realpath(dirname(__FILE__) . '/works/wkhtmltopdf.pdf');

use Knp\Snappy\Pdf;

ob_start();
include_once $origin;
$html = ob_get_contents();
ob_end_clean();

$pdf = new Pdf('/usr/local/bin/wkhtmltopdf');
header("Content-Type: application/pdf");
$pdf = $pdf->getOutputFromHtml($html);
echo $pdf;
file_put_contents($result, $pdf);
```

真骨頂です。PDF化されても遜色ありませんね。

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282332/e18ff331-3290-abf7-0912-7f1b7a9e7704.png)

# 5. 4にTCPDFでデジタル署名

```php:tcpdf.php
include realpath(dirname(__FILE__) . '/../TCPDF/tcpdf.php');
include realpath(dirname(__FILE__) . '/../TCPDF/FPDI/autoload.php');

// Let's EncryptのSSL証明書(例)
$cert = 'file://' . realpath('/etc/letsencrypt/live/example.com/fullchain.pem'); // 

// Let's Encryptの秘密鍵(例)
$pkey = 'file://' . realpath('/etc/letsencrypt/live/example.com/privkey.pem');

// デジタル署名に添える画像(ハンコ)
$sign = realpath(dirname(__FILE__) . '/works/mysign.png');

// 4. 3をwkhtmltopdfでPDF化
$origin = realpath(dirname(__FILE__) . '/works/wkhtmltopdf.pdf');

// 4にTCPDFでデジタル署名
$result = realpath(dirname(__FILE__) . '/works/result.pdf');

$pdf = new setasign\Fpdi\Tcpdf\Fpdi(
    PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false
);

$pdf->setSourceFile($origin);

$pdf->setSignature($cert, $pkey, '', '', 1, [
    'Name' => 'Qiita',
    'Location' => 'Web-site',
    'Reason' => 'To test',
    'ContactInfo' => 'http://www.tcpdf.org',
]);

$pdf->AddPage();
$tpl = $pdf->importPage(1);
$pdf->useTemplate($tpl);

$pdf->Image($sign, 180, 100, 16, 16, 'PNG');

$pdf->Output('file://' . $result, "F");
header("Content-Type: application/pdf");
readfile($result);
```

デジタル署名に使用するSSL証明書とその鍵にはLet's Encryptで発行したものを使用していますが、オレオレでも正規ブランドでも動作するので、用途にあわせて調整してください。

ハンコ（Qiitan）が捺印されています。
赤くハンコっぽくしたかったですが、ロゴのデザインガイドラインから逸脱しそうなのでやめておきました。

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282332/4293933b-5c96-37e1-74fc-5506e8050f33.png)

# 6. おわりに

うまく利用すれば外部のデジタル署名サービスの力を借りずとも自前でWEBサービスに導入が期待できますね。

この記事ではPHPで実現していますが、wkhtmltopdf自体はコマンドラインでも利用できる優れものなので、デジタル署名に対応したライブラリさえ見つけられればどの言語でもいけるのではないかと思います。