PDFPipe

East Asian scripts / Japanese

Japanese text in a PDF

Japanese shares code points with Chinese while wanting different glyph shapes for them, and has line-breaking rules that no default implements.

A font with the characters is not enough

Han unification means a character used in both Japanese and Chinese has one Unicode code point and two correct printed forms. A Japanese document set in a Simplified Chinese font renders every character, looks fine to anyone who does not read it, and looks foreign to anyone who does. The engine picks the regional form from the language of the text, which it learns from the lang attribute, so a document with no lang attribute gets whichever variant the font defaults to. Separately, Japanese has kinsoku shori: a set of characters that may not begin a line, such as a closing bracket or a small kana, and others that may not end one. Those rules are typographic convention rather than a property of the text.

What the failure looks like

Correct-looking text that a Japanese reader describes as slightly off, which is the Han unification problem. And lines that begin with a closing bracket or a full stop, which is the kinsoku problem and is immediately visible once you know to look.

Line breaking

Japanese breaks between almost any pair of characters, because there are no word spaces. That makes wrapping easy and correctness hard: the constraint is not where you may break but where you may not, which is what kinsoku describes. line-break: strict asks the engine for the stricter set of rules where it implements them.

The markup and the CSS

The language attribute is doing real typographic work in every one of these examples. It is what lets the engine choose a regional glyph form, a case mapping or a hyphenation dictionary, and leaving it off is the most common single omission in a multilingual document.

html
<!-- lang is what selects the Japanese glyph forms. Without it a font
     covering both may hand you the Chinese ones. -->
<p lang="ja" class="japanese">請求書の準備ができました。</p>

<style>
@font-face {
  font-family: "Noto Sans JP";
  src: url("https://assets.example.com/fonts/noto-sans-jp.woff2")
       format("woff2");
}

.japanese {
  font-family: "Noto Sans JP", sans-serif;
  /* Ask for the stricter kinsoku rules where the engine has them. */
  line-break: strict;
  /* CJK glyphs fill their em box, so text set at a Latin line-height
     looks cramped. 1.7 to 1.8 is normal for body text. */
  line-height: 1.75;
}</style>

The mistake people make

Using one CJK font for Japanese, Simplified Chinese and Traditional Chinese because it covers all three. It does cover all three, and it will render one of them in another's glyph forms unless the language is declared on the element. The lang attribute is doing real typographic work here, not accessibility work.

How to test this without reading the language

Render a string you know is correct, and compare it against a reference rendering from a system that definitely handles the script, such as a browser on a machine with the language installed. Comparing against nothing tells you only that characters appeared, and for most of the scripts on this site characters appearing is not the same as text being right. Where you can, get one native reader to look at one page once; it is faster than any amount of specification reading.

Frequently asked

Why does Japanese look wrong when every character is there?

Correct-looking text that a Japanese reader describes as slightly off, which is the Han unification problem. And lines that begin with a closing bracket or a full stop, which is the kinsoku problem and is immediately visible once you know to look. The underlying reason is that a font is two things: a set of glyphs and a set of layout tables that say how those glyphs combine and position. Character coverage is the first half. Most of the failures on this page are the second half.

Does the lang attribute actually change the rendering?

Yes, and it is not an accessibility nicety here. It selects regional glyph forms where a code point has more than one correct shape, it picks the case mapping for a text transform, it chooses a hyphenation dictionary, and it triggers the locl OpenType feature. A document with no lang attribute gets whatever the font defaults to, which is usually right for one language and wrong for the others.

Can I subset the font to keep the file small?

Carefully, and not with a tool configured for Latin. Subsetters routinely drop the GSUB and GPOS tables that shape and position glyphs, because for Latin those are largely optional. The subset renders without error and every complex feature in it is broken. Test a subset by rendering real text and looking at it, never by checking that the file got smaller.

Related writing systems

Scripts with a related mechanism, and one from each of the other families.

Paste a real string in this script into the playground and compare the output against a reference. It is the only test that means anything if you do not read the language.