🖨️

wkhtmltopdfをlinuxにインストールして、PHPで実行するまで

2022/01/01に公開

かつてはHTMLからPDFへの変換は「mPDF」を利用していましたが、最近はQT Webkitエンジンを使ってよりブラウザに近い精度で変換する「wkhtmltopdf」を使用しています。本記事ではAmazon LinuxやCent OS等での環境にインストールする方法を記載します。

ダウンロードする

wkhtmltopdfのダウンロードページ よりサーバに応じたRPMパッケージをダウンロードします。
https://wkhtmltopdf.org/downloads.html

必要なパッケージがわからない場合

このときに必要なパッケージがわからない場合は、下記のようなコマンドで確認します。

OSのバージョンを確認する

cat /etc/redhat-release

ビット数を確認する

arch

インストールする

ダウンロードしたRPMパッケージを適当なフォルダにアップロードして、インストールのコマンドを実行します。

rpm -ivh wkhtmltox-0.12.6-1.centos7.x86_64.rpm 

色々と足りないと言われるので、足りないと言われたものをyumでインストールします。(ググるとコンパイルなどの記述が出てきて沼るのでご注意ください。)

yum install -y libXext libXrender xorg-x11-fonts-75dpi xorg-x11-fonts-Type1

そのままだと日本語環境で文字化けするので、フォントをインストールします。

yum install -y ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts

足りないものがインストールできたら、再度インストールのコマンドを実行します。

rpm -ivh wkhtmltox-0.12.6-1.centos7.x86_64.rpm 

実行する

今回は mikehaertl/phpwkhtmltopdf でPHP経由で実行します。まずはcomposerでインストールします。

https://github.com/mikehaertl/phpwkhtmltopdf

composer require mikehaertl/phpwkhtmltopdf

サンプルに従って実行します。

<?php
require_once 'autoload.php';
use mikehaertl\wkhtmlto\Pdf;

$main = '<!DOCTYPE><html><head></head><body></body></html>'
$pdf = new Pdf([
	'encoding' => 'utf-8',
	'commandOptions' => array(
		'useExec' => true, // エラーメッセージが出てこない場合のための記述
		'procEnv' => array(
			'LANG' => 'ja_JP.utf-8', // 出力のロケールチェック
		),
	),
]);
$pdf->addPage($main);
if (!$pdf->send()) {
	echo($pdf->getError());
}

実務向け

CSSも使えますが、いくつか制約があります。simplehtmldomと組み合わせて実行します。

  • flexが効かないのでfloatで代用する
  • メディアクエリーが効かないので、メディアクエリは削除する
  • 画像は数が多いと、メモリー不足により読み込みできないので、事前にbase64エンコードする
  • 画像は相対パスだとエラーになるので、絶対パスにする(CSSも同様)
<?php
require_once 'autoload.php';
require_once 'simplehtmldom_1_9_1/simple_html_dom.php';
use mikehaertl\wkhtmlto\Pdf;

class html2Pdf
{
	/**
	 * HTMLオブジェクトから要素を削除
	 *
	 * @param bool $html HTMLオブジェクト
	 * @param bool $selector 削除するセレクタ
	 */
	public function removeNode($html, $selector)
	{
		$html = str_get_html($html);
		foreach ($html->find($selector) as $node){
			$node->outertext = '';
		}
		return $html;
	}

	/**
	 * HTMLオブジェクトの画像をbase64に変換
	 *
	 * @param bool $html HTMLオブジェクト
	 * @param bool $selector 画像のセレクタ
	 */
	public function insertImage($html, $selector)
	{
		$html = str_get_html($html);
		foreach ($html->find($selector) as $node){
			$src = $node->src;
			$base64 = base64_encode(file_get_contents($src));
			$pathinfo = pathinfo($src);
			$file_ext = strtolower($pathinfo['extension']);
			if ($file_ext == 'jpeg') {
				$file_ext = 'jpg';
			}
			$node->src = 'data: ' . $file_ext . ';base64,' . $base64;
		}
		return $html;
	}

    
	/**
	 * 親要素を削除する
	 *
	 * @param bool $html HTMLオブジェクト
	 * @param bool $selector 削除するセレクタ
	 */
	public function unwrap($html, $selector)
	{
		$html = str_get_html($html);
		foreach ($html->find($selector) as $node){
			$text = $node->innertext;
			$node->outertext = $text;
		}
		return $html;
	}
    
	/**
	 * DIVで囲む
	 *
	 * @param bool $html HTMLオブジェクト
	 * @param bool $selector 囲むセレクタ
	 * @param bool $start 開始タグ
	 * @param bool $end 終了タグ
	 */
	public function insertDivider($html, $selector, $start, $end)
	{
		$html = str_get_html($html);
		foreach ($html->find($selector) as $node){
			$node->outertext = $start.$node->outertext.$end;
		}
		return $html;
	}
    
	/**
	 * PDFで出力する
	 */
	public function generate($res)
	{
		// URLを取得
		$base  = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"];
		
		// HTMLを調整
		$html = str_get_html($res);
		$title = $html->find('title', 0)->innertext;
		$main = $html->find('main', 0)->innertext; // 必要なセレクタのみ取得
		$main = $this->removeNode($main, '.only-display'); // 不要なセレクタを削除
		$main = str_replace('src="/', 'src="'.$base.'/', $main); // 画像を絶対パスに変更
		$main = $this->insertImage($main, 'img'); // 画像をbase64エンコード

		// CSSを調整
		$printStyle = file_get_contents(__DIR__.'/css/pdf.css'); // 印刷用CSSを読み込み
		$printStyle = str_replace('background: url(../', 'background:url('.$base.'/', $printStyle); // 背景画像を絶対パスに変更
		$printStyle = preg_replace('/^\@media.*?^\}$/ms', '', $printStyle); // メディアクエリを削除

		// HTMLとCSSを統合
		$main = "<!doctype><html lang=\"ja\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><style>{$printStyle}</style></head><body>{$main}</body></html>";
          
		// PDFを出力
		$pdf = new Pdf([
			'encoding' => 'utf-8',
			'commandOptions' => array(
				'useExec' => true, // エラーメッセージが出てこない場合のための記述
				'procEnv' => array(
					'LANG' => 'ja_JP.utf-8', // 出力のロケールチェック
				),
			),
		]);
		$pdf->addPage($main);
		if (!$pdf->send()) {
			echo($pdf->getError());
		}
	}
}

PHPなどでHTMLのPDF化を試みている人口が少なく、偉大な先人たちに感謝します。

Discussion