Invalid Date

You built an HTML template that looks perfect in the browser. Then you generate a PDF — and every Japanese character turns into an empty box (tofu). The invoice is unreadable. The report is broken.

This is one of the most common issues developers face when generating Japanese PDFs from HTML. The fix is straightforward once you understand why it happens.

This guide covers everything you need to render Japanese text correctly in PDF output: how fonts work in PDF engines, how to load Google Fonts (Noto Sans JP), CSS best practices, how FUNBREW PDF handles Japanese fonts, common problems and their solutions, font subsetting for performance, and a complete Japanese invoice template.

Why Japanese Characters Show as Tofu in PDFs

Understanding the mechanism makes debugging much easier.

The Server-Side Font Problem

When a browser renders HTML, it uses fonts installed on the user's operating system. macOS has Hiragino Sans. Windows has Yu Gothic. Chromium on macOS renders Japanese text beautifully because those fonts exist locally.

But when a PDF engine runs on a Linux server, those OS fonts are not installed. The CSS declaration below looks fine locally — but breaks on a server:

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

On the server:

  1. Hiragino Sans — not found
  2. Yu Gothic — not found
  3. sans-serif fallback — resolves to DejaVu Sans or FreeSerif, which have no Japanese glyphs
  4. Result: every Japanese character renders as □

PDF Engine Comparison

Engine Japanese Font Handling
Chromium headless Uses OS fonts. Japanese breaks if fonts not installed on server
wkhtmltopdf WebKit engine. Same OS font dependency
FUNBREW PDF Noto Sans JP pre-installed. Japanese works without configuration

FUNBREW PDF's quality engine has Noto Sans JP bundled at the server level, so Japanese text renders correctly without any extra setup. If you're running Puppeteer or wkhtmltopdf yourself, follow the steps below to configure fonts explicitly.

Google Fonts: Loading Noto Sans JP for PDF

Noto Sans JP from Google Fonts is the most widely used Japanese font for PDF generation. It covers hiragana, katakana, kanji (CJK Unified Ideographs), punctuation, and common symbols. It's open source (OFL license) and comes in 9 weights.

Load via HTML <link> Tag

The simplest approach is embedding Google Fonts in the HTML <head>:

<!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>日本語テキストが正しく表示されます。Japanese text renders correctly.</p>
</body>
</html>

Note: this requires the PDF engine server to reach fonts.googleapis.com. If the server has network restrictions, use self-hosted fonts instead (covered below).

Load via CSS @import

For CSS-only setups:

@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;
}

Available Japanese Google Fonts

Font Style Best For
Noto Sans JP Gothic / sans-serif General documents, invoices, reports
Noto Serif JP Mincho / serif Contracts, formal documents
BIZ UDPGothic UD gothic Government docs, accessibility-first design
BIZ UDPMincho UD mincho Formal government-style documents
M PLUS 1p Modern gothic Design-forward documents
Kosugi Maru Rounded gothic Friendly, approachable tone

CSS Best Practices for Japanese PDFs

Building the Font Stack

Japanese text is often paired with a Latin font for numbers and Roman characters. List the Latin font first, then the Japanese font as fallback:

body {
  /* Latin: Inter → Japanese: Noto Sans JP → generic fallback */
  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;
}

/* Monospace for code, invoice numbers, amounts */
code, .invoice-number, .amount {
  font-family: 'JetBrains Mono', 'Noto Sans Mono', monospace;
}

When the browser renders a character, it uses the first font in the stack that has a glyph for that character. Latin characters come from Inter; Japanese characters fall through to Noto Sans JP. This gives you sharp Latin typography alongside proper Japanese rendering.

Always Specify font-weight Explicitly

This is the most common mistake. Google Fonts only loads the weights you specify in the URL. If you request only weight 400 but use font-weight: 700 in your CSS, the browser synthesizes a bold — and PDF engines often render synthesized bold poorly or not at all:

<!-- BAD: Only 400 loaded. font-weight: 700 will not render correctly -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">

<!-- GOOD: List every weight you use -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700;900&display=swap" rel="stylesheet">

Self-Hosted Fonts with @font-face

If you can't use Google Fonts CDN (security restrictions, air-gapped environments, reliability requirements), host the font files yourself:

@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;
}

Download font files from Google's google/fonts GitHub repository or the Noto fonts website.

Base64 Font Embedding: data:font/woff2;base64

For air-gapped environments, secure servers with no outbound internet access, or any situation where you need a completely self-contained HTML document, you can embed the font directly into your CSS using a Base64-encoded data: URI. This is the technique developers search for as data:font/woff2;base64 noto sans jp.

With Base64 embedding, the font data lives inside the HTML string itself. No external requests are made — making Japanese text rendering 100% deterministic regardless of network conditions.

How It Works

@font-face {
  font-family: 'NotoSansJP';
  /* Replace the placeholder below with the actual Base64 string */
  src: url('data:font/woff2;base64,d09GMgABAAA...(Base64 string)...') format('woff2');
  font-weight: 400;
  font-style: normal;
}

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

The data:font/woff2;base64, prefix is followed immediately by the Base64-encoded contents of the .woff2 file. For Noto Sans JP Regular, this is typically around 2MB of Base64 characters embedded in the CSS — but it eliminates all network round trips.

Converting a .woff2 File to Base64

Command line (Linux / macOS):

# Standard base64 (GNU coreutils)
base64 -w 0 NotoSansJP-Regular.woff2 > NotoSansJP-Regular.b64.txt

# macOS without GNU coreutils
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');

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-embedded.css', fontFaceCSS);
console.log('Base64 CSS written. Length:', fontFaceCSS.length);

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-embedded.css', 'w') as f:
    f.write(font_face_css)

print(f'Base64 CSS written. Characters: {len(base64_str):,}')

Complete Example: Base64 Font with FUNBREW PDF API

Here is a full working example sending an HTML document with an embedded Base64 Noto Sans JP font to the FUNBREW PDF API:

const fs = require('fs');

// Load and encode both weights
const regularBase64 = 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,${regularBase64}') 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; margin-bottom: 16px; }
  </style>
</head>
<body>
  <h1>Zero-Dependency Japanese PDF</h1>
  <p>This PDF was generated with a Base64-embedded Noto Sans JP font.</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 is not needed — Base64 fonts are inline, no network request
    },
  }),
});

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

Comparison: Base64 Embedding vs. Other Methods

Method Network Required HTML Size Impact Reliability Recommended When
FUNBREW PDF pre-installed None No change Highest Using FUNBREW PDF (default)
data:font/woff2;base64 None +2–5 MB per weight Very High Air-gapped / self-hosted environments
Self-hosted CDN (woff2) Yes No change High Stable CDN available
Google Fonts CDN Yes No change Network-dependent Development / prototyping

Note for FUNBREW PDF users: Noto Sans JP is pre-installed on FUNBREW PDF servers, so Base64 embedding is unnecessary when using the managed API. Base64 embedding is the right solution when you self-host Puppeteer or wkhtmltopdf in a network-restricted environment, or when you need a single portable HTML file that works anywhere.

Japanese Fonts in FUNBREW PDF

FUNBREW PDF runs on a Chromium-based engine with Noto Sans JP, Noto Serif JP, and BIZ UDPGothic pre-installed at the server level. You get reliable Japanese rendering without external network calls.

Using Pre-installed Fonts (Recommended)

Just reference the font name in your CSS — no loading required:

// 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>
        <p>Numbers: 1,234,567 | Symbols: ①②③ ㎡ ㈱</p>
      </body>
      </html>
    `,
    options: {
      engine: 'quality',
      format: 'A4',
    },
  }),
});

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

Using Google Fonts with FUNBREW PDF

If you need a specific font not pre-installed, enable waitForFonts to ensure the font finishes loading before PDF rendering begins:

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=M+PLUS+1p:wght@400;700&display=swap" rel="stylesheet">
        <style>
          body { font-family: 'M PLUS 1p', sans-serif; }
        </style>
      </head>
      <body>
        <p>カスタムフォントのテスト</p>
      </body>
      </html>
    `,
    options: {
      engine: 'quality',
      waitForFonts: true,
    },
  }),
});

Python Example

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を使用して生成されたPDFです。</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'])

For the full API reference, see the API documentation.

Common Problems and Solutions

Problem 1: Bold Text Not Working

Symptom: font-weight: 700 or font-weight: bold has no visible effect in the PDF.

Cause: The bold weight font file was never loaded.

Solution: List all weights you need in the Google Fonts URL:

<!-- Before: Only weight 400 loaded -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">

<!-- After: Explicit weight list -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700;900&display=swap" rel="stylesheet">

Then confirm your CSS actually references those weights:

h1 { font-weight: 700; }
.light { font-weight: 300; } /* Will only work if 300 is in the URL */

Problem 2: Specific Characters Still Showing as Tofu

Symptom: Regular hiragana and kanji render fine, but special symbols (①②③, ㎡, ㈱, NEC-defined characters) or emoji appear as □.

Cause: The chosen font doesn't contain glyphs for those specific code points.

Solution: Add Noto Color Emoji for emoji, or expand the font stack:

body {
  /* Noto Color Emoji handles emoji; Noto Sans JP handles Japanese text */
  font-family: 'Noto Sans JP', 'Noto Color Emoji', sans-serif;
}

For specific problem characters, target them with a separate class:

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

NEC special characters (NEC拡張文字) and IBM extension characters require fonts that include those code points — consider Noto Sans JP which has broad CJK coverage.

Problem 3: Line Spacing or Character Spacing Looks Wrong

Symptom: Japanese text looks cramped or overly spaced.

Cause: CSS defaults are tuned for Latin text. Japanese text benefits from slightly different values.

Solution: Explicitly set typography values for Japanese:

body {
  font-family: 'Noto Sans JP', sans-serif;
  line-height: 1.7;         /* Japanese body text: 1.6–1.8 */
  letter-spacing: 0.04em;  /* Slight tracking for readability */
  word-break: break-all;    /* Break long strings (URLs, numbers) */
  overflow-wrap: break-word;
}

h1, h2 {
  line-height: 1.4;         /* Tighter line height for headings */
  letter-spacing: -0.02em;
}

p {
  text-align: justify;
  text-justify: inter-ideograph; /* Improve justification for CJK */
}

Problem 4: Vertical Text (縦書き) Not Rendering

Symptom: writing-mode: vertical-rl is set but text appears horizontal, or the layout breaks.

Cause: Vertical writing mode support varies by engine. wkhtmltopdf has incomplete support.

Solution: Use FUNBREW PDF's quality mode (Chromium-based), which fully supports vertical writing:

.vertical-text {
  writing-mode: vertical-rl;  /* Right-to-left vertical */
  text-orientation: mixed;    /* Rotate Latin characters 90° */
  height: 300px;
  font-family: 'Noto Sans JP', serif;
}

.vertical-upright {
  writing-mode: vertical-rl;
  text-orientation: upright;  /* Latin characters upright in vertical flow */
}
<div class="vertical-text">
  <p>縦書きのサンプルテキスト。<br>日本語が縦方向に表示されます。</p>
</div>

For a detailed engine comparison, see wkhtmltopdf vs Chromium.

Problem 5: Intermittent Font Load Failures

Symptom: PDFs usually render correctly, but occasionally Japanese characters are missing.

Cause: Google Fonts CDN request timed out during PDF generation. The engine rendered without waiting for the font.

Solution A: Self-host fonts for guaranteed availability:

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

Solution B: Use FUNBREW PDF's pre-installed fonts:

/* No network request needed. Most reliable approach. */
body {
  font-family: 'Noto Sans JP', sans-serif;
}

FUNBREW PDF's pre-installed fonts eliminate all font-loading network latency, making Japanese PDF generation both faster and more reliable.

Font Subsetting and Performance

Japanese font files are large — Noto Sans JP's full version with all glyphs can reach 2MB–5MB per weight. At scale, font loading time becomes a bottleneck.

Google Fonts Automatic Subsetting

Google Fonts supports a text= parameter to load only glyphs for specific characters. For Japanese PDFs where the content isn't known in advance, this generally isn't practical:

<!-- Only useful for fixed text like UI labels -->
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&text=申請書類"
      rel="stylesheet">

CSS unicode-range to Limit Glyph Loading

Use unicode-range to split font loading by character range. This is useful when you want a separate Latin font (Inter, etc.) for numbers and a Japanese font for CJK:

@font-face {
  font-family: 'NotoSansJP';
  src: url('/fonts/NotoSansJP-Regular.woff2') format('woff2');
  font-weight: 400;
  /* Hiragana + Katakana + punctuation + basic CJK */
  unicode-range:
    U+3000-303F,  /* Japanese punctuation */
    U+3040-309F,  /* Hiragana */
    U+30A0-30FF,  /* Katakana */
    U+4E00-9FFF;  /* CJK Unified Ideographs (basic) */
}

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

body {
  /* Inter handles Latin; NotoSansJP handles Japanese automatically */
  font-family: 'Inter', 'NotoSansJP', sans-serif;
}

Font Loading Performance Comparison

Font Source Added Latency Reliability
Pre-installed (FUNBREW PDF) 0ms Highest
Self-hosted CDN (woff2) 50–150ms High
Google Fonts CDN 100–400ms Network-dependent

For high-volume PDF generation, pre-installed fonts are significantly faster. See the PDF API production guide for broader performance optimization strategies.

Font Embedding in the Output PDF

Chromium-based engines embed fonts in the generated PDF by default. This is the correct behavior — without embedding, the PDF relies on the viewer's installed fonts, which recreates the same problem on the reader's machine:

// Puppeteer: font embedding is on by default
const pdf = await page.pdf({
  format: 'A4',
  // No special flag needed — Chromium embeds fonts automatically
});

Do not disable font embedding. The slight increase in file size is worth the guaranteed rendering consistency.

Complete Example: Japanese Invoice Template

Here's a production-ready Japanese invoice template incorporating all the font best practices above.

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- Load Noto Sans JP with all weights we'll use: 400 (body) and 700 (headings) -->
  <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>
    /* ===== Reset & Base ===== */
    * {
      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;
    }

    /* ===== Print / PDF Settings ===== */
    @page {
      size: A4;
      margin: 15mm 20mm;
    }

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

    /* ===== Layout ===== */
    .page {
      max-width: 210mm;
      margin: 0 auto;
      padding: 20px;
    }

    /* ===== Header ===== */
    .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 ===== */
    .parties {
      display: flex;
      justify-content: space-between;
      margin-bottom: 32px;
    }

    .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;
    }

    /* ===== Total Highlight ===== */
    .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 Table ===== */
    .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;
    }

    /* Prevent table rows from splitting across pages */
    .line-items tbody tr {
      break-inside: avoid;
      page-break-inside: avoid;
    }

    /* Repeat header on every page for long tables */
    thead { display: table-header-group; }
    tfoot { display: table-footer-group; }

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

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

    .line-items tbody td.amount {
      text-align: right;
      font-variant-numeric: tabular-nums; /* Align decimal points */
    }

    /* ===== Summary ===== */
    .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;
    }

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

    /* ===== Payment Info ===== */
    .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: 12px;
      font-weight: 700;
      color: #6b7280;
      margin-bottom: 8px;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

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

    <!-- Bill To / From -->
    <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>

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

    <!-- Line items -->
    <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>

    <!-- Subtotal / tax / total -->
    <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>

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

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

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

To turn this into a dynamic template with variables like {{customer_name}} and {{total}}, see the invoice PDF automation guide. You can also paste this HTML directly into the Playground to preview the PDF output instantly.

Summary

Japanese text in HTML-to-PDF conversion breaks because PDF engines run on servers that don't have Japanese fonts installed. Here's the complete checklist:

  • Root cause: PDF engine can't render Japanese characters if server-side fonts are missing
  • Fastest fix: Use FUNBREW PDF or another service with Noto Sans JP pre-installed
  • DIY approach: Load Google Fonts (Noto Sans JP) in HTML — specify all weights you use
  • Bold not working: List wght@400;700 (or all weights) in the Google Fonts URL
  • Intermittent failures: Self-host fonts or use pre-installed fonts to eliminate CDN dependency
  • Vertical text: Requires Chromium-based engine (FUNBREW PDF quality mode)
  • Performance: Pre-installed fonts add 0ms latency; Google Fonts CDN adds 100–400ms

Test your font setup in the Playground before deploying to production.

Related Articles

Powered by FUNBREW PDF