2026/05/11

wkhtmltopdf @page CSSガイド|A4サイズ・margin 0・ページ番号の設定

wkhtmltopdfCSSPDF生成チュートリアル

@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-beforepage-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

デバッグのコツ

  1. --debug-javascript を追加して、フッター/ヘッダー HTML の JS エラーを確認する
  2. --dump-outline outline.xml でページ構造を検査する
  3. 本番環境では --quiet を指定して stderr への非エラー出力を抑制する
  4. 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: alwaysbreak-inside: avoid は動作する — 積極的に使う
  • 背景色: --print-media-type-webkit-print-color-adjust: exact が必要

これらのワークアラウンドに限界を感じているなら、FUNBREW PDF のような Chromium ベースの API が CLI を不要にし、完全な @page CSS サポートを提供します。Playground でサインアップなしに試すことができます。

関連リンク

Powered by FUNBREW PDF