YorozuyaIT
Dev & Infrastructure

Japanese Text Shows as Empty Boxes in Docker, Puppeteer, and PDFs: Install a CJK Font

Written by the Yorozuya IT editorial team, based in Japan. Menus, apps, and rules change, so check the date.

Short answer

A page that looks perfect in your browser renders as □□□□□□ in a Puppeteer screenshot, a headless-Chrome PDF, or a chart made inside a container. Nothing is wrong with the text. The machine drawing it has no font that contains Japanese glyphs, so it draws the placeholder box people call tofu.

Diagram: a container without a Japanese font renders boxes; adding a CJK font fixes the output

Why it only breaks in containers

Your laptop ships with Japanese fonts. Slim base images such as node:slim, python:slim, or Alpine ship only a minimal set. A rule like font-family: "Noto Sans JP" in your CSS only requests a font; it does not put a font file on the machine.

The fix

Add a CJK font package to the image.

Debian / Ubuntu:

RUN apt-get update && apt-get install -y --no-install-recommends \
    fonts-noto-cjk fontconfig \
    && fc-cache -f \
    && rm -rf /var/lib/apt/lists/*

Alpine:

RUN apk add --no-cache font-noto-cjk fontconfig && fc-cache -f

If your pages also use emoji, add fonts-noto-color-emoji, or you will get boxes for those too.

Verify inside the container

Do not guess. Ask the font system:

fc-list :lang=ja | head
fc-match "sans-serif:lang=ja"

The first command should list at least one font. The second shows which font the system picks for Japanese sans-serif text. If both are empty or point to a font that has no Japanese, the install did not take effect.

If it still shows boxes

Test with a minimal page that contains only a line of Japanese, using the same image, browser build, and loading code as production. If the minimal page works, the difference is in your real page's fonts or print styles. Also check that you are not rendering before a web font has finished loading; wait for document.fonts.ready before capturing.

Japanese shows, but the shapes look Chinese

Japanese and Chinese share many Han characters, and fonts draw some of them differently (for example 直 or 骨). If the system picks a Chinese font, Japanese text can look subtly wrong. Fix it in two places:

<html lang="ja">
body { font-family: "Noto Sans CJK JP", "Noto Sans JP", sans-serif; }

The lang attribute tells the renderer which regional variant to prefer.

PDF libraries that do not use a browser

Libraries such as ReportLab or FPDF do not use system fonts automatically. Register a font explicitly. With ReportLab, one option is a built-in Japanese CID font:

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont

pdfmetrics.registerFont(UnicodeCIDFont("HeiseiKakuGo-W5"))
# canvas.setFont("HeiseiKakuGo-W5", 12)

For better typography, register a TrueType font file you have the license to embed.

Quick checklist

FAQ

Do I need to change my code?

Usually not. The fix is in the image or the machine that renders the page. Your HTML and CSS can stay the same.

My image is already big. Is there a smaller font?

Japanese-only fonts such as the IPA fonts (fonts-ipafont-gothic on Debian/Ubuntu) are smaller than the full CJK set. Test that the glyphs you need are covered.

Can I use a web font instead?

Yes, but the renderer must download it and wait for it to load before capturing. Installing the font in the image is more predictable.

Keep reading