Matterhorn 31-029 · PDF/UA-1 UA1:7.21.7-3
ToUnicode values of U+FEFF or U+FFFE: byte-order marks
Byte-order marks are not characters and must not appear as mapping targets.
The condition, as the protocol states it. “One or more Unicode values specified in the ToUnicode CMap are equal to either U+FEFF or U+FFFE.”
Matterhorn Protocol 1.1, checkpoint 31, index 31-029, section UA1:7.21.7-3. Software can decide this one on its own.
What a screen reader actually does
Those characters extract as invisible BOMs that corrupt the surrounding text.
What is in the map
ToUnicode targets are UTF-16 in big-endian byte order, with nothing in front: <0041> is A, <00E9> is é, <D835DC00> is a surrogate pair for a mathematical letter. A byte-order mark is the two-byte signature some UTF-16 text starts with to say which byte order follows — FE FF for big-endian, FF FE for little-endian. It has no business in a target, and PDF/UA-1 clause 7.21.7 forbids U+FEFF and U+FFFE as mapping values. Condition 31-029 is a map that contains one.
It comes in two shapes:
2 beginbfchar
<41> <FEFF0041> % U+FEFF, then A
<42> <FFFE4200> % U+FFFE, then U+4200
endbfchar
The first is big-endian text with a BOM in front: the right character, preceded by an invisible one. The second is worse. FF FE announces little-endian, so the bytes after it are B written the wrong way round, and read as UTF-16BE they are U+4200, a Chinese ideograph.
What the user gets
We put that map on a Helvetica font and extracted the text with Poppler’s pdftotext. The A came out as U+FEFF followed by A. The B came out as U+FFFD, the replacement character Poppler substitutes for U+FFFE, followed by 䈀.
For the first shape, the text looks right when pasted, because U+FEFF is a zero-width no-break space and draws nothing. It is still there: a search for a word containing that letter fails, word boundaries can shift, and what speech output does with it depends on the screen reader. For the second, a screen reader announces an ideograph, or nothing, where the page shows a Latin letter.
Where it comes from
One way is a producer that reuses its text-string encoder for CMap targets. PDF text strings — /Title, /Alt, /ActualText — are allowed to be UTF-16, and when they are they must begin with FE FF. A ToUnicode target is a different syntax with the opposite rule. Code that writes both through one function produces <FEFF0041>.
The little-endian shape comes from encoding to “UTF-16” with a general-purpose library call rather than to UTF-16BE specifically. In Python, "A".encode("utf-16") gives FF FE 41 00 on any little-endian machine, which is every current Mac and PC: a reversed BOM and the letter backwards, exactly the second line above.
How to check it free
Look at extracted text in hex. pdftotext file.pdf - | xxd | less and search for efbb bf (U+FEFF in UTF-8) or efbf bd (the replacement character, which is how Poppler writes U+FFFE). Unexpected CJK characters in a Latin document are the other giveaway.
Read the CMap. Find the font’s /ToUnicode object in its dictionary with qpdf --show-object= and the font’s object number from pdffonts, then print it with qpdf --show-object=14 --filtered-stream-data file.pdf. Any target containing FEFF or FFFE as one of its four-digit groups is the condition.
veraPDF’s PDF/UA-1 profile tests the Unicode value of every glyph the document shows for zero, U+FEFF and U+FFFE.
Acrobat Pro’s Accessibility Checker has a Character encoding rule; Adobe’s guidance for it in Create and verify PDF accessibility is to change the font in the source and re-create the PDF, and it does not say what the rule tests. Preflight’s Options → Browse Internal PDF Structure shows the font dictionaries but not for editing, per Adobe’s advanced preflight inspections page. Adobe says some character-encoding problems cannot be repaired within Acrobat.
How to fix it free
Fix the producer, if it is yours: write targets as UTF-16BE with no BOM. In Python that is .encode("utf-16-be").
Edit the map. The fix is mechanical once you can see it. With qpdf in QDF mode (qpdf --qdf --object-streams=disable in.pdf edit.pdf), open the CMap stream, and:
- for
<FEFF0041>, delete theFEFFso the target is<0041>; - for
<FFFE4200>, delete theFFFEand swap each remaining pair of bytes, so4200becomes0042.
Then fix-qdf edit.pdf out.pdf. Nothing on the page changes, because the CMap is only used for reading text.
Or re-export from the source with a different tool.
What Taggart does
Taggart parses every target in the map, bfchar and bfrange alike, as a sequence of UTF-16 code units, and counts the units that are FEFF or FFFE wherever they fall. One finding per font:
The ToUnicode CMap of /Helvetica maps 2 code(s) to U+FEFF or U+FFFE. Byte order marks are not characters.
The repair is tounicode-repair, the same one that handles 31-028. It is proposed rather than applied in bulk, and it does one of three things:
- Rebuilds the map from the font’s encoding, when that is WinAnsiEncoding or MacRomanEncoding. The new map replaces the old one, so every BOM goes with it. On our test file this cleared the finding.
- Takes characters from the document’s own
/ActualText, where a span around a single glyph says what that code is and every such span agrees. - Refuses where neither applies — a composite font with no
/ActualText, for example — with cannot recover a Unicode mapping, and the finding stays.
It does not do the mechanical edit described above. Stripping a BOM from a target is safe and the result is obvious, but the repair is built to write maps from evidence in the file, and does not special-case this.
How Taggart handles it
Taggart checks for this condition automatically and reports it as a blocker, with where in the file it found it.
The fix is Repair ToUnicode CMaps. It changes what the document means, so Taggart proposes it and waits for your approval rather than applying it unattended.
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 is a BOM wrong here when PDF text strings start with one?
Because they are different syntaxes. A PDF text string encoded in UTF-16 has to begin with FE FF to say so. A ToUnicode target is defined as UTF-16BE already, so the same two bytes are read as a character, U+FEFF, in front of the real one.
Will a screen reader say anything?
For U+FEFF, probably not: it is an invisible zero-width character, and whether speech skips it depends on the screen reader. Search, word boundaries and anything that compares text do not skip it. U+FFFE is not a character at all, and when it marks little-endian bytes the character after it is wrong too.
Does Taggart find a BOM in the middle of a target?
Yes. It reads every target as a sequence of UTF-16 code units and counts any unit that is FEFF or FFFE, wherever it falls in the target.
Where this sits in the standards
| Standard | Reference |
|---|---|
| Matterhorn Protocol 1.1 | Checkpoint 31 (Fonts), index 31-029 |
| PDF/UA-1 (ISO 14289-1) | Clause 7.21.7-3 |
| 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-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-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.
- 10-001 Character code cannot be mapped to UnicodeA glyph is painted whose character code has no Unicode equivalent reachable through the font's ToUnicode CMap, encoding, or glyph names. The text renders correctly but cannot be extracted.
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-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-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’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.
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 .