Text and fonts
Chinese, Japanese or Korean characters show as boxes
Latin text renders correctly and CJK text is a row of empty rectangles, often called tofu. Sometimes only some characters fail, which is the more confusing version.
What is actually happening
The font in use has no glyphs for those characters, and no font on the render machine does either. A minimal container image ships with almost no fonts, and none of them cover CJK. The partial-failure version happens when the fallback chain finds a font covering some ranges but not all.
Confirming it is this and not something that looks like it
Try a single common character in each script. If they all fail, no CJK font is available at all. If some render, you have partial coverage and need a font with the full range.
The fix
Declare a font that actually covers the script and load it explicitly rather than relying on whatever the machine has. Noto Sans CJK is the usual choice because its coverage is complete and its licence permits embedding. Note that Simplified Chinese, Traditional Chinese, Japanese and Korean want different regional variants for correct glyph shapes even where the code points overlap.
@font-face {
font-family: "Noto Sans SC";
src: url("https://assets.example.com/fonts/noto-sans-sc-400.woff2") format("woff2");
font-weight: 400;
/* Restrict to the ranges this face is for, so Latin still uses the brand font. */
unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF;
}
.document {
font-family: "Brand Sans", "Noto Sans SC", sans-serif;
}
/* Japanese and Korean want their own faces: the code points overlap but the
correct glyph shapes do not. */
:lang(ja) { font-family: "Brand Sans", "Noto Sans JP", sans-serif; }
:lang(ko) { font-family: "Brand Sans", "Noto Sans KR", sans-serif; }If that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- A lang attribute missing from the markup, so the engine cannot pick a regional variant
- A CJK font subset that dropped the characters actually used, which is easy to do given the character count
- line-height tuned for Latin, which crowds CJK text badly
- Full-width punctuation, which needs the CJK font rather than the Latin one
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. The font in use has no glyphs for those characters, and no font on the render machine does either. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
No, and that is what makes it expensive. A document that renders wrong is still a successful render as far as every log line is concerned. It is found by someone opening the file, which is usually the customer.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
Custom fonts fall back to a default in the PDF
The font file was not fetched.
Emoji do not render in the PDF
Emoji need a dedicated font, and colour emoji need one in a colour font format.
Arabic or Hebrew text renders backwards or disconnected
Two separate problems.
Text in the PDF cannot be selected or searched
The text is not text.
Line breaks in the PDF differ from the browser preview
The font is not the same.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.