mPDFで日本語PDFを生成する:フォント登録・改ページ・レイアウト崩れの確認ポイント
作成日:2026.09.23
PHPからmPDFで日本語を含むPDFや帳票を生成する際に、Noto Sans JPなどのフォントをプロジェクト側で登録する方法、fontDir・fontdata・tempDirの設定、HTML/CSSの制約、改ページや長い文字列の扱いを解説します。Linuxや本番環境で起きる文字化け・レイアウト崩れを、PHP拡張、フォント、権限、入力HTMLの観点から切り分ける手順も紹介します。
目次
以前、PHPでmPDFを使ってHTMLをPDFに変換する方法で、mPDFのインストールや簡単なPDF出力、日本語フォントの指定方法を紹介しました。
今回はその続きとして、日本語を含む帳票をmPDFで安定して生成するために確認したい設定を整理します。フォントの登録方法、mPDFが対応するHTML/CSS、改ページ、長い文字列、Linuxや本番環境での切り分けを扱います。
mPDFはブラウザと同じHTML/CSSをそのまま描画するライブラリではありません。画面では問題なく表示できるHTMLでも、PDFでは文字化けやレイアウト崩れが起こる場合があります。この記事では、最小構成で出力を確認しながら、帳票へ要素を追加していく流れで説明します。
前提
記事中のコードは、次の環境を想定しています。
- PHP 8.3系
- mPDF 8.x
- Composer
- LinuxまたはLinux上のWebサーバー
- PHPの
mbstring、gd拡張
mPDFの公式ドキュメントでは、PHP 8.3はmPDF 8.2.1以降でサポートされています。実際に利用するバージョンは、インストール時のComposerの解決結果とcomposer.lockを確認してください。mPDFのバージョンによって、必要なPHPバージョンや拡張が変わる可能性があります。
必要な拡張を確認するには、PHPが動作する環境で次のコマンドを実行します。
php -v
php -m | grep -E 'mbstring|gd|zlib|xml'
zlibやxmlは、圧縮やSVGなど利用する機能によって必要になる場合があります。すべての機能に同じ拡張が必要とは限らないため、実際に利用する機能とmPDFの要件を確認します。
ComposerでmPDFを導入する
mPDFをまだ導入していないプロジェクトでは、Composerでインストールします。
composer require mpdf/mpdf
composer show mpdf/mpdf
composer showで、実際にインストールされたバージョンを確認できます。記事に書いたバージョンと、プロジェクトのcomposer.lockに記録されたバージョンが異なる場合は、実際の環境を優先してください。
PDFを生成するPHPファイルから、Composerのオートローダーを読み込みます。
require_once __DIR__ . '/vendor/autoload.php';
日本語フォントをプロジェクト側で登録する
mPDFで日本語を出力するには、日本語フォントを用意し、mPDFから参照できるように登録します。今回はNoto Sans JPを例にしますが、実際に使用するフォントの配布条件とライセンスを確認してください。
フォントは、mPDF本体のvendor/mpdf/mpdf配下へ直接追加せず、アプリケーション側のディレクトリで管理します。例えば、次のような構成にします。
project/
├── fonts/
│ └── NotoSansJP-Regular.ttf
├── storage/
│ └── mpdf/
├── vendor/
└── generate-pdf.php
ここでは、使用許諾を確認したフォントファイルをfonts/NotoSansJP-Regular.ttfへ配置したものとします。配布元によってファイル名や形式が異なるため、手元のファイル名に合わせてコードを変更してください。
mPDFの公式ドキュメントにある方法を参考に、標準フォントの設定を残したまま、独自のフォントディレクトリとフォント情報を追加します。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables;
use Mpdf\Mpdf;
$defaultConfig = (new ConfigVariables())->getDefaults();
$fontDirs = $defaultConfig['fontDir'];
$defaultFontConfig = (new FontVariables())->getDefaults();
$fontData = $defaultFontConfig['fontdata'];
$mpdf = new Mpdf([
'fontDir' => array_merge($fontDirs, [
__DIR__ . '/fonts',
]),
'fontdata' => $fontData + [
'notosansjp' => [
'R' => 'NotoSansJP-Regular.ttf',
],
],
'default_font' => 'notosansjp',
'tempDir' => __DIR__ . '/storage/mpdf',
]);
fontdataのキーには、mPDFで使用するフォント名を指定します。公式ドキュメントの例では、フォント名に小文字を使用しています。HTMLやCSSから同じ名前をfont-familyへ指定します。
太字も使用する場合は、対応するフォントファイルを用意してBへ登録します。
'notosansjp' => [
'R' => 'NotoSansJP-Regular.ttf',
'B' => 'NotoSansJP-Bold.ttf',
],
Regularしか登録していない状態でHTMLから太字を指定すると、mPDFが別のフォントへ置き換えることがあります。太字や斜体を使う場合は、実際に生成されたPDFで書体と文字化けを確認します。
最小の日本語PDFを生成する
まずは、帳票のレイアウトを追加せず、日本語が出力できるかだけを確認します。
$html = <<<'HTML'
<!DOCTYPE html>
<html lang="ja">
<body>
<h1>日本語PDFの確認</h1>
<p>Noto Sans JPを使って日本語を出力します。</p>
</body>
</html>
HTML;
$mpdf->WriteHTML($html);
$mpdf->Output(__DIR__ . '/storage/mpdf/sample.pdf', \Mpdf\Output\Destination::FILE);
実行前に、tempDirと出力先が存在し、PHPを実行するユーザーから書き込めることを確認します。
mkdir -p storage/mpdf
php generate-pdf.php
ls -lh storage/mpdf/sample.pdf
PDFを開いて、日本語の文字化けがないこと、指定したフォントで表示されていることを確認します。この段階で失敗する場合は、帳票のHTMLやCSSを追加せず、フォントのパスと登録名を先に確認します。
帳票向けのHTMLとCSSを組み立てる
mPDFへ渡すHTMLは、ブラウザで表示する画面とは分けて考えます。ブラウザでは使えるCSSでも、PDF出力で同じ結果になるとは限りません。帳票では、表、見出し、余白、文字サイズなど、印刷結果を確認しやすい要素から組み立てると切り分けやすくなります。
例えば、次のように本文全体のフォントと帳票の表を指定します。
$html = <<<'HTML'
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
body {
font-family: notosansjp;
font-size: 10pt;
line-height: 1.5;
}
h1 {
font-size: 18pt;
margin-bottom: 12pt;
}
table {
border-collapse: collapse;
width: 100%;
}
th,
td {
border: 0.2mm solid #666666;
padding: 2mm;
vertical-align: top;
}
th {
background-color: #eeeeee;
}
</style>
</head>
<body>
<h1>注文一覧</h1>
<table>
<thead>
<tr>
<th>注文番号</th>
<th>顧客名</th>
<th>備考</th>
</tr>
</thead>
<tbody>
<tr>
<td>ORDER-001</td>
<td>山田太郎</td>
<td>納品時間の指定あり</td>
</tr>
<tr>
<td>ORDER-002</td>
<td>佐藤花子</td>
<td>住所を確認してから発送する</td>
</tr>
</tbody>
</table>
</body>
</html>
HTML;
実際のデータを埋め込む場合は、利用者が入力した文字列をHTMLへそのまま連結しないようにします。HTMLとして許可する必要がない値は、次のようにエスケープしてから埋め込みます。
$customerName = htmlspecialchars(
$customerName,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
PDF生成用のHTMLへ利用者入力を渡す場合、意図しないタグや外部リソースを読み込ませない設計も必要です。HTMLを許可する場合は、許可するタグや属性を先に決め、必要に応じてサニタイズしましょう。
対応しているHTML/CSSの範囲を確認する
mPDFには、対応しているHTMLタグやCSSの一覧があります。CSSが効かない場合は、ブラウザの表示を正しい結果と考えるのではなく、mPDFの対応表を確認します。
帳票のレイアウトでは、次のような点を先に決めておくと、CSSの指定を増やしすぎずに済みます。
- 用紙サイズと余白
- 本文と見出しのフォントサイズ
- 表の列幅と折り返し方
- ページをまたいでよい要素
- 1ページに収める必要がある要素
flexboxやgridなど、ブラウザで一般的なレイアウト機能を使う場合は、mPDFの対応状況と対象バージョンを確認してから採用しましょう。対応が不明なCSSを増やすより、帳票ではtableや単純なブロック要素へ戻して、出力結果を確認する方が原因を追いやすくなります。
改ページを制御する
帳票では、見出しだけがページ末尾に残る、表の行が意図しない位置で分かれるといった問題が起こります。mPDFではCSSのpage-break-before、page-break-after、page-break-insideや、HTMLの<pagebreak>を使って改ページを制御できます。
章や帳票のまとまりを明示的に次のページから始めたい場合は、<pagebreak>を使う方法があります。
<h2>明細</h2>
<table>
<!-- 明細 -->
</table>
<pagebreak />
<h2>備考</h2>
見出しの直後に表を置き、見出しだけが前のページに残らないようにしたい場合は、page-break-inside: avoidなどを検討できます。ただし、この指定には制約があり、表の自動縮小や複数ページにまたがる要素との組み合わせによっては、期待どおりにならない場合があります。
改ページ指定を追加したら、短いデータだけでなく、表が2ページ以上になるデータでも確認しましょう。1ページに収まる場合だけ成功しても、本番の件数で同じ結果になるとは限りません。
長い文字列を扱う
URL、識別子、エラーメッセージなど、空白の少ない長い文字列は、通常の文章よりも折り返し方が不安定になりやすい項目です。
長い文字列を帳票へ出力するときは、次のような順番で考えます。
- 業務上、全文を表示する必要があるか確認する。
- 全文が必要な場合は、実際の最大長に近いデータでPDFを生成する。
- 折り返し用CSSが対象バージョンで利用できるか確認する。
- 必要に応じて、表示用に改行を入れる、別ページへ分ける、短縮表示にする。
- 短縮した場合は、元の値を別の画面やログで参照できるようにする。
CSSを追加するだけで解決しようとすると、別の帳票で列幅や行高が変わる場合があります。文字列の最大長や表示ルールを先に決め、PDFだけでなく元データの確認方法も用意します。
Linuxや本番環境で確認する
ローカル環境でPDFを生成できても、本番環境ではPHPの実行ユーザーやファイルシステムの権限が異なります。特に、フォントと一時ディレクトリは、Webサーバーから読み書きできるかを確認します。
本番環境へ配置する前に、少なくとも次の項目を確認します。
php -vで、CLIとWebサーバーが利用するPHPのバージョンを確認する。composer.lockを配置し、開発環境と同じ依存関係を利用する。- フォントファイルがデプロイ対象に含まれていることを確認する。
tempDirとPDFの一時出力先を、Webサーバーの実行ユーザーが利用できるようにする。- 書き込み権限を広げすぎず、アプリケーションが必要とするディレクトリだけに限定する。
- 外部画像や外部HTTPリソースを使う場合は、接続先、タイムアウト、利用可否を確認する。
Linuxでは、ターミナルで使うPHPとPHP-FPMやWebサーバーから呼び出されるPHPが異なることがあります。ターミナルでphp -mを実行しただけで、Webリクエスト側の拡張まで有効だとは限りません。必要に応じて、アプリケーションから実際のPHPバージョンと設定を確認します。ただし、公開画面へ詳細な環境情報を表示しないようにします。
文字化けやレイアウト崩れを切り分ける
問題が起きた場合は、帳票全体を見ながら推測でCSSを追加するのではなく、次の順番で確認します。
| 症状 | 最初に確認すること | 次の対応 |
|---|---|---|
| 日本語が文字化けする | フォントファイルの存在、fontDir、fontdata、font-family |
日本語1行だけの最小PDFで確認する |
| 太字だけ別の見た目になる | Bなどの書体登録とCSSの指定 |
Regularだけで出力し、太字の登録を追加して比較する |
| CSSが効かない | mPDFの対応CSSと対象バージョン | tableや単純なブロック要素へ戻してからCSSを一つずつ追加する |
| 途中でエラーになる | PHP拡張、tempDirの権限、入力HTML、画像 |
ログの例外内容と、最小HTMLでの結果を比較する |
| 改ページが意図と異なる | 実際のデータ量、表の高さ、page-break-*の指定 |
1ページ、2ページ、長い行を含む3種類のデータで確認する |
最小HTMLで日本語が出力できた後に、表、画像、改ページ、実データの順番で追加します。どの段階で崩れたかが分かれば、フォントの問題とレイアウトの問題を分けて調べられます。
動作確認の例
PHPファイルの構文を確認してから、PDFを生成します。
php -l generate-pdf.php
php generate-pdf.php
生成されたPDFでは、次の項目を確認します。
- 日本語、記号、数字が文字化けしていない
- RegularやBoldの書体が意図したフォントになっている
- 表の列幅と文字の折り返しが適切である
- 見出しだけがページ末尾に残っていない
- 複数ページの表が読める状態で続いている
- 長いURLや連続文字列がページ外へはみ出していない
- PDFファイルを生成した一時ファイルが不要に残っていない
PDFのページ数やメタデータをコマンドで確認したい場合は、環境にpdfinfoがインストールされていれば利用できます。
pdfinfo storage/mpdf/sample.pdf
PDFビューアでの目視確認も必要です。文字が存在することと、帳票として読みやすいことは別なので、実際に印刷またはPDFとして利用するサイズで確認します。
注意点
- mPDF本体の
vendor配下へフォントを直接追加すると、Composer更新時に失われる可能性があります。 - フォントファイルを追加する場合は、配布条件、埋め込み、商用利用の条件を確認します。
- ブラウザで表示できるCSSが、mPDFでも同じように動作するとは限りません。
page-break-inside: avoidなどの改ページ指定には、表のサイズや他の機能との組み合わせによる制約があります。- 利用者が入力したHTMLやURLをそのままPDFへ渡すと、意図しないタグや外部リソースを処理する可能性があります。
- PDF生成に失敗した場合、利用者へ例外の詳細やサーバー上のパスを表示しないようにします。
- 本番環境では、PDFの一時ファイルやログに個人情報が残らないように保存期間と出力内容を確認します。
まとめ
mPDFで日本語を含むPDFを安定して生成するには、フォントを登録するだけでなく、PDFを生成する環境と帳票のレイアウトを一緒に確認する必要があります。
- PHP・mPDFのバージョンと必要な拡張を確認する
- フォントをプロジェクト側で管理し、
fontDirとfontdataへ登録する - mPDFのHTML/CSS対応範囲に合わせて帳票を組み立てる
- 改ページや長い文字列は、複数ページの実データで確認する
- 文字化け、CSS、権限、入力データを分けて切り分ける
最初から複雑な帳票を完成させようとせず、日本語1行のPDF、表を含むPDF、複数ページのPDFという順番で確認すると、問題の場所を見つけやすくなります。mPDFのバージョンやフォントを変更した場合も、同じ確認用帳票を再生成して比較するとよいかと思います。
参考資料
奈良市を拠点に、27年以上の経験を持つフリーランスWebエンジニア、阿部辰也です。
これまで、ECサイトのバックエンド開発や業務効率化システム、公共施設の予約システムなど、多彩なプロジェクトを手がけ、企業様や制作会社様のパートナーとして信頼を築いてまいりました。
【制作会社・企業様向けサポート】
Webシステムの開発やサイト改善でお困りの際は、どうぞお気軽にご相談ください。小さな疑問から大規模プロジェクトまで、最適なご提案を心を込めてさせていただきます。
ぜひ、プロフィールやWeb制作会社様向け業務案内、一般企業様向け業務案内もご覧くださいね。
PHPでmPDFを使ってHTMLをPDFに変換する方法
2025.02.07
PHPのライブラリmPDFを使用して、HTMLドキュメントをPDFとして出力する方法を詳しく解説します。インストールから日本語対応まで、具体的なコード例を交えて説明します。
LaravelのArtisanコマンドを本番運用する:冪等性・二重実行防止・再実行の設計
2026.09.22
LaravelのArtisanコマンドでデータベースを更新する処理を、本番環境で安全に運用するための考え方を整理します。商品在庫の一括更新を例に、冪等性、dry-run、チャンク処理、トランザクション、二重実行防止、失敗後の再実行、ログや終了コード、実行前後の確認手順を紹介します。
PHPでDMARC集計レポート(XML)を解析する:SimpleXMLでSPF・DKIMの結果を配列化
2026.09.21
Googleなどのメール受信サービスから届くDMARC集計レポート(XML)をPHPのSimpleXMLで読み込み、report_metadata、policy_published、複数のrecordから送信元IP、件数、disposition、SPF・DKIMの結果を配列へ変換する方法を解説します。欠損項目やXML解析エラー、名前空間、UNIXタイムスタンプの日時変換も扱い、後続の画面表示や保存処理へ渡せる構造を作ります。
PHPのcURLで外部API連携を実装する:HTTPメソッド・認証・エラー処理を共通化
2026.09.20
PHPのcURL拡張を使い、GET・POST・PUT・PATCH・DELETEのHTTPリクエストを送信する方法を解説します。APIキーやBearerトークンの扱い、タイムアウト、通信エラーとHTTPエラーの分離、JSONレスポンスの解析までを共通関数にまとめます。