Matterhorn 31-019 · PDF/UA-1 UA1:7.21.6-3

TrueType font with no /Encoding: every reader guesses

BlockerCheckpoint 31: FontsWCAG 1.3.1 (A)

Non-symbolic TrueType fonts must declare an /Encoding.

The condition, as the protocol states it. “The font dictionary for a non-symbolic TrueType font does not contain an Encoding entry.”

Matterhorn Protocol 1.1, checkpoint 31, index 31-019, section UA1:7.21.6-3. Software can decide this one on its own.

What a screen reader actually does

Character codes cannot be reliably mapped to characters.

What the entry is for

A TrueType font in a PDF is a simple font: each byte in a text string is one character code, 0 to 255. Two things need a route from that code. The viewer needs a glyph to draw, and anything reading the text — a screen reader, search, copy and paste — needs a character.

For a font whose descriptor says it is non-symbolic (the Nonsymbolic flag, value 32, in /Flags), PDF/UA-1 fixes both routes by making the font name its encoding: /WinAnsiEncoding or /MacRomanEncoding, either as the value of /Encoding or as the /BaseEncoding of an encoding dictionary. Those two are separate conditions, 31-020 and 31-021. This one is the step before them: no /Encoding at all.

12 0 obj
<< /Type /Font
   /Subtype /TrueType
   /BaseFont /BatchJNoEncoding
   /FirstChar 65 /LastChar 67
   /Widths [ 600 600 600 ]
   /FontDescriptor 13 0 R      % /Flags 32 — non-symbolic
>>                             % … and no /Encoding
endobj

That is the font in the file Taggart’s test suite uses for this page. Its program is embedded and its (3,1) Unicode cmap is correct. The only thing missing is the line that says what its codes mean.

Why naming it matters

With /WinAnsiEncoding the chain is defined end to end. Code 0x92 is the glyph name quoteright, which is U+2019, which the viewer looks up in the font’s Unicode cmap and which an extractor hands to the screen reader as an apostrophe.

Without it, ISO 32000-1 (9.6.6.4) treats the font the way it treats a symbolic one: the code goes straight into the font’s (3,0) or (1,0) cmap subtable. A font built for ordinary text often has neither — it has (3,1) — and then the standard gives the viewer nothing to go on. Every viewer falls back on something of its own choosing, and so does every text extractor.

They mostly choose the same thing for ASCII, which is why this survives. Above 127 the encodings disagree:

CodeWinAnsiMacRoman
0x92’í
0x96–ñ
0xE9éÈ
0xD5Õ’

What a screen-reader user hears

Whatever the PDF library under the screen reader guessed. On plain ASCII text that is the right thing. On a word like don’t, set by a producer that used WinAnsi codes, a reader that guesses MacRoman gets donít — and says it. An en dash becomes ñ. Nothing on the page looks wrong, so nobody checking it by eye finds out.

The guesses are visible if you know where to look. Poppler’s pdffonts lists the encoding of the test file’s font as WinAnsi. The file contains no such entry; that column is Poppler’s assumption, printed as if it were a fact. Acrobat’s Fonts tab (File → Properties) is no better a witness: Adobe describes it as listing the encoding used to display the fonts, which is the viewer’s choice rather than the file’s declaration.

Where it comes from

A producer that writes every embedded TrueType font the way a symbolic font is written — no /Encoding, codes handed straight to the font — and then sets the Nonsymbolic flag in the descriptor. Either half is legitimate on its own. Together they contradict each other, because the flag decides which rules apply: a symbolic font must not carry /Encoding (31-024), a non-symbolic one must.

The same mismatch appears when a tool rewrites descriptor flags after the fact, or when a font dictionary is assembled by hand in a script that copies the flags from one font and the dictionary layout from another.

How to check it free

veraPDF fails it under clause 7.21.6. Its own rule number is 7.21.6-2, and the message talks about the font’s encoding not defining a correct mapping to the Adobe Glyph List — not obviously the same thing, but the rule’s test fails any non-symbolic TrueType font whose /Encoding is not MacRomanEncoding or WinAnsiEncoding, and a missing one is not either of those.

Acrobat Pro can show you the font dictionary, though not change it. Following Adobe’s guide to advanced preflight inspections: select Use print production → Preflight from the All tools menu, then in the Preflight dialog box select Options → Browse Internal PDF Structure. Expand the tree down to the page’s font resources and open the TrueType font. If there is no Encoding key beside FontDescriptor, that is this condition.

Copy a word with a character above 127 and paste it into a plain text editor. A wrong character is the symptom; a right one tells you only that this reader guessed well.

How to fix it free

Re-export from the source document, and check the new file’s font dictionaries the same way. A producer that wrote this once will write it again with the same settings, so try a different export route if the first does not change.

Acrobat cannot add the entry. Adobe’s page for the structure browser says you can navigate the PDF and fonts in those windows but cannot edit them, and no Preflight fixup Adobe documents sets a font’s encoding.

A short script can, once you have decided which encoding is true. With the free Python library pikepdf:

import pikepdf

with pikepdf.open("in.pdf") as pdf:
    for obj in pdf.objects:
        if not isinstance(obj, pikepdf.Dictionary):
            continue
        fd = obj.get("/FontDescriptor")
        if (obj.get("/Subtype") == "/TrueType" and "/Encoding" not in obj
                and fd is not None and not int(fd.get("/Flags", 0)) & 4):
            obj.Encoding = pikepdf.Name.WinAnsiEncoding
    pdf.save("out.pdf")

It adds /WinAnsiEncoding to every TrueType font that is not flagged symbolic and has no encoding. Before you trust the result, copy a word containing a character above 127 from the new file, and compare the page with the old one: an encoding changes which glyph a viewer looks up as well as what the text reads as.

What Taggart does

Taggart reports every non-symbolic TrueType font that some text is set in and that has no /Encoding, with the page it first appears on and its object number. On the test file:

/BatchJNoEncoding is a non-symbolic TrueType font whose font dictionary has no /Encoding entry.

It is a blocker and there is no automatic repair. Adding the entry, as the script above does, clears it; the test suite checks that too.

Three details of how it decides:

  • Non-symbolic is read from the descriptor’s Flags. A font counts as non-symbolic unless its Symbolic flag is set and its Nonsymbolic flag is not. A font with no Flags at all, or Flags of 0, is not classified, and this condition is not reported for it.
  • It stands in for the rest. Once there is no encoding there is nothing to check a base encoding or a Differences array against, so 31-020 to 31-022 are skipped for that font. And because PDF/UA exempts non-symbolic TrueType fonts from needing a ToUnicode CMap, so does Taggart: the test file’s font, with no encoding and no ToUnicode, gets this finding and no other — under an exemption that only works if the encoding this condition asks for is there.
  • Taggart guesses too. The text reader behind its screen-reader preview decodes a single-byte font with no encoding and no ToUnicode as Latin-1. For ASCII that is right. For 0x92 it produces a control character, not an apostrophe — one more reader making a choice the file should have made for it.

How Taggart handles it

Taggart checks for this condition automatically and reports it as a blocker, with where in the file it found it.

Whatever Taggart changes, the page still looks identical. Fixes edit the PDF object model, not the content streams, and every remediation is re-rendered and compared against the original at structural similarity ≥ 0.999 before you get the file back. A tool that quietly reflows your document is worse than no tool.

Frequently asked questions

The text copies and pastes correctly. Is this a real problem?

For plain ASCII it usually is not, yet. Codes 32 to 126 mean the same letters in WinAnsi and MacRoman, so every guess lands on the same character. The guesses part company above 127 — curly quotes, dashes, accented letters — and the file fails PDF/UA either way, because the standard asks the font to say which encoding it uses rather than leave it to the reader.

Which encoding should I add, WinAnsi or MacRoman?

The one the producer used when it chose the codes. Find a character above 127 in the document — a curly apostrophe, an en dash, an accented letter — add an encoding, and copy that word. If it pastes correctly, you picked the right one. If the document is pure ASCII, either is correct and WinAnsiEncoding is the usual choice.

Why must a symbolic TrueType font not have /Encoding when this one must?

Because the two kinds of font are looked up differently. A symbolic font's codes go straight into its own (3,0) cmap, so PDF/UA forbids an /Encoding that would contradict it (31-024). A non-symbolic font is looked up by character, and the encoding is what says which character a code is. The descriptor's Flags decide which rule applies, so a font with the wrong flag fails the other condition.

Why doesn't Taggart just add WinAnsiEncoding?

Because it is a statement about what every code in the font means, and for codes above 127 it can be wrong. A wrong encoding extracts cleanly and says the wrong thing, which is harder to notice than a missing one. Taggart reports the font and leaves the choice to someone who can check the text.

Where this sits in the standards

Standards this condition maps to
StandardReference
Matterhorn Protocol 1.1Checkpoint 31 (Fonts), index 31-019
PDF/UA-1 (ISO 14289-1)Clause 7.21.6-3
WCAG 2.11.3.1 Info and Relationships — Level A
EN 301 549 / Section 508 / ADA Title IIAll three point at WCAG 2.1 Level AA for non-web documents, so a Level A or AA criterion here is in scope for each of them.

Other conditions in checkpoint 31

Checkpoint 31 covers fonts. These are its other failure conditions; the ones without a link are in the rule set but do not have a written page yet.

Check your own file. Taggart’s validator is free and unlimited — 75 of the 87 Matterhorn conditions software can decide, with the other 12 listed for you to confirm; no watermark, no expiry, and no account needed up to 60 pages.

Check a PDF free

Checked with Taggart on a generated file that fails this condition on . The Acrobat steps follow Adobe’s documentation and were not walked through by hand. Last updated .