CJK · Chinese · Japanese · Korean

CJK PDF API: Hanzi, Kanji, and Hangul rendering that just works.

Generate Simplified + Traditional Chinese, Japanese, and Korean PDFs from HTML. Chromium handles CJK shaping, vertical writing-mode, and ruby annotations natively. Noto Sans CJK (SC, TC, JP, KR) is pre-loaded — verified rendering out of the box (see font coverage). Upload a custom CJK family via Brand Kit and Kamy injects @font-face on every render.

Coverage

Four scripts, four font uploads.

Chromium shapes all four CJK scripts when the font is loaded. Use the family name in theme.fontFamily and Kamy applies it to every render.

LanguageScriptNotesRecommended font
Simplified Chinese (zh-CN)Hanzi (简体)Mainland China standard. GB 18030 codepoint coverage required for legal documents.Noto Sans SC (or Source Han Sans SC)
Traditional Chinese (zh-TW, zh-HK)Hanzi (繁體)Taiwan / Hong Kong. Different character set — not interchangeable with SC.Noto Sans TC (or Noto Sans HK for HK-specific glyphs)
Japanese (ja)Kanji + Hiragana + KatakanaMixed-script. Vertical writing-mode common in legal and editorial layouts.Noto Sans JP (or Noto Serif JP for legal docs)
Korean (ko)Hangul (+ Hanja)Hangul syllabic composition is automatic in Chromium when the font is loaded.Noto Sans KR (or Noto Serif KR)

Code

Two complete examples.

Japanese invoice (vertical writing-mode)

// Japanese invoice — vertical writing-mode for legal-style layouts
// Set theme.fontFamily to a Noto Sans JP family you've uploaded via brand kit.

const res = await fetch("https://kamy.dev/api/v1/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.KAMY_API_KEY}`,
  },
  body: JSON.stringify({
    html: `
      <!doctype html>
      <html lang="ja">
        <head>
          <style>
            body {
              font-family: "Noto Sans JP", sans-serif;
              writing-mode: vertical-rl;
              padding: 40px;
              line-height: 1.8;
            }
            .total { font-size: 28px; font-weight: 600; }
          </style>
        </head>
        <body>
          <h1>請求書 No. INV-2026-0042</h1>
          <p>株式会社サンプル様</p>
          <p>2026年4月24日発行</p>
          <p class="total">合計金額: ¥1,234,560(税込)</p>
        </body>
      </html>
    `,
    theme: {
      fontFamily: "Noto Sans JP",
      locale: "ja-JP",
    },
  }),
});

Korean receipt

// Korean receipt — Hangul composes automatically in Chromium with Noto Sans KR

const res = await fetch("https://kamy.dev/api/v1/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.KAMY_API_KEY}`,
  },
  body: JSON.stringify({
    html: `
      <!doctype html>
      <html lang="ko">
        <head>
          <style>
            body { font-family: "Noto Sans KR", sans-serif; padding: 40px; }
            table { width: 100%; border-collapse: collapse; }
            th, td { padding: 8px 12px; border-bottom: 1px solid #ddd; }
            th { text-align: left; background: #f5f5f5; }
          </style>
        </head>
        <body>
          <h1>영수증 #2026-0042</h1>
          <p>김민수 고객님 · 2026년 4월 24일</p>
          <table>
            <thead><tr><th>품목</th><th>수량</th><th>금액</th></tr></thead>
            <tbody>
              <tr><td>구독료 (월간)</td><td>1</td><td>₩49,000</td></tr>
              <tr><td>부가세 (10%)</td><td></td><td>₩4,900</td></tr>
            </tbody>
          </table>
          <p style="margin-top:24px;font-size:18px;font-weight:600">
            합계: ₩53,900
          </p>
        </body>
      </html>
    `,
    theme: { fontFamily: "Noto Sans KR", locale: "ko-KR" },
  }),
});

Production tip — defaults are already canonical

Noto Sans CJK (SC, TC, JP, KR) is pre-loaded — verified rendering out of the box.

Unlike Indic scripts where the default falls back to older fonts, every CJK PDF on Kamy renders with Noto Sans CJK — Adobe + Google's reference family. You only need to upload via Brand Kit if you want a specific custom CJK family (Source Han, Sarasa Gothic, a corporate font, etc).

Common pitfalls

Why CJK breaks elsewhere — and how Kamy avoids it.

All Hanzi / Kanji / Hangul render as empty boxes

Symptom
Document is full of □ □ □ where CJK text should be.

Cause
The render image's default font has no CJK glyph coverage, so Chromium falls back to .notdef (the empty box).

Kamy fix
On kamy.dev this doesn't happen by default — Noto Sans CJK (SC, TC, JP, KR) is pre-loaded and verified. If you're self-hosting on a render image without CJK fonts, upload Noto Sans CJK via Brand Kit → Fonts and set theme.fontFamily to the family name.

Vertical writing-mode prints horizontally

Symptom
Japanese legal documents need top-to-bottom, right-to-left flow but render as standard left-to-right.

Cause
writing-mode: vertical-rl is a CSS property — wkhtmltopdf and older PDF tools ignore it entirely.

Kamy fix
Kamy uses Chromium, which supports CSS Writing Modes Level 3 natively. Just set writing-mode: vertical-rl on the body and the entire document flips correctly, including page-break behavior.

Full-width punctuation overlaps on line breaks

Symptom
Japanese 「 」 and Chinese ,。 punctuation gets stuck at the start of a new line, breaking typographic conventions.

Cause
kinsoku shori (Japanese line-breaking rules) and CJK punctuation positioning need a font that declares the right OpenType features.

Kamy fix
Noto Sans CJK ships with the correct kinsoku tables. Chromium honors them automatically once the font is loaded — no extra config.

Ship your first CJK PDF in 60 seconds.

Send HTML, get a PDF — Noto Sans CJK is pre-loaded. Upload a custom family only if you want a specific style. Same API as every other render.

CJK PDF API — Chinese, Japanese, Korean rendering | Kamy — Kamy