Matterhorn 31-023 · PDF/UA-1 UA1:7.21.6-7
No (3,1) cmap behind a TrueType Differences array
The Differences array is resolved through the (3,1) cmap; without it the mapping is undefined.
The condition, as the protocol states it. “The Differences array is present in the Encoding entry in a non-symbolic TrueType font dictionary but the embedded font program does not contain a (3,1) Microsoft Unicode cmap.”
Matterhorn Protocol 1.1, checkpoint 31, index 31-023, section UA1:7.21.6-7. Software can decide this one on its own.
What a screen reader actually does
Characters extract incorrectly.
The lookup this condition protects
A non-symbolic TrueType font with a /Differences array reaches its glyphs in three steps:
- The byte in the content stream becomes a glyph name, through the encoding and its Differences.
- The glyph name becomes a Unicode value, through the Adobe Glyph List:
eacuteis U+00E9. - The Unicode value becomes a glyph, through the font program’s (3,1) cmap subtable: platform 3, Microsoft; encoding 1, Unicode.
Step 3 is inside the embedded font program, not in the PDF’s dictionaries. A TrueType program’s cmap table can hold several subtables, each mapping some kind of code to glyph numbers, and fonts from different eras carry different sets. PDF/UA-1 clause 7.21.6 requires that a non-symbolic TrueType font with Differences carries the one step 3 needs. Condition 31-023 is a program without it.
Here is the cmap table of the test file we built for this page, as fontTools prints it:
<cmap>
<tableVersion version="0"/>
<cmap_format_6 platformID="1" platEncID="0" language="0">
<map code="0x41" name="A"/>
<map code="0x42" name="B"/>
</cmap_format_6>
</cmap>
One subtable, (1,0), the Macintosh Roman one. No (3,1).
What breaks
ISO 32000-1 gives the viewer a fallback: with no (3,1) subtable but a (1,0), turn the glyph name back into a Mac Roman byte and look that up instead. That works for names Mac Roman has a code for and fails for every other — a Hebrew letter, a Polish ł, most mathematical symbols. After that the viewer may look the name up in the program’s own glyph names, and after that it may do whatever it likes.
On our test file, whose names are all plain Latin letters, Poppler and PDFium both drew the right glyphs through the fallback. That is the usual outcome for Latin text, and it is why this condition survives review. It stops being the outcome as soon as the text goes outside Mac Roman.
What the user hears
For a screen-reader user, this is often silent. Text extraction from a non-symbolic TrueType font, done the way ISO 32000-1 describes, runs through steps 1 and 2 only: if the Differences names are in the Adobe Glyph List, the reader gets the right characters without ever looking inside the program. The failure is on the drawing side, so the cost lands on whoever is reading the page rather than listening to it — and, worst, on the person comparing the two: a low-vision user following the text with magnification while speech reads it out can be shown one character and told another.
Where it comes from
Subsetting tools keep the cmap subtables they think they need and drop the rest. One that keeps only the Macintosh subtable, or rebuilds the cmap with only a (1,0) subtable, produces this whenever the font also gets a Differences array. Older fonts converted from Macintosh sources can lack a Windows subtable from the start.
How to check it free
You need the font program out of the PDF and a tool that reads TrueType tables. Both are free.
- Find the font’s object number with
pdffonts file.pdf(last column), thenqpdf --show-object=12 file.pdfand follow/FontDescriptorto its/FontFile2entry. - Write the program out:
qpdf --show-object=19 --filtered-stream-data file.pdf > font.ttf. The qpdf manual documents both options. - With fontTools installed,
ttx -t cmap font.ttfwritesfont.ttx. Every subtable is listed with itsplatformIDandplatEncID. You are looking forplatformID="3" platEncID="1".
veraPDF checks this as part of its PDF/UA-1 rule for non-symbolic TrueType encodings, which includes the requirement for a (3,1) subtable when the program is embedded.
Acrobat Pro has a font browser — All tools → Use print production → Preflight, then Options → Browse Internal Structure of All Document Fonts — which Adobe’s advanced preflight inspections page describes as showing glyph outlines and coordinates. Adobe does not document a view of the cmap table in it, and notes that you cannot edit fonts there.
How to fix it free
Re-export from the source. A current application embedding a current font usually writes a program with a Windows Unicode subtable. If the font is the problem, Adobe’s advice for character-encoding problems in Create and verify PDF accessibility is to switch to a different font, preferably OpenType, and re-create the PDF.
Or add the subtable. If the program has a (1,0) subtable, the (3,1) one can be built from it, because Mac Roman says what Unicode character each of its bytes is. With fontTools:
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
font = TTFont("font.ttf")
mac = font["cmap"].getcmap(1, 0)
win = CmapSubtable.newSubtable(4)
win.platformID, win.platEncID, win.language = 3, 1, 0
win.cmap = {ord(bytes([code]).decode("mac_roman")): glyph
for code, glyph in mac.cmap.items()}
font["cmap"].tables.append(win)
font.save("font-fixed.ttf")
Then put the program back, here with pikepdf, using the /FontFile2 object number from step 1:
import pikepdf
pdf = pikepdf.open("in.pdf")
program = pdf.get_object(19, 0)
data = open("font-fixed.ttf", "rb").read()
program.write(data)
program.Length1 = len(data)
pdf.save("out.pdf")
On our test file this added the subtable and left the page as Poppler renders it byte-for-byte identical. Check yours in more than one viewer: viewers that were using the fallback now use the new subtable, and they agree only if the two map each character to the same glyph, which a subtable built this way does.
What Taggart does, and doesn’t
Taggart reads the PDF’s font dictionaries; it does not parse embedded font programs. So it reports this condition only in the one case it can see from the dictionary — a non-symbolic TrueType font with Differences whose program is not embedded at all:
/Calibri uses a /Differences array but its font program is not embedded, so the required (3,1) Microsoft Unicode cmap cannot be verified to exist.
That file also gets 31-009 for the missing program, which is the finding to act on.
When the program is embedded and has no (3,1) subtable — the case this condition is actually about — Taggart reports nothing. We ran it on the test file above: no finding for this condition or any other font condition. A Taggart report with no 31-023 finding therefore tells you nothing about an embedded font’s cmap; use veraPDF or ttx for that. There is no repair.
It is one of the conditions Taggart does not check, and those hold the conformance claim: the PDF/UA identifier is not written until a person confirms the list — the checkbox I have checked these conditions by hand, or with another validator in the app. Run the check above before ticking it; the tick covers the whole list.
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
What is a (3,1) cmap?
A subtable of the TrueType cmap table identified by platform 3 (Microsoft) and encoding 1 (Unicode). It maps Unicode values to glyphs, which is the step a viewer needs after it has turned a Differences glyph name into a Unicode value.
Does this change what a screen reader reads?
Usually not directly. Extraction from this kind of font goes through the glyph names and the Adobe Glyph List, which do not need the cmap. What the missing subtable affects is which glyph is drawn, so the page and the text a reader receives can disagree.
Does Taggart check it?
Only when the font program is not embedded, where it reports that the subtable cannot be verified to exist. Taggart does not open embedded font programs, so an embedded font that lacks the subtable gets no finding. Use veraPDF, or fontTools as this page describes.
Where this sits in the standards
| Standard | Reference |
|---|---|
| Matterhorn Protocol 1.1 | Checkpoint 31 (Fonts), index 31-023 |
| PDF/UA-1 (ISO 14289-1) | Clause 7.21.6-7 |
| 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-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-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-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-020 The Encoding dictionary for a non-symbolic TrueType font does not contain BaseEncodingIf /Encoding is a dictionary it must name a /BaseEncoding.
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-018 A glyph cannot be looked up by any non-symbolic cmap entrySome rendered glyph is unreachable through the declared cmap tables.
- 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-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 .