Matterhorn 31-018 · PDF/UA-1 UA1:7.21.6-2
Glyph in the font but unreachable through its cmap
Some rendered glyph is unreachable through the declared cmap tables.
The condition, as the protocol states it. “A non-symbolic TrueType font is used for rendering, but for at least one glyph to be rendered the glyph cannot be looked up by any of the non-symbolic cmap entries in the embedded font program.”
Matterhorn Protocol 1.1, checkpoint 31, index 31-018, section UA1:7.21.6-2. Software can decide this one on its own.
What a screen reader actually does
Depending on the viewer, the character may be drawn as a blank or a box while extraction still delivers it through the encoding: the screen reader hears a character the page may not show.
What the condition is
A non-symbolic TrueType font with a standard encoding finds each glyph in two steps. The encoding turns the character code into a character — in WinAnsiEncoding, code 67 is C, U+0043 — and the font’s own cmap turns the character into a glyph, through its (3,1) Unicode subtable or its (1,0) Mac Roman subtable. PDF/UA-1 requires every glyph the text draws to be reachable that way. This condition is a code for which the second step finds nothing.
It sits between two neighbours. If the font has no (3,1) or (1,0) subtable at all, that is 31-017. If the glyph is not in the program at all, that is 31-011. Here the subtable exists, the glyph exists, and the subtable has no entry pointing at it.
The file Taggart’s test suite uses for this page:
content (ABC) Tj % codes 65, 66, 67
font dictionary /Encoding /WinAnsiEncoding /Flags 32
program glyphs .notdef A B C
program cmap (3,1) U+0041 → A U+0042 → B % no U+0043
post table format 3 % no glyph names
What breaks, and what a screen-reader user hears
On the page, it depends on the viewer. Poppler draws A, B and a blank. Give the same font glyph names in its post table and Poppler finds C by name and draws it; take them away and it has nothing left to try. Other viewers have their own fallbacks, or none. The lookup the standard defines has failed either way — the condition exists precisely so that what appears does not depend on which reader’s improvisation you get.
In the text, the character is there. Extraction never consults the cmap: it goes from the code through the encoding, and WinAnsiEncoding says 67 is C. pdftotext returns ABC for the test file. The screen-reader user hears three letters while a sighted reader, in Poppler, sees two and a gap.
So this is not a character that disappears. It is a character that exists for one audience and, depending on the software, not for the other — and since a sighted reviewer only sees the half their viewer shows, it survives review in any viewer with a good fallback.
Where it comes from
A cmap built for the wrong encoding. WinAnsi and Latin-1 agree on almost everything except codes 128 to 159, where WinAnsi has curly quotes, dashes, the euro sign and ellipsis and Latin-1 has control characters. A producer that builds the font’s (3,1) table as if its codes were Latin-1 maps code 0x93 to U+0093 instead of to the left curly quote, U+201C. Under WinAnsi the quote is looked up as U+201C, which the table does not list. The letters render; the typographic punctuation does not, in any viewer without a fallback.
A subsetter that prunes the cmap separately from the glyphs. A glyph kept because something else needs it — a component of an accented letter, or a glyph reached by name — while its cmap entry is dropped as unused, is in the program and unreachable.
How to check it free
Copy what you cannot see. A blank or box where a character should be, which pastes as the right character into a plain text editor, is the signature. Open the file in a second viewer too: a character that shows in one and not the other points at a failed lookup and a fallback.
veraPDF has no rule of its own for this clause that tests the cmap. Its rule for missing glyphs, under clause 7.21.4.1, is worded to cover TrueType codes that cannot be mapped to a glyph by the method ISO 32000-1 describes without a processor’s own improvised mapping, which is this condition.
Acrobat Pro’s Preflight inventory shows the glyphs embedded in each font and which character each is supposed to represent — Adobe’s page on Preflight reports suggests it for resolving missing-glyph errors. Select Use print production → Preflight from the All tools menu, then Options → Create Inventory. A glyph that is in the inventory but missing from the page in the viewer where you saw the gap is this condition rather than 31-011.
List the unreachable codes. For a WinAnsi TrueType font, a short script with the free Python libraries pikepdf and fontTools checks every code the font declares a width for against its (3,1) subtable:
import io, sys
import pikepdf
from fontTools.ttLib import TTFont
with pikepdf.open(sys.argv[1]) as pdf:
for font in pdf.objects:
if not (isinstance(font, pikepdf.Dictionary) and font.get("/Subtype") == "/TrueType"
and font.get("/Encoding") == "/WinAnsiEncoding"
and "/FontFile2" in font.FontDescriptor):
continue
tt = TTFont(io.BytesIO(bytes(font.FontDescriptor.FontFile2.read_bytes())))
table = tt["cmap"].getcmap(3, 1)
reachable = table.cmap if table else {}
for code in range(int(font.FirstChar), int(font.LastChar) + 1):
char = bytes([code]).decode("cp1252", "replace")
if ord(char) not in reachable:
print(font.BaseFont, f"code {code:#04x} ({char}) has no (3,1) entry")
On the test file it prints /BatchJCmapGap code 0x43 (C) has no (3,1) entry. It does not look at a (1,0) subtable, so check that separately if the font has one.
How to fix it free
Re-export from the source. The font is then embedded with a cmap built from the same text as the subset.
Acrobat cannot edit a font program. Adobe’s guide to advanced preflight inspections says you can navigate fonts in its browser but not edit them, and none of the fixups Adobe documents rebuilds a cmap.
A corrected cmap can be written with fontTools — add the missing entries to the (3,1) subtable and embed the rebuilt program in place of the old one — but that is surgery on a font, and the glyph each new entry points at has to be identified by someone who can see the outlines. For a cmap built for Latin-1, the fix is mechanical: map each WinAnsi character in 128–159 to the glyph the Latin-1 entry pointed at.
What Taggart does
Taggart does not detect this condition. It does not open embedded font programs, so it never sees the cmap or the glyph list. On the test file it reports nothing about the font. It does not treat that silence as a pass: 31-018 is on the list of conditions Taggart does not check, and the conformance claim waits until a person confirms them. Check with the script above or veraPDF before you do.
How Taggart handles it
Software could decide this one, and Taggart does not check it yet. A clean report from Taggart says nothing about it either way. It is on the list of conditions Taggart asks you to confirm before it writes the PDF/UA claim into the file; check it by hand or with another validator, as described above.
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
Why does the character show in one viewer and not another?
Because once the cmap lookup fails, every viewer is on its own. Poppler, for one, tries the glyph names in the font's post table and finds the glyph if they are there. A viewer without that fallback, or a font without those names, draws .notdef. The standard's lookup has already failed in both cases; only the improvisation differs.
How is this different from 31-011?
In 31-011 the glyph is not in the program at all. Here it is, and nothing in the font's non-symbolic cmap leads to it. The repair differs too: a missing glyph needs a fuller font, an unreachable one needs a corrected cmap, and both are usually easiest to get by re-exporting.
Why do the screen reader and the page disagree?
Because they take different routes. The screen reader's text comes from the code through the encoding or ToUnicode CMap, which never touches the cmap. The page's glyph comes from the code through the cmap. Break the second route and the first still delivers the character.
Where this sits in the standards
| Standard | Reference |
|---|---|
| Matterhorn Protocol 1.1 | Checkpoint 31 (Fonts), index 31-018 |
| PDF/UA-1 (ISO 14289-1) | Clause 7.21.6-2 |
| WCAG 2.1 | 1.3.1 Info and Relationships — Level A |
| EN 301 549 / Section 508 / ADA Title II | All 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. |
Related conditions
- 31-017 A non-symbolic TrueType font has no non-symbolic cmapA font declared non-symbolic must actually carry a (3,1) or (1,0) cmap subtable.
- 31-011 The embedded font program does not contain glyphs for all referenced glyphsThe subset embedded in the file is missing glyphs the content stream asks for — very common when a document is edited after subsetting.
- 31-030 Characters used in text showing operators reference the .notdef glyphThe content stream paints glyph index 0, which is by definition "no glyph" — typically a hollow box or nothing at all.
- 31-019 The font dictionary for a non-symbolic TrueType font does not contain an Encoding entryNon-symbolic TrueType fonts must declare an /Encoding.
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.
- 31-001 CIDSystemInfo Registry values are not identicalThe CMap and the descendant CID font must agree on the character collection registry.
- 31-002 CIDSystemInfo Ordering values are not identicalThe CMap and the descendant CID font must agree on the character collection ordering.
- 31-003 CIDSystemInfo Supplement of the CID font is lower than that of the CMapThe font's character collection supplement must be at least as new as the CMap's, or the CMap may reference CIDs the font does not define.
- 31-004 A Type 2 CID font contains neither a stream nor the name Identity as CIDToGIDMapCIDToGIDMap must be either the name /Identity or a stream mapping CIDs to glyph indices.
- 31-005 A Type 2 CID font does not contain a CIDToGIDMap entryThe entry is absent entirely.
- 31-006 A CMap is neither listed in ISO 32000-1 Table 118 nor embeddedA predefined CMap must be one of the standard names; anything else must be embedded in the file.
- 31-007 The WMode entry in a CMap dictionary is not identical to the WMode value in the CMap streamWriting mode (horizontal or vertical) must be declared consistently in the dictionary and the stream.
- 31-008 A CMap references another CMap which is not listed in ISO 32000-1 Table 118A UseCMap chain must terminate at a predefined CMap.
- 31-009 For a font used by text intended to be rendered the font program is not embeddedPDF/UA-1 requires every font used for visible text to be embedded, so the document renders and extracts identically everywhere. A glyph counts as rendered if the text render mode is not 3.
- 31-010 A font program is embedded that is not legally embeddable for unlimited, universal renderingThe font's embedding permission bits forbid the kind of embedding used. Machines can read the flags but the flags may not reflect the actual licence.
- 31-011 The embedded font program does not contain glyphs for all referenced glyphsThe subset embedded in the file is missing glyphs the content stream asks for — very common when a document is edited after subsetting.
- 31-012 A glyph present in an embedded Type 1 font program is not listed in CharSetCharSet must accurately list the glyphs in the embedded subset.
- 31-013 A glyph listed in CharSet is not present in the Type 1 font programCharSet promises glyphs the font does not contain.
- 31-014 A glyph present in an embedded CID font program is not listed in CIDSetCIDSet must accurately list the CIDs in the embedded subset.
- 31-015 A glyph listed in CIDSet is not present in the CID font programCIDSet promises CIDs the font does not contain.
- 31-016 Glyph widths in the font dictionary and the font program differThe /Widths array must agree with the embedded font program.
- 31-017 A non-symbolic TrueType font has no non-symbolic cmapA font declared non-symbolic must actually carry a (3,1) or (1,0) cmap subtable.
- 31-019 The font dictionary for a non-symbolic TrueType font does not contain an Encoding entryNon-symbolic TrueType fonts must declare an /Encoding.
- 31-020 The Encoding dictionary for a non-symbolic TrueType font does not contain BaseEncodingIf /Encoding is a dictionary it must name a /BaseEncoding.
- 31-021 BaseEncoding is neither MacRomanEncoding nor WinAnsiEncodingOnly MacRomanEncoding and WinAnsiEncoding are permitted for non-symbolic TrueType fonts.
- 31-022 Differences contains glyph names not listed in the Adobe Glyph ListGlyph names in /Differences must be resolvable through the Adobe Glyph List so a processor can derive Unicode.
- 31-023 Differences present but the font has no (3,1) Microsoft Unicode cmapThe Differences array is resolved through the (3,1) cmap; without it the mapping is undefined.
- 31-024 The Encoding entry is present in the font dictionary for a symbolic TrueType fontSymbolic TrueType fonts must not carry an /Encoding; the font's own cmap governs.
- 31-025 The embedded font program for a symbolic TrueType font contains no cmapA symbolic TrueType font must contain at least one cmap subtable.
- 31-026 A symbolic TrueType font has multiple cmaps but none is a (3,0) Microsoft Symbol cmapWith multiple cmaps present, a (3,0) symbol cmap is required to disambiguate.
- 31-027 A font dictionary does not contain the ToUnicode entry and no exemption appliesA ToUnicode CMap is required unless the font uses MacRoman/MacExpert/WinAnsi encoding, or is a Type 1/Type 3 whose glyph names are all in the Adobe Glyph List or the Symbol set, or is a Type 0 whose descendant uses a standard Adobe character collection, or is a non-symbolic TrueType.
- 31-028 Unicode values in the ToUnicode CMap are zeroU+0000 is not a valid mapping target; it usually means the producer could not determine the character and wrote a placeholder.
- 31-029 Unicode values in the ToUnicode CMap are U+FEFF or U+FFFEByte-order marks are not characters and must not appear as mapping targets.
- 31-030 Characters used in text showing operators reference the .notdef glyphThe content stream paints glyph index 0, which is by definition "no glyph" — typically a hollow box or nothing at all.
Check your own file. Taggart does not check this condition yet, and lists it for you to confirm. Its validator is free and checks 75 of the 87 Matterhorn conditions software can decide — no watermark, no expiry, and no account needed up to 60 pages.
Taggart does not check this condition yet. A generated file that fails it is in the repository, with a test that fails the day Taggart starts catching it. Written on . The Acrobat steps follow Adobe’s documentation and were not walked through by hand. Last updated .