Matterhorn 31-017 · PDF/UA-1 UA1:7.21.6-1
Non-symbolic TrueType font with only a symbol cmap
A font declared non-symbolic must actually carry a (3,1) or (1,0) cmap subtable.
The condition, as the protocol states it. “A non-symbolic TrueType font is used for rendering, but none of the cmap entries in the embedded font program is a non-symbolic cmap.”
Matterhorn Protocol 1.1, checkpoint 31, index 31-017, section UA1:7.21.6-1. Software can decide this one on its own.
What a screen reader actually does
Which glyph is drawn is left to each viewer's own fallback. The screen reader reads what the encoding says, so a symbol font described as text is read as letters where the page shows symbols.
What the condition is
A TrueType font program finds glyphs through its cmap table, which can hold several subtables, each identified by a platform and an encoding number. Three matter in PDF:
- (3,1), Microsoft Unicode: characters to glyphs.
- (1,0), Macintosh Roman: Mac character codes to glyphs.
- (3,0), Microsoft Symbol: the table symbol fonts use, mapping glyphs into a private-use range, U+F020 to U+F0FF, that means nothing outside the font.
For a font whose descriptor says it is non-symbolic, the lookup ISO 32000-1 describes runs through the first two. The code is turned into a glyph name by the font’s encoding, the name into a character, and the character is looked up in (3,1) — or into a Mac code and looked up in (1,0). PDF/UA-1 requires a non-symbolic TrueType font’s program to have at least one of those. This condition is a program that has neither.
The file Taggart’s test suite uses for this page is declared non-symbolic and WinAnsi-encoded, and its program has one subtable:
font dictionary /Subtype /TrueType /Encoding /WinAnsiEncoding
descriptor /Flags 32 % non-symbolic
program cmap (3,0) U+F041 → A U+F042 → B U+F043 → C
There is no (3,1) for the WinAnsi character A to be looked up in.
What breaks
Drawing becomes the viewer’s guess. Poppler draws the test file correctly: it falls back to the symbol table and finds A at U+F041. That is a reasonable fallback, but it is not the lookup the standard defines, so another viewer is free to do something else — including drawing .notdef for every character in the font.
The text and the glyphs can mean different things. This is the serious case, and it is what a (3,0)-only font usually is: a symbol font described as a text font. The encoding is what a screen reader trusts, and it says WinAnsi. Take Wingdings, whose check mark sits at code 0xFC. Described as non-symbolic with WinAnsiEncoding, that code is the letter ü as far as every reader is concerned. The page shows a tick; the screen reader reads ü.
What a screen-reader user hears
Whatever the encoding says, regardless of what is drawn. In the test file, whose glyphs really are A, B and C, pdftotext returns ABC and that is correct. In a real symbol font flagged this way, the user hears letters where the page shows bullets, ticks, arrows or boxes — fluent, confident, and wrong, which is harder to catch than silence.
Where it comes from
A symbol font written as a text font. A producer that sets the Nonsymbolic flag and a WinAnsi encoding on every TrueType font it embeds will do it to Wingdings, Symbol and icon fonts too. Bullets and check boxes set in symbol fonts are where it shows.
A font converted or rebuilt without its Unicode table. A tool that regenerates a font program and keeps only one cmap subtable can drop the one the PDF needs.
How to check it free
veraPDF reports it under clause 7.21.6. Its test fails a non-symbolic TrueType program that has no cmap subtable other than (3,0).
List the subtables. Acrobat has no documented view of a font’s cmap subtables, so this needs a script. With the free Python libraries pikepdf and fontTools:
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"):
continue
fd = font.get("/FontDescriptor")
if fd is None or "/FontFile2" not in fd:
continue
tt = TTFont(io.BytesIO(bytes(fd.FontFile2.read_bytes())))
tables = sorted((t.platformID, t.platEncID) for t in tt["cmap"].tables)
symbolic = bool(int(fd.get("/Flags", 0)) & 4)
print(font.BaseFont, "symbolic" if symbolic else "non-symbolic", tables)
On the test file it prints /BatchJSymbolCmap non-symbolic [(3, 0)]. A non-symbolic font with no (3, 1) and no (1, 0) in its list is this condition.
Copy a symbol. Select a bullet or a check box and paste it into a plain text editor. A letter where there should be a symbol means the encoding is claiming the font is text.
How to fix it free
Re-export from the source. If the symbols came from a symbol font, the export should describe it as one; if it does not, replacing those characters in the source with their Unicode equivalents from a text font — • for a bullet, ✓ for a tick — fixes this and gives the screen reader the right word at the same time.
Acrobat cannot change it. Adobe’s guide to advanced preflight inspections says its font and structure browsers are for navigating, not editing, and Adobe documents no Preflight fixup that rewrites a font’s cmap or flags.
Re-describing the font by hand — Symbolic flag, no /Encoding, a ToUnicode CMap saying what each symbol is — is possible with a script, but every piece of it is a separate condition (31-024, 31-027), and the last one is a judgement about meaning. That is a remediation job, not a one-liner.
What Taggart does
Taggart does not detect this condition. It reads font dictionaries and flags but does not open embedded font programs, so it never sees the cmap table. On the test file it reports nothing about the font, and the condition goes on the list of those Taggart asks a person to confirm before it writes a PDF/UA claim.
What it reports under 31-017 is a narrower case: a non-symbolic TrueType font with no embedded program, whose cmap therefore cannot be examined at all.
/BatchJSymbolCmap is a non-symbolic TrueType font with no embedded font program, so its cmap tables cannot be verified.
That font is also reported under 31-009, which is the finding to act on; once the program is embedded, this condition is about what is in it. For an embedded program, use veraPDF or the script above before you confirm it.
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
The page renders correctly. What is actually wrong?
The route from code to glyph. The standard's lookup for a non-symbolic font goes through a (3,1) or (1,0) cmap, and this font has neither, so whatever the viewer drew it found by a route of its own. Poppler finds it through the symbol table; nothing obliges another viewer to, and one that does not draws .notdef instead.
What are (3,1), (1,0) and (3,0)?
Platform and encoding numbers of the subtables in a TrueType font's cmap table. (3,1) is Microsoft Unicode, (1,0) is Macintosh Roman, and (3,0) is Microsoft Symbol — the table symbol fonts use, which maps their glyphs into a private-use range rather than to real characters.
Should I just mark the font as symbolic?
Only if it is one, and then the rest has to follow: a symbolic TrueType font must not carry /Encoding (31-024), and it needs a ToUnicode CMap to say what each symbol means (31-027). Writing that CMap is a statement about meaning that a person has to make; a re-export from the source is usually less work and more reliable.
Where this sits in the standards
| Standard | Reference |
|---|---|
| Matterhorn Protocol 1.1 | Checkpoint 31 (Fonts), index 31-017 |
| PDF/UA-1 (ISO 14289-1) | Clause 7.21.6-1 |
| 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-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-025 The embedded font program for a symbolic TrueType font contains no cmapA symbolic TrueType font must contain at least one cmap subtable.
- 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-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.
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-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-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 .