Matterhorn 31-023 · PDF/UA-1 UA1:7.21.6-7

No (3,1) cmap behind a TrueType Differences array

BlockerCheckpoint 31: FontsWCAG 1.3.1 (A)

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:

  1. The byte in the content stream becomes a glyph name, through the encoding and its Differences.
  2. The glyph name becomes a Unicode value, through the Adobe Glyph List: eacute is U+00E9.
  3. 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.

  1. Find the font’s object number with pdffonts file.pdf (last column), then qpdf --show-object=12 file.pdf and follow /FontDescriptor to its /FontFile2 entry.
  2. Write the program out: qpdf --show-object=19 --filtered-stream-data file.pdf > font.ttf. The qpdf manual documents both options.
  3. With fontTools installed, ttx -t cmap font.ttf writes font.ttx. Every subtable is listed with its platformID and platEncID. You are looking for platformID="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

Standards this condition maps to
StandardReference
Matterhorn Protocol 1.1Checkpoint 31 (Fonts), index 31-023
PDF/UA-1 (ISO 14289-1)Clause 7.21.6-7
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 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.

Check a PDF free

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 .