NaN/NaN/NaN

HTML→PDFの変換で日本語が文字化けした、□(豆腐)が並んでしまった――この問題は、日本語コンテンツを扱う開発者なら一度は経験するはずです。

原因はシンプルです。PDFエンジンがサーバー上で動作するとき、日本語フォントがインストールされていないと文字を描画できません。ブラウザはOSのフォントを使うため問題が出ませんが、サーバーサイドのPDF生成では明示的なフォント設定が必要になります。

この記事では、FUNBREW PDFをはじめとするHTML to PDFツールで日本語フォントを正しく表示するための方法を、実際のコードとともに体系的に解説します。

なぜPDF変換で日本語が文字化けするのか

文字化けが起きるメカニズムを理解しておくと、問題の切り分けが格段に楽になります。

CSSのフォント指定と「フォールバック」の落とし穴

CSSで以下のように指定しても、実際に使われるフォントはCSSエンジンが自動で選択します:

body {
  font-family: 'Hiragino Sans', 'Yu Gothic', sans-serif;
}

ブラウザ(ローカル環境)では、Hiragino SansやYu GothicはmacOSやWindowsにインストール済みなので正常表示されます。しかしLinuxサーバー上のPDFエンジンには、これらのフォントが存在しません。フォールバック先のsans-serifも、Linuxでは欧文のDejaVuやFreeSansになることが多く、日本語グリフを持たないため豆腐が表示されます。

PDFエンジンの種類による違い

エンジン 日本語フォントの扱い
Chromium headless OSのフォントを参照。サーバーにフォントがなければ文字化け
wkhtmltopdf WebKitエンジン。同様にOSフォントに依存
FUNBREW PDF Noto Sans JPをプリインストール済み。追加設定不要

FUNBREW PDFはNoto Sans JPがあらかじめ組み込まれているため、特別な設定なしで日本語が表示されます。セルフホストのPuppeteerやwkhtmltopdfを使う場合は、次のセクションの手順でフォントを設定してください。

Google Fontsの日本語フォント活用法(Noto Sans JP)

Google Fontsが提供するNoto Sans JPは、日本語PDF生成に最もよく使われるフォントです。オープンライセンス(OFL)で商用利用も可能で、細字から黒体まで9ウェイト揃っています。

HTMLの<head>で読み込む

最もシンプルな方法は、HTMLに直接Google Fontsのリンクを埋め込むことです:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
  <style>
    body {
      font-family: 'Noto Sans JP', sans-serif;
    }
  </style>
</head>
<body>
  <p>日本語テキストが正しく表示されます。</p>
</body>
</html>

ただし、PDFエンジンがサーバー上で実行される場合、Google Fontsのサーバーへの通信が必要になります。通信に失敗するとフォントが読み込まれず文字化けします。

@importでCSSから読み込む

CSSファイルに記述する場合は@importを使います:

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700;900&display=swap');

body {
  font-family: 'Noto Sans JP', sans-serif;
  font-weight: 400;
}

h1, h2, h3 {
  font-weight: 700;
}

.light {
  font-weight: 300;
}

主要な日本語Google Fontsの比較

フォント 特徴 適したユースケース
Noto Sans JP ゴシック体。視認性が高い 一般的な文書、請求書、レポート
Noto Serif JP 明朝体。フォーマルな印象 契約書、公式文書
BIZ UDPGothic UD(ユニバーサルデザイン)ゴシック 官公庁系、読みやすさ重視
BIZ UDPMincho UD明朝体 官公庁系フォーマル文書
M PLUS 1p モダンなゴシック体 デザイン性の高いドキュメント
Kosugi Maru 丸ゴシック体 柔らかい印象の資料

CSSでのフォント指定ベストプラクティス

フォントスタックの書き方

日本語フォントは英数字フォントと組み合わせることが多いです。英数字には別のフォントを指定し、日本語はNoto Sans JPに任せる書き方がよく使われます:

body {
  /* 英数字: Inter → 日本語: Noto Sans JP → 汎用フォールバック */
  font-family: 'Inter', 'Noto Sans JP', sans-serif;
  font-size: 14px;
  line-height: 1.7;
  color: #1a1a1a;
}

h1 {
  font-family: 'Noto Sans JP', sans-serif;
  font-weight: 700;
  font-size: 24px;
  letter-spacing: -0.02em;
}

/* コード・数値は等幅フォントを優先 */
code, .invoice-number, .amount {
  font-family: 'JetBrains Mono', 'Noto Sans Mono', monospace;
}

font-weightの指定を忘れずに

Google Fontsは読み込むウェイトを?wght=400;700のように指定する必要があります。指定していないウェイトは読み込まれず、ブラウザが近いウェイトで代替するため、PDF上で「太字が効かない」問題が発生します:

<!-- NG: 400のみ読み込むと font-weight: 700 が効かない -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">

<!-- OK: 使用するウェイトをすべて指定 -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700;900&display=swap" rel="stylesheet">

@font-faceでローカルフォントを使う

セキュリティポリシーや通信環境の制約でGoogle Fontsが使えない場合、フォントファイルをホスティングしてCSSで指定できます:

@font-face {
  font-family: 'NotoSansJP';
  src: url('/fonts/NotoSansJP-Regular.woff2') format('woff2'),
       url('/fonts/NotoSansJP-Regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'NotoSansJP';
  src: url('/fonts/NotoSansJP-Bold.woff2') format('woff2'),
       url('/fonts/NotoSansJP-Bold.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: 'NotoSansJP', sans-serif;
}

フォントファイルはGoogleのNoto GitHubリポジトリ(google/fonts)から入手できます。

data:font/woff2;base64 によるフォントのBase64埋め込み

外部ネットワークにアクセスできないセキュア環境や、完全にスタンドアロンなHTMLでPDFを生成したい場合、フォントをBase64エンコードして直接CSSに埋め込む方法が有効です。data:font/woff2;base64@font-facesrc に指定することで、一切の外部リクエストなしに日本語フォントを使えます。

この手法は「data:font/woff2;base64 noto sans jp」などで検索して解決策を探している開発者にとってよく必要とされるアプローチです。

Base64埋め込みの仕組み

@font-face {
  font-family: 'NotoSansJP';
  src: url('data:font/woff2;base64,d09GMgABAAA...(Base64文字列)...') format('woff2');
  font-weight: 400;
  font-style: normal;
}

body {
  font-family: 'NotoSansJP', sans-serif;
}

data:font/woff2;base64, の後にフォントファイルをBase64エンコードした文字列を続けます。Noto Sans JPのフルウェイト(400)では約2MBのBase64文字列になりますが、HTMLに直接埋め込まれるためネットワーク遅延ゼロで確実に読み込まれます。

フォントファイルのBase64変換方法

コマンドラインでwoff2ファイルをBase64に変換する方法:

# Linux / macOS
base64 -w 0 NotoSansJP-Regular.woff2 > NotoSansJP-Regular.b64.txt

# macOS (GNU base64 非依存)
base64 < NotoSansJP-Regular.woff2 | tr -d '\n' > NotoSansJP-Regular.b64.txt

Node.jsで変換する場合:

const fs = require('fs');

const woff2Buffer = fs.readFileSync('./NotoSansJP-Regular.woff2');
const base64String = woff2Buffer.toString('base64');

// CSS文字列として出力
const fontFaceCSS = `@font-face {
  font-family: 'NotoSansJP';
  src: url('data:font/woff2;base64,${base64String}') format('woff2');
  font-weight: 400;
  font-style: normal;
}`;

fs.writeFileSync('./font-face.css', fontFaceCSS);

Pythonで変換する場合:

import base64

with open('NotoSansJP-Regular.woff2', 'rb') as f:
    woff2_bytes = f.read()

base64_str = base64.b64encode(woff2_bytes).decode('utf-8')

font_face_css = f"""@font-face {{
  font-family: 'NotoSansJP';
  src: url('data:font/woff2;base64,{base64_str}') format('woff2');
  font-weight: 400;
  font-style: normal;
}}"""

with open('font-face.css', 'w') as f:
    f.write(font_face_css)

FUNBREW PDF APIでBase64フォントを使う

FUNBREW PDF APIに送るHTMLにBase64フォントを埋め込む完全なサンプル:

const fs = require('fs');

// フォントファイルをBase64に変換
const fontBase64 = fs.readFileSync('./NotoSansJP-Regular.woff2').toString('base64');
const boldBase64 = fs.readFileSync('./NotoSansJP-Bold.woff2').toString('base64');

const html = `<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    @font-face {
      font-family: 'NotoSansJP';
      src: url('data:font/woff2;base64,${fontBase64}') format('woff2');
      font-weight: 400;
      font-style: normal;
    }
    @font-face {
      font-family: 'NotoSansJP';
      src: url('data:font/woff2;base64,${boldBase64}') format('woff2');
      font-weight: 700;
      font-style: normal;
    }
    body {
      font-family: 'NotoSansJP', sans-serif;
      font-size: 14px;
      line-height: 1.7;
      margin: 40px;
    }
    h1 { font-weight: 700; font-size: 24px; }
  </style>
</head>
<body>
  <h1>外部ネットワーク不要の日本語PDF</h1>
  <p>Base64埋め込みフォントにより、ネットワーク接続なしで日本語が正しく表示されます。</p>
  <p>ひらがな・カタカナ・漢字・記号(!?…①②)が完全対応。</p>
</body>
</html>`;

const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk-your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html,
    options: {
      engine: 'quality',
      format: 'A4',
      // waitForFonts不要: Base64埋め込みのため外部リクエストなし
    },
  }),
});

const result = await response.json();
console.log('PDF URL:', result.data.download_url);

Base64埋め込み vs. その他の方法の比較

方法 ネットワーク依存 HTMLサイズ セキュリティ 推奨シーン
FUNBREW PDFプリインストール なし 変化なし 最高 FUNBREW PDF利用時(推奨)
data:font/woff2;base64 なし +2〜5MB/ウェイト 高い エアギャップ環境・セルフホスト
自前CDN(woff2) あり 変化なし 高い 安定したCDNがある場合
Google Fonts CDN あり 変化なし ネット依存 開発・プロトタイプ

Note: FUNBREW PDFはNoto Sans JPをサーバーにプリインストールしているため、Base64埋め込みは不要です。自前でPuppeteerやwkhtmltopdfをホストしている場合、またはAPIが外部ネットワークにアクセスできないセキュア環境でご利用の場合にBase64埋め込みが有効です。

FUNBREW PDF APIでの日本語フォント対応

FUNBREW PDFはChromiumベースのエンジンを使用しており、Noto Sans JP、Noto Serif JP、BIZ UDPGothicなどの主要な日本語フォントをプリインストールしています。外部ネットワークへの依存なしに日本語PDFを生成できます。

プリインストールフォントを使う(推奨)

追加設定なしで使えます。CSSでfont-familyを指定するだけです:

// JavaScript (Node.js)
const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk-your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: `
      <!DOCTYPE html>
      <html lang="ja">
      <head>
        <meta charset="UTF-8">
        <style>
          body {
            font-family: 'Noto Sans JP', sans-serif;
            font-size: 14px;
            line-height: 1.7;
          }
          h1 { font-weight: 700; }
        </style>
      </head>
      <body>
        <h1>日本語タイトル</h1>
        <p>本文テキストが正しく表示されます。</p>
      </body>
      </html>
    `,
    options: {
      engine: 'quality',
      format: 'A4',
    },
  }),
});

const result = await response.json();
console.log('PDF URL:', result.data.download_url);

Google Fontsを使う場合

外部フォントを読み込む場合は、waitForFontsオプションを有効にしてフォントの読み込み完了を待つことを推奨します:

const response = await fetch('https://pdf.funbrew.cloud/api/pdf/generate', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk-your-api-key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    html: `
      <!DOCTYPE html>
      <html lang="ja">
      <head>
        <meta charset="UTF-8">
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
        <style>
          body { font-family: 'Noto Sans JP', sans-serif; }
        </style>
      </head>
      <body>
        <p>外部フォントを使った日本語テキスト</p>
      </body>
      </html>
    `,
    options: {
      engine: 'quality',
      waitForFonts: true,  // フォント読み込みを待つ
    },
  }),
});

Python での実装例

import requests

html_content = """
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <style>
    body {
      font-family: 'Noto Sans JP', sans-serif;
      margin: 40px;
      color: #1a1a1a;
    }
    .title {
      font-size: 24px;
      font-weight: 700;
      margin-bottom: 16px;
    }
    .body-text {
      font-size: 14px;
      line-height: 1.8;
    }
  </style>
</head>
<body>
  <div class="title">日本語PDF生成テスト</div>
  <div class="body-text">
    <p>この文書はFUNBREW PDF APIを使って生成されています。</p>
    <p>ひらがな・カタカナ・漢字・記号(!?…)が正しく表示されます。</p>
  </div>
</body>
</html>
"""

response = requests.post(
    'https://pdf.funbrew.cloud/api/pdf/generate',
    headers={'Authorization': 'Bearer sk-your-api-key'},
    json={
        'html': html_content,
        'options': {
            'engine': 'quality',
            'format': 'A4',
        },
    },
)

print('PDF URL:', response.json()['data']['download_url'])

APIの詳細な仕様はAPIリファレンスを参照してください。

よくある問題と解決策

問題1: 太字(font-weight: bold)が効かない

症状: font-weight: 700font-weight: bold を指定しても、通常の太さで表示される。

原因: ボールドウェイトのフォントファイルが読み込まれていない。

解決策: Google Fontsの読み込み時に使用するウェイトをすべて指定する:

<!-- 変更前: デフォルト(400のみ)-->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">

<!-- 変更後: 使用するウェイトを列挙 -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&display=swap" rel="stylesheet">

問題2: 特定の文字(記号・絵文字)が豆腐になる

症状: 一般的なひらがな・漢字は表示されるが、特殊記号(①②③、㎡、㈱)や絵文字が□になる。

原因: 使用しているフォントがその文字のグリフを持っていない。

解決策: Noto Sans JPは主要な日本語特殊記号をカバーしています。絵文字にはNoto Color Emojiを追加するか、CSSでフォントスタックに追加します:

body {
  /* 通常テキスト: Noto Sans JP → 絵文字: Noto Color Emoji */
  font-family: 'Noto Sans JP', 'Noto Color Emoji', sans-serif;
}

または、問題のある文字だけ別フォントで上書きします:

.emoji {
  font-family: 'Noto Color Emoji', 'Twemoji Mozilla', sans-serif;
}

問題3: 文字間隔・行間がおかしい

症状: 文字が詰まりすぎる、または行間が広すぎる。

原因: 欧文フォント用のデフォルト値が日本語には合わない場合がある。

解決策: 日本語テキスト向けの値を明示的に指定する:

body {
  font-family: 'Noto Sans JP', sans-serif;
  line-height: 1.7;          /* 日本語は1.6〜1.8が読みやすい */
  letter-spacing: 0.04em;   /* わずかに文字間を開ける */
  word-break: break-all;     /* 長い単語(URLなど)の折り返し */
  overflow-wrap: break-word;
}

h1, h2 {
  line-height: 1.4;          /* 見出しは少し詰める */
  letter-spacing: -0.02em;   /* 見出しは若干詰めても可 */
}

問題4: 縦書きテキストが表示されない

症状: writing-mode: vertical-rl を指定しても横書きのまま、または崩れて表示される。

原因: 縦書きはCSSのサポートが限定的で、PDFエンジンによって対応が異なる。

解決策: Chromiumベースのエンジン(FUNBREW PDFのqualityモード)は縦書きに対応しています:

.vertical-text {
  writing-mode: vertical-rl;  /* 右から左へ */
  text-orientation: mixed;    /* 英数字は90度回転 */
  height: 300px;
  font-family: 'Noto Sans JP', serif;
}

.vertical-upright {
  writing-mode: vertical-rl;
  text-orientation: upright;  /* 英数字も縦に並べる */
}
<div class="vertical-text">
  <p>縦書きのサンプルテキスト。<br>改行も正しく処理されます。</p>
</div>

wkhtmltopdfでは縦書きのサポートが不完全なため、縦書きが必要な場合はChromiumベースのエンジンを使用してください。詳しくはwkhtmltopdf vs Chromiumの比較記事を参照してください。

問題5: フォントの読み込みタイムアウト

症状: 通常は表示されるが、ごくまれに文字化けが発生する。

原因: Google Fontsなど外部CDNへの通信がタイムアウトし、フォントが読み込まれないことがある。

解決策A: フォントをセルフホスティングする(最も安定):

@font-face {
  font-family: 'NotoSansJP';
  src: url('https://your-cdn.example.com/fonts/NotoSansJP-Regular.woff2') format('woff2');
  font-weight: 400;
}

解決策B: FUNBREW PDFのプリインストールフォントを使う:

/* 外部リクエストが不要。最も安定した方法 */
body {
  font-family: 'Noto Sans JP', sans-serif;
}

FUNBREW PDFではフォントがサーバーにプリインストールされているため、ネットワーク遅延の影響を受けません。

フォントサブセット化とパフォーマンス

日本語フォントファイルは欧文フォントに比べて大幅に大きく、Noto Sans JPのフル版は約2MB〜5MB(全グリフ)になります。大量のPDFを生成する環境では、フォントのロード時間がボトルネックになりえます。

Google Fontsの自動サブセット化

Google Fontsはtext=パラメータで使用する文字のみを指定できます。ただし日本語PDFでは使用文字が事前にわからないため、このアプローチは通常使えません

<!-- 欧文のみの場合は有効(日本語には実用的でない) -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&text=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
      rel="stylesheet">

Unicodeレンジで読み込むグリフを限定

CSSのunicode-rangeを使って、ひらがな・カタカナ・基本漢字に絞る方法もあります:

@font-face {
  font-family: 'NotoSansJP';
  src: url('/fonts/NotoSansJP-Regular.woff2') format('woff2');
  font-weight: 400;
  /* ひらがな + カタカナ + 記号 + CJK統合漢字(基本) */
  unicode-range:
    U+3000-303F,  /* 日本語記号・句読点 */
    U+3040-309F,  /* ひらがな */
    U+30A0-30FF,  /* カタカナ */
    U+4E00-9FFF;  /* CJK統合漢字(基本) */
}

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  /* 英数字 */
  unicode-range: U+0000-00FF, U+0100-024F;
}

body {
  /* 英数字にはInter、日本語にはNotoSansJPが自動的に選ばれる */
  font-family: 'Inter', 'NotoSansJP', sans-serif;
}

PDFエンジン側でのフォント埋め込み設定

ChromiumベースのエンジンはデフォルトでフォントをPDFに埋め込みます。フォントを埋め込まないと、閲覧環境によって表示が変わるためONのままにしておきます:

// Puppeteer でのフォント埋め込み確認
const pdf = await page.pdf({
  format: 'A4',
  // Chromiumはデフォルトでフォントを埋め込む
  // 追加設定は不要
});

FUNBREW PDFでのパフォーマンス最適化

FUNBREW PDFでは、頻繁に生成するPDFにはプリインストールフォントの使用を推奨します。外部フォントを読み込む場合と比較した速度差の目安:

フォントの取得方法 追加レイテンシ 安定性
プリインストール(Noto Sans JP) 0ms 最高
自前CDNからwoff2 50〜150ms 高い
Google Fonts CDN 100〜400ms ネットワーク依存

詳細なパフォーマンス最適化はPDF API本番運用チェックリストで解説しています。

実践例:日本語の請求書テンプレート

これまでの内容を踏まえた、実務で使える日本語請求書テンプレートです。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Noto Sans JP: 400(本文)と 700(見出し・強調)を読み込む -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
  <style>
    /* ===== リセットと基本設定 ===== */
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      font-family: 'Noto Sans JP', sans-serif;
      font-size: 13px;
      line-height: 1.7;
      color: #1a1a1a;
      background: #ffffff;
    }

    /* ===== 印刷設定 ===== */
    @page {
      size: A4;
      margin: 15mm 20mm;
    }

    @media print {
      body {
        font-size: 11pt;
      }
    }

    /* ===== レイアウト ===== */
    .page {
      max-width: 210mm;
      margin: 0 auto;
      padding: 20px;
    }

    /* ===== ヘッダー ===== */
    .invoice-header {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 40px;
      padding-bottom: 20px;
      border-bottom: 3px solid #1a56db;
    }

    .invoice-title {
      font-size: 32px;
      font-weight: 700;
      color: #1a56db;
      letter-spacing: 0.1em;
    }

    .invoice-meta {
      text-align: right;
      font-size: 12px;
      color: #6b7280;
      line-height: 1.8;
    }

    .invoice-meta strong {
      color: #1a1a1a;
      font-weight: 700;
      font-size: 14px;
    }

    /* ===== 宛先・発行元 ===== */
    .parties {
      display: flex;
      justify-content: space-between;
      margin-bottom: 32px;
    }

    .to-section {
      flex: 1;
    }

    .to-section .company-name {
      font-size: 20px;
      font-weight: 700;
      margin-bottom: 4px;
    }

    .from-section {
      text-align: right;
      font-size: 12px;
      color: #6b7280;
      line-height: 1.8;
    }

    .from-section .company-name {
      font-size: 16px;
      font-weight: 700;
      color: #1a1a1a;
      margin-bottom: 4px;
    }

    /* ===== 合計金額 ===== */
    .total-highlight {
      background: #eff6ff;
      border: 2px solid #1a56db;
      border-radius: 8px;
      padding: 16px 24px;
      margin-bottom: 32px;
      display: flex;
      align-items: center;
      justify-content: space-between;
    }

    .total-label {
      font-size: 16px;
      font-weight: 700;
      color: #1e40af;
    }

    .total-amount {
      font-size: 28px;
      font-weight: 700;
      color: #1e40af;
      letter-spacing: -0.02em;
    }

    /* ===== 明細テーブル ===== */
    .line-items {
      width: 100%;
      border-collapse: collapse;
      margin-bottom: 24px;
    }

    .line-items thead th {
      background: #1a56db;
      color: #ffffff;
      font-weight: 700;
      padding: 10px 12px;
      text-align: left;
      font-size: 12px;
    }

    .line-items thead th:last-child,
    .line-items thead th:nth-last-child(2),
    .line-items thead th:nth-last-child(3) {
      text-align: right;
    }

    .line-items tbody tr {
      break-inside: avoid;
      page-break-inside: avoid;
    }

    .line-items tbody tr:nth-child(even) {
      background: #f8fafc;
    }

    .line-items tbody td {
      padding: 10px 12px;
      border-bottom: 1px solid #e5e7eb;
      font-size: 13px;
    }

    .line-items tbody td.amount {
      text-align: right;
      font-variant-numeric: tabular-nums;
    }

    /* 明細が多い場合の改ページ制御 */
    thead {
      display: table-header-group;
    }
    tfoot {
      display: table-footer-group;
    }

    /* ===== 小計・税・合計 ===== */
    .summary {
      width: 280px;
      margin-left: auto;
      margin-bottom: 32px;
    }

    .summary-row {
      display: flex;
      justify-content: space-between;
      padding: 8px 0;
      border-bottom: 1px solid #e5e7eb;
      font-size: 13px;
    }

    .summary-row.grand-total {
      font-size: 18px;
      font-weight: 700;
      border-bottom: 3px double #1a1a1a;
      padding: 12px 0;
    }

    /* ===== 振込先・備考 ===== */
    .payment-info {
      background: #f9fafb;
      border-radius: 6px;
      padding: 16px 20px;
      margin-bottom: 24px;
      break-inside: avoid;
      page-break-inside: avoid;
    }

    .payment-info h3 {
      font-size: 13px;
      font-weight: 700;
      color: #6b7280;
      margin-bottom: 8px;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    .payment-info p {
      font-size: 13px;
      line-height: 1.7;
    }

    /* ===== フッター ===== */
    .invoice-footer {
      margin-top: 32px;
      padding-top: 16px;
      border-top: 1px solid #e5e7eb;
      font-size: 11px;
      color: #9ca3af;
      text-align: center;
      break-inside: avoid;
      page-break-inside: avoid;
    }
  </style>
</head>
<body>
  <div class="page">
    <!-- ヘッダー -->
    <div class="invoice-header">
      <div class="invoice-title">請 求 書</div>
      <div class="invoice-meta">
        <strong>請求番号: INV-2026-0042</strong><br>
        発行日: 2026年4月5日<br>
        お支払い期限: 2026年4月30日
      </div>
    </div>

    <!-- 宛先・発行元 -->
    <div class="parties">
      <div class="to-section">
        <div class="company-name">株式会社サンプルクライアント 御中</div>
        <div style="color: #6b7280; font-size: 12px; margin-top: 4px;">
          担当: 山田 太郎 様
        </div>
      </div>
      <div class="from-section">
        <div class="company-name">株式会社FUNBREW</div>
        東京都渋谷区渋谷1-1-1<br>
        渋谷ビル 5F<br>
        TEL: 03-1234-5678<br>
        billing@funbrew.example.com<br>
        登録番号: T1234567890123
      </div>
    </div>

    <!-- 合計金額ハイライト -->
    <div class="total-highlight">
      <div class="total-label">今回ご請求金額(税込)</div>
      <div class="total-amount">¥165,000</div>
    </div>

    <!-- 明細テーブル -->
    <table class="line-items">
      <thead>
        <tr>
          <th style="width: 40%;">品目・内容</th>
          <th style="width: 15%; text-align: right;">数量</th>
          <th style="width: 20%; text-align: right;">単価(税抜)</th>
          <th style="width: 25%; text-align: right;">小計(税抜)</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Webシステム開発(2026年3月分)</td>
          <td class="amount">1式</td>
          <td class="amount">¥100,000</td>
          <td class="amount">¥100,000</td>
        </tr>
        <tr>
          <td>デザイン作成<br>
            <span style="font-size: 11px; color: #9ca3af;">トップページリニューアル</span>
          </td>
          <td class="amount">1式</td>
          <td class="amount">¥30,000</td>
          <td class="amount">¥30,000</td>
        </tr>
        <tr>
          <td>保守・サポート費用</td>
          <td class="amount">1ヶ月</td>
          <td class="amount">¥20,000</td>
          <td class="amount">¥20,000</td>
        </tr>
      </tbody>
    </table>

    <!-- 小計・消費税・合計 -->
    <div class="summary">
      <div class="summary-row">
        <span>小計(税抜)</span>
        <span>¥150,000</span>
      </div>
      <div class="summary-row">
        <span>消費税(10%)</span>
        <span>¥15,000</span>
      </div>
      <div class="summary-row grand-total">
        <span>合計(税込)</span>
        <span>¥165,000</span>
      </div>
    </div>

    <!-- 振込先 -->
    <div class="payment-info">
      <h3>お振込先</h3>
      <p>
        サンプル銀行 渋谷支店(支店番号: 123)<br>
        普通預金 口座番号: 1234567<br>
        口座名義: カ)ファンブリュー<br>
        ※振込手数料はご負担ください。
      </p>
    </div>

    <!-- 備考 -->
    <div class="payment-info">
      <h3>備考</h3>
      <p>
        お支払い期限を過ぎた場合は、年利14.6%の遅延損害金が発生します。<br>
        ご不明点はお気軽にご連絡ください。
      </p>
    </div>

    <!-- フッター -->
    <div class="invoice-footer">
      <p>本請求書は電子発行されています。印影・押印は省略しています。</p>
      <p style="margin-top: 4px;">Generated by FUNBREW PDF</p>
    </div>
  </div>
</body>
</html>

このテンプレートをFUNBREW PDFのダッシュボードに登録して変数化する方法は、請求書PDF自動化ガイドで詳しく解説しています。また、Playgroundに貼り付けてリアルタイムにPDF出力を確認できます。

まとめ

HTML→PDF変換で日本語フォントを正しく表示するポイントをまとめます:

  • 文字化けの原因: PDFエンジンを動かすサーバーに日本語フォントがなければ表示できない
  • 最短の解決策: FUNBREW PDFなどNoto Sans JPをプリインストールしたサービスを使う
  • 自前で設定する場合: Google Fonts(Noto Sans JP)をHTMLに読み込む。使用するウェイトをすべて指定すること
  • 太字が効かない: font-weight: 400;700 のようにウェイトを明示指定
  • 縦書き: Chromiumベースのエンジン(FUNBREW PDFのqualityモード)が対応
  • パフォーマンス: プリインストールフォント > 自前CDN > Google Fonts CDN の順で安定・高速

PlaygroundでHTMLを貼り付けてフォント設定を確認しながら、最適な日本語PDF環境を構築してください。

関連リンク

Powered by FUNBREW PDF