PDFPipe

Indic scripts / Tamil

Tamil text in a PDF

Tamil avoids the stacked conjuncts of Devanagari and instead has vowel signs that wrap around the consonant they belong to, which breaks anything that counts characters.

A font with the characters is not enough

Two features do the damage. Tamil marks a bare consonant with an explicit dot above rather than by stacking letters, so it has far fewer conjunct forms than its neighbours and is easier for a font to cover. But several of its vowel signs are two-part: a single logical vowel is drawn as one mark before the consonant and another after it, surrounding the letter. A string with one character in it occupies a position on both sides of another character, which is fine for a shaping engine and fatal for any code that slices strings, measures widths per character, or highlights a range.

What the failure looks like

Vowel signs orphaned on the wrong side of a consonant, or one half of a two-part vowel appearing without the other after a truncation. Because the surviving half is a valid glyph, the output looks like a typo rather than like a rendering failure.

Line breaking

Word spaces are used, so breaking works normally. The rule that matters is that a two-part vowel and its consonant are one unit: a break between them is not a break in a word, it is a break inside a letter.

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
<p lang="ta" class="tamil">உங்கள் விலைப்பட்டியல் தயாராக உள்ளது.</p>

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

.tamil {
  font-family: "Noto Sans Tamil", sans-serif;
  line-height: 1.7;
  /* Two-part vowels surround their consonant, so never break a run of
     Tamil at an arbitrary point to make it fit. Let it wrap or let it
     overflow visibly; do not slice it. */
  word-break: normal;
  overflow-wrap: break-word;
}</style>

The mistake people make

Truncating with an ellipsis at a fixed character count, which is exactly what a template does to fit a name into a table cell. Cut a Tamil string in the middle of a two-part vowel and you get a valid glyph in an invalid position. If a field has to be truncated, truncate on a grapheme cluster boundary rather than a code point, or let CSS do it with text-overflow, which understands the difference.

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 Tamil look wrong when every character is there?

Vowel signs orphaned on the wrong side of a consonant, or one half of a two-part vowel appearing without the other after a truncation. Because the surviving half is a valid glyph, the output looks like a typo rather than like a rendering failure. 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.