wkhtmltopdf @page CSSガイド|A4サイズ・margin 0・ページ番号の設定
@page { size: A4; margin: 0; } と書いたのに wkhtmltopdf がマージンを無視して余白が残る――このトラブルに直面している開発者は多いです。この記事では、wkhtmltopdf が @page CSS のどのプロパティを処理し、どれを無視するかを具体的に解説します。
すでに移行を決断しているなら、モダンなPDF APIへの移行 セクションに進んでください。ここで紹介するワークアラウンドは一時的な解決策です。根本的な制限はアーキテクチャに起因するためです。
wkhtmltopdf が @page CSS を独自に扱う理由
wkhtmltopdf は Qt WebKit(2012年頃に凍結されたバージョン)をベースにしています。CSS @page ルールは CSS Paged Media 仕様の一部ですが、WebKit はこの仕様を完全に実装しませんでした。その結果、wkhtmltopdf は @page プロパティの一部しか読まず、残りはエラーも警告もなく無視します。
実際の影響:@page だけでページレイアウトを制御することはできません。CSS の宣言と CLI フラグを組み合わせる必要があります。
@page プロパティ:動作するものとしないもの
| プロパティ | wkhtmltopdf のサポート | 備考 |
|---|---|---|
size: A4 |
部分的 | CLIの --page-size と競合する場合は無視される |
size: A4 landscape |
部分的 | --orientation Landscape CLIフラグを使用すること |
margin: 0 |
無視 | --margin-* CLIフラグが必要 |
margin: 20mm |
無視 | --margin-* CLIフラグが必要 |
@top-center { content: ... } |
非対応 | --header-html または --footer-html を使用 |
@bottom-right { content: counter(page) } |
非対応 | --footer-right "[page]" を使用 |
marks: crop cross |
非対応 | — |
orphans / widows |
部分的 | 結果が不安定 |
まとめ: マージンは常に CLI フラグを使用してください。ヘッダー/フッターには専用の HTML オプションを使用してください。@page size は動作することもありますが、--page-size の方が確実です。
ページサイズの設定
A4(最も一般的な設定)
/* CSS ヒント(wkhtmltopdfのバージョンによっては動作する) */
@page {
size: A4;
}
CLI フラグ(確実):
wkhtmltopdf --page-size A4 input.html output.pdf
両方が指定されて競合する場合、CLI フラグが優先されます。
カスタムサイズ
# 210mm x 297mm(A4の正確な寸法)
wkhtmltopdf --page-width 210mm --page-height 297mm input.html output.pdf
# A3
wkhtmltopdf --page-size A3 input.html output.pdf
横向き(ランドスケープ)
/* wkhtmltopdfの多くのバージョンでは無視される */
@page { size: A4 landscape; }
# 確実な方法
wkhtmltopdf --orientation Landscape --page-size A4 input.html output.pdf
マージンの設定
@page { margin: 0; } は wkhtmltopdf では動作しません。エンジンは CSS の指定に関係なく、独自のデフォルトマージン(約10mm)を使用します。
マージンをゼロにする
wkhtmltopdf \
--margin-top 0 \
--margin-right 0 \
--margin-bottom 0 \
--margin-left 0 \
input.html output.pdf
標準的なドキュメントマージン
wkhtmltopdf \
--margin-top 20mm \
--margin-right 15mm \
--margin-bottom 20mm \
--margin-left 15mm \
input.html output.pdf
フッター用に下部マージンを追加
フッターを使用する場合、コンテンツとの重なりを防ぐために下部マージンを広げます:
wkhtmltopdf \
--margin-bottom 25mm \
--footer-html footer.html \
--footer-spacing 5 \
input.html output.pdf
ページ番号
wkhtmltopdf でのページ番号は、CSS カウンターではなく CLI の置換変数を使って実装します。
フッターにシンプルなページ番号
wkhtmltopdf \
--footer-right "Page [page] of [topage]" \
--footer-font-size 9 \
input.html output.pdf
使用できる変数: [page](現在ページ)、[topage](総ページ数)、[date]、[time]、[title]、[doctitle]。
HTMLフッターにページ番号を表示
スタイリングされたフッターを使う場合は、別のHTMLファイルとJavaScriptを使います:
<!-- footer.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
margin: 0;
padding: 4mm 15mm;
font-family: 'Noto Sans JP', sans-serif;
font-size: 8pt;
color: #6b7280;
}
.footer-inner {
display: flex;
justify-content: space-between;
border-top: 1px solid #e5e7eb;
padding-top: 3mm;
}
</style>
</head>
<body>
<div class="footer-inner">
<span>社外秘</span>
<span id="page-num"></span>
</div>
<script>
// wkhtmltopdfはフッターURLに変数をクエリパラメーターとして注入する
var vars = {};
var parts = window.location.search.substr(1).split('&');
for (var i = 0; i < parts.length; i++) {
var p = parts[i].split('=', 2);
if (p.length == 2) vars[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
}
document.getElementById('page-num').textContent =
(vars['page'] || '') + ' / ' + (vars['topage'] || '');
</script>
</body>
</html>
wkhtmltopdf \
--footer-html footer.html \
--margin-bottom 20mm \
--footer-spacing 3 \
input.html output.pdf
スクリプトが確実に実行されない場合は --enable-javascript と --javascript-delay フラグを追加します:
wkhtmltopdf \
--enable-javascript \
--javascript-delay 200 \
--footer-html footer.html \
input.html output.pdf
ヘッダーの設定
ヘッダーはフッターと同じように設定します:
wkhtmltopdf \
--header-html header.html \
--margin-top 25mm \
--header-spacing 3 \
input.html output.pdf
または、シンプルなテキストオプション:
wkhtmltopdf \
--header-left "[title]" \
--header-right "[date]" \
--header-line \
--header-font-size 9 \
input.html output.pdf
動作する @page CSS
一部の @page プロパティは確実に動作します:
@page {
/* サイズヒント(確実性はCLIの方が高い) */
size: A4;
/* 以下は効果なし — CLIを使うこと */
/* margin: 20mm; */
/* @top-center { ... } */
}
/* 要素への改ページ指定(@page の外側) — これは動作する */
.page-break {
page-break-before: always;
break-before: page;
}
/* 要素の分割防止 */
.keep-together {
page-break-inside: avoid;
break-inside: avoid;
}
page-break-before、page-break-inside などの改ページプロパティは wkhtmltopdf でも動作します。 ページレイアウト制御の主要な CSS ツールとして積極的に使用してください。
完全な動作例
マージン・フッター・ページサイズを含む本番用コマンド:
wkhtmltopdf \
--page-size A4 \
--margin-top 20mm \
--margin-right 15mm \
--margin-bottom 25mm \
--margin-left 15mm \
--footer-html footer.html \
--footer-spacing 5 \
--enable-javascript \
--javascript-delay 100 \
--print-media-type \
--encoding utf-8 \
--disable-smart-shrinking \
input.html \
output.pdf
主要フラグの説明:
| フラグ | 目的 |
|---|---|
--print-media-type |
@media print の CSS ルールを適用 |
--disable-smart-shrinking |
コンテンツの自動縮小を防止 |
--encoding utf-8 |
日本語などのマルチバイト文字に必要 |
--javascript-delay 100 |
レンダリング前に JS の実行を待機 |
アプリケーションコードからの呼び出し
アプリケーションコードからは、wkhtmltopdf をサブプロセスとして標準入力経由で起動するのが一般的です:
import subprocess
html = "<html><body><h1>請求書</h1></body></html>"
result = subprocess.run(
[
"wkhtmltopdf",
"--page-size", "A4",
"--margin-top", "20mm",
"--margin-right", "15mm",
"--margin-bottom", "20mm",
"--margin-left", "15mm",
"--footer-right", "Page [page] / [topage]",
"--encoding", "utf-8",
"--quiet",
"-", # 標準入力
"-", # 標準出力
],
input=html.encode("utf-8"),
capture_output=True,
)
pdf_bytes = result.stdout
const { execFile } = require("child_process");
const { promisify } = require("util");
const execFileAsync = promisify(execFile);
async function htmlToPdf(html) {
const args = [
"--page-size", "A4",
"--margin-top", "20mm",
"--footer-right", "Page [page] of [topage]",
"--encoding", "utf-8",
"--quiet",
"-", // stdin
"-", // stdout
];
const { stdout } = await execFileAsync("wkhtmltopdf", args, {
input: html,
encoding: "buffer",
maxBuffer: 50 * 1024 * 1024,
});
return stdout; // PDFバイナリ
}
既知の制限事項
@page CSS 以外にも、wkhtmltopdf にはアーキテクチャ的な限界があります:
- CSS Grid / Flexbox の非対応: Grid は未対応、Flexbox は部分的。複雑なレイアウトにはテーブルベースのフォールバックが必要
- JavaScript の制限: 組み込みの WebKit では現代的な JS API が動作しないことがある
- フォント読み込みの不安定さ:
@font-faceで指定したウェブフォントが読み込まれないことがある - thead 繰り返しの不安定さ:
display: table-header-groupが一部のテーブル構造で機能しないことがある - メンテナンス終了: 最終リリースは 2020年の 0.12.6。セキュリティパッチは提供されていない
機能の詳細な比較は wkhtmltopdf vs Chromium を参照してください。
モダンなPDF APIへの移行
wkhtmltopdf の制限が本番環境で問題になっているなら、Chromium ベースの API への移行でほとんどが解決します。コード変更は最小限です。
FUNBREW PDF では、wkhtmltopdf が苦手とする HTML が正しく動作します。エンジンが Chromium(Chrome と同じレンダリングエンジン)であるため、@page CSS 仕様が完全に実装されています。margin: 0 が動作し、thead { display: table-header-group; } も確実に繰り返されます。
移行前(wkhtmltopdf サブプロセス)
result = subprocess.run(
["wkhtmltopdf", "--margin-top", "20mm", "--margin-bottom", "20mm",
"--footer-right", "Page [page]", "--encoding", "utf-8", "-", "-"],
input=html.encode("utf-8"),
capture_output=True,
)
pdf_bytes = result.stdout
移行後(FUNBREW PDF API)
import requests
response = requests.post(
"https://pdf.funbrew.cloud/api/pdf/generate",
headers={"Authorization": "Bearer sk-your-api-key"},
json={
"html": html,
"options": {
"engine": "quality", # Chromium — @page CSS完全対応
"format": "A4",
"margin": {
"top": "20mm",
"right": "15mm",
"bottom": "20mm",
"left": "15mm",
},
"displayHeaderFooter": True,
"footerTemplate": "<div style='font-size:9pt; color:#6b7280; width:100%; text-align:right; padding-right:15mm;'><span class='pageNumber'></span> / <span class='totalPages'></span></div>",
},
},
)
download_url = response.json()["data"]["download_url"]
主な違い:サブプロセス不要、CLI フラグ不要、margin が JSON オプションで直接指定可能、Chromium の完全な @page CSS サポートでワークアラウンドが不要になります。
移行のステップバイステップは wkhtmltopdf 移行ガイド を参照してください。
クイックリファレンス:wkhtmltopdf CLI フラグ一覧
| 目的 | CSS(動作するか?) | CLI フラグ |
|---|---|---|
| A4 ページサイズ | @page { size: A4 } (部分的) |
--page-size A4 |
| マージンをゼロに | @page { margin: 0 } (動作しない) |
--margin-top 0 --margin-right 0 --margin-bottom 0 --margin-left 0 |
| 20mm 上マージン | @page { margin-top: 20mm } (動作しない) |
--margin-top 20mm |
| 横向き | @page { size: landscape } (動作しない) |
--orientation Landscape |
| ページ番号 | @page { @bottom-right { ... } } (動作しない) |
--footer-right "Page [page] of [topage]" |
| 強制改ページ | page-break-before: always (動作する) |
— |
| 行の分割防止 | break-inside: avoid (動作する) |
— |
@media print CSS 適用 |
— | --print-media-type |
デバッグのコツ
--debug-javascriptを追加して、フッター/ヘッダー HTML の JS エラーを確認する--dump-outline outline.xmlでページ構造を検査する- 本番環境では
--quietを指定して stderr への非エラー出力を抑制する - Playground でリアルタイムテスト — CLI セットアップなしで HTML を貼り付けて即 PDF 出力を確認できます
まとめ
wkhtmltopdf の @page CSS まとめ:
- マージン: CSS の
@page { margin }は無視される。常に--margin-*CLI フラグを使う - ページサイズ: CLI の
--page-sizeが正式。@page { size }はせいぜいヒントとして機能 - 横向き:
@page { size: landscape }ではなく--orientation Landscapeを使う - ページ番号 / ヘッダー / フッター:
--footer-right、--header-html、--footer-htmlオプションを使う - 改ページ:
page-break-before: alwaysとbreak-inside: avoidは動作する — 積極的に使う - 背景色:
--print-media-typeと-webkit-print-color-adjust: exactが必要
これらのワークアラウンドに限界を感じているなら、FUNBREW PDF のような Chromium ベースの API が CLI を不要にし、完全な @page CSS サポートを提供します。Playground でサインアップなしに試すことができます。
関連リンク
- wkhtmltopdf 移行ガイド — wkhtmltopdf から FUNBREW PDF へのステップバイステップ移行
- wkhtmltopdf vs Chromium — 機能比較とベンチマーク結果
- PDFテーブル改ページ完全ガイド — thead 繰り返しを含むテーブルの改ページ問題解決
- HTML→PDF変換で使えるCSS設計テクニック集 — Chromium ベースエンジンでの改ページ・フォント・印刷CSS
- APIリファレンス — FUNBREW PDF エンドポイントの詳細仕様
- Playground — ブラウザで HTML を即 PDF 変換してテスト