Chinese and Japanese PDFs from HTML: fonts and language settings
Chinese and Japanese text in a PDF goes wrong in two ways. Either you get empty boxes because the machine has no CJK font, or the text shows up but some characters have the wrong regional shape. Both come from the same place: the fonts and the lang attribute. This guide shows what to install and what to set when you print HTML with Puppeteer, headless Chrome or an API.
Install the fonts Set the language Pick a font per language Line breaks Full example One API call FAQ
Install the fonts
Chinese and Japanese use thousands of characters, and a basic server or container image has no font that covers them. Chrome then draws empty boxes without any error. On a Debian-based image, install the Noto CJK fonts:
RUN apt-get update && apt-get install -y --no-install-recommends \
fontconfig fonts-noto-cjk \
&& fc-cache -f \
&& rm -rf /var/lib/apt/lists/*Rebuild the image and check that the system sees fonts for each language:
fc-list :lang=zh-cn family | head -3
fc-list :lang=ja family | head -3Each command should list at least one family. The CJK package makes the image noticeably bigger. For the general version of this problem, see our guide to empty boxes in Puppeteer PDFs.
Set the language, not only the font
Chinese, Japanese and Korean share many characters, but the same character can be drawn with a different shape depending on the language. Characters such as 直 and 骨 are good examples. If the page has no language, the browser can pick a regional font you did not intend, and Chinese text may appear with Japanese shapes.
Set lang on the <html> tag: lang="zh-CN" for Simplified Chinese, lang="zh-TW" for Traditional Chinese, lang="ja" for Japanese. For a document that mixes languages, set lang on the element that holds each piece, for example <p lang="ja">.
Pick a font per language
Do not rely on the default font. Name the regional family for each language, so the result is the same on every machine that has the fonts installed:
:lang(zh-CN) { font-family: "Noto Sans CJK SC", sans-serif; }
:lang(zh-TW) { font-family: "Noto Sans CJK TC", sans-serif; }
:lang(ja) { font-family: "Noto Sans CJK JP", sans-serif; }
body { line-height: 1.7; }CJK text looks denser than Latin text at the same size, so a line height of about 1.6 to 1.8 usually reads better.
Line breaks and punctuation
- No spaces between words: Chinese and Japanese lines can break between almost any two characters. This is normal, and Chrome does it for you.
- Long URLs and codes: a long Latin string inside CJK text can overflow the page. Add
overflow-wrap: anywhereto that element. - Japanese line-break rules:
line-break: strictfollows the stricter rules about which characters may start or end a line. - Punctuation: use the full-width marks of the language, such as
,and。in Chinese or、and。in Japanese. Chrome prints what you send. - Vertical text: Chrome supports
writing-mode: vertical-rlfor vertical Japanese layouts.
Full example
A Japanese document with one Chinese line, regional fonts per language and a safe long URL:
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<style>
:lang(zh-CN) { font-family: "Noto Sans CJK SC", sans-serif; }
:lang(ja) { font-family: "Noto Sans CJK JP", sans-serif; }
body { line-height: 1.7; }
.url { overflow-wrap: anywhere; }
</style>
</head>
<body>
<h1>請求書</h1>
<p>請求書番号: <bdi>INV-00123</bdi></p>
<p lang="zh-CN">发票 · 你好,世界</p>
<p class="url">https://example.com/orders/12345/invoice</p>
</body>
</html>The same result with one API call
Getprintoza is an HTML to PDF API that runs real Chromium with fonts for 12 languages already installed, including Simplified Chinese (zh) and Japanese (ja). Set the language and we apply the matching language attribute and font. If you leave it out, we detect Japanese from kana, Korean from Hangul, and text with only Chinese characters is treated as Chinese. Because Japanese text made only of kanji looks the same to a detector, set language explicitly for important documents.
curl -X POST https://getprintoza.com/v1/convert \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"language": "ja", "html": "<h1>請求書</h1><p>ご利用ありがとうございます</p>"}' \
--output invoice.pdfIf your HTML contains characters that none of the installed fonts can draw, the PDF is still created and the response carries an X-Getprintoza-Warnings header that lists them. This is a best-effort check, so look at a few real documents before you launch. Details are in the API documentation. The Free plan includes 50 PDFs a month and needs no card: get a free API key or see the pricing.
FAQ
My Chinese text looks like Japanese. Why?
The page has no language, or the wrong one, so the browser used a Japanese font for shared characters. Set lang="zh-CN" or lang="zh-TW" and name the matching font.
Do I need a separate font for every document?
No. Install the Noto CJK package once on the machine that runs Chrome and name the regional family in CSS.
Why does my image grow so much after adding CJK fonts?
The fonts cover tens of thousands of characters. If size matters, install them only in the image that renders PDFs.
Can I mix Chinese, Japanese and English in one PDF?
Yes. Put lang on each element that holds a different language and keep Latin text in the same font stack.