European scripts / Greek
Greek text in a PDF
Greek has two accent systems, a letter whose shape changes at the end of a word, and a capitalisation rule that a CSS text transform gets wrong.
A font with the characters is not enough
Modern Greek uses the monotonic system, with one accent. Classical and liturgical texts use polytonic, with several accents plus breathings and the iota subscript, which lives in a different Unicode block and which most fonts advertising Greek support do not cover. Separately, lowercase sigma has a different form at the end of a word, and the two forms are different code points rather than a contextual alternate, so the distinction lives in your data. And Greek drops accents when text is set in capitals: an accented alpha uppercases to a plain capital alpha, not to an accented one. CSS text-transform: uppercase applies the Unicode default case mapping, which keeps the accent, unless the element declares Greek as its language, in which case the engine applies the Greek tailoring.
What the failure looks like
Polytonic text with boxes where the breathings should be, which is the coverage gap. Or a heading in capitals with accents still on it, which is the case-mapping issue and looks to a Greek reader roughly the way a heading in Latin with random letters capitalised looks to everyone else.
Line breaking
Ordinary word-space breaking. Greek hyphenation rules differ from Latin ones and need the language declared before a hyphenation dictionary can be chosen.
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.
<p lang="el" class="greek">Το τιμολόγιό σας είναι έτοιμο.</p>
<h2 lang="el" class="greek heading">ΤΙΜΟΛΟΓΙΟ</h2>
<style>
@font-face {
font-family: "Noto Sans Greek";
src: url("https://assets.example.com/fonts/noto-sans-greek.woff2")
format("woff2");
/* Greek and Coptic, plus Greek Extended for polytonic. Omitting the
second range is what leaves boxes in a classical text. */
unicode-range: U+0370-03FF, U+1F00-1FFF;
}
.greek { font-family: "Noto Sans Greek", sans-serif; }
.heading {
/* With lang="el" on the element the engine applies the Greek case
mapping and strips the accents. Without it, uppercasing keeps them,
which is wrong. The language attribute is doing the work here. */
text-transform: uppercase;
}</style>The mistake people make
Uppercasing a Greek heading in your application code with a generic toUpperCase, which keeps the accents because it applies the default Unicode mapping. Either use a locale-aware uppercase, or leave the text alone and let CSS do it on an element that declares its language.
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 Greek look wrong when every character is there?
Polytonic text with boxes where the breathings should be, which is the coverage gap. Or a heading in capitals with accents still on it, which is the case-mapping issue and looks to a Greek reader roughly the way a heading in Latin with random letters capitalised looks to everyone else. 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.
Cyrillic text in a PDF
Most Latin fonts include some Cyrillic, usually the Russian subset, and several languages written in Cyrillic need letters or letter shapes that subset does not have.
Devanagari text in a PDF
Hindi, Marathi, Nepali and Sanskrit are written in a script where the order characters are stored in is not the order they are drawn in.
Thai text in a PDF
Thai is written without spaces between words and stacks marks up to three levels above the baseline, so both line breaking and line height need deciding rather than defaulting.
Vietnamese text in a PDF
Vietnamese is Latin with two diacritics stacked on one letter, and most Latin fonts contain the characters while positioning the second mark badly or not at all.
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.
Loading a font for PDF output
The adjacent problem, covered in full there rather than repeated here.
Every writing system covered
The nine that needed a page, and the eight existing pages that cover the rest.
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.