Matterhorn 31-016 · PDF/UA-1 UA1:7.21.5-1
Glyph widths that disagree with the embedded font
The /Widths array must agree with the embedded font program.
The condition, as the protocol states it. “For one or more glyphs, the glyph width information in the font dictionary and in the embedded font program differ by more than 1/1000 unit.”
Matterhorn Protocol 1.1, checkpoint 31, index 31-016, section UA1:7.21.5-1. Software can decide this one on its own.
What a screen reader actually does
Text positioning drifts, which corrupts geometry-based reading-order inference.
What the condition is
A glyph’s width is recorded twice in a PDF. The font program has its own advance width for every glyph — in a TrueType font, the hmtx table, in font units. The font dictionary repeats them in thousandths of an em: /Widths for a simple font, indexed from /FirstChar, or /W and /DW in the descendant of a CID font. PDF/UA-1 requires the two to agree, to within one unit.
The file Taggart’s test suite uses for this page has a TrueType font whose program advances every glyph 600 units. Its dictionary says otherwise for B:
<< /Type /Font /Subtype /TrueType
/BaseFont /BatchJWidths
/FirstChar 65 /LastChar 67
/Widths [ 600 400 600 ] % A, B, C — the program says 600, 600, 600
/Encoding /WinAnsiEncoding
/FontDescriptor … >> % /FontFile2: hmtx B = 600
Why two numbers for one thing
Because they do different jobs. A viewer positions text with the dictionary: after drawing B it moves along by B’s width from /Widths, and starts C there. It draws with the program: the shape of B comes from the outline, whatever width the outline was designed for. When the numbers agree, nobody can tell which source was used. When they don’t, both are used, for different halves of the same operation.
In Poppler’s rendering of the test file, C starts a fifth of an em early and its outline runs into B’s. The page shows A and then one joined shape.
What a screen-reader user hears
In the test file, ABC: the characters come from the encoding, not from the widths, and nothing about the order changes.
Where it starts to matter is word boundaries. A screen reader gets text from an extraction layer that has to decide where words end, and it does that by measuring the gaps between glyphs — with the dictionary’s widths, since those are what position text. Some producers place each glyph or each word explicitly, at the advance their layout engine used, which is the program’s. If the dictionary then says a glyph is narrower, the extractor sees a gap after it that is not on the page.
The test suite checks this directly: the same font, with A, B and C each placed at the program’s 600-unit advance. With correct widths, pdftotext and Taggart’s own text reader both read ABC. With B recorded as 400, both read AB C. A sighted reader sees one word; a screen-reader user hears two. The opposite error — a dictionary width wider than the outline — merges what the eye sees as separate words.
Where it comes from
A font program embedded after the fact. The widths were written for the font the document was made with; a later tool embeds a different version of it — the copy installed on the machine doing the embedding — and keeps the original dictionary. This is the hidden cost of fixing an unembedded font (31-009) with whatever is to hand.
Widths written in the wrong units. TrueType fonts often have 2048 units per em, and /Widths is in thousandths. A generator that copies advances without scaling writes every width roughly twice as large as it should be.
An off-by-one /FirstChar. If the array starts one code too early or too late, every glyph gets its neighbour’s width. In a proportional font that is wrong almost everywhere and right by coincidence wherever two neighbours happen to match.
How to check it free
Look at the letter spacing. Letters touching or overlapping, or a regular gap inside words, in a font that is otherwise rendered cleanly, is the visible symptom. Then copy a line: a space inside a word, or two words run together, is the extraction symptom.
Acrobat Pro has a tool Adobe describes for this case. In Adobe’s guide to advanced preflight inspections, browsing a document’s fonts is offered as a way to find the source of preflight problems such as mismatches caused by inconsistent glyph widths. Select Use print production → Preflight from the All tools menu, then Options → Browse Internal Structure of All Document Fonts. For one font, expand Fonts under Results → Overview, select it, and choose Browse Internal Font Structure from the Options menu.
veraPDF compares the two widths glyph by glyph under clause 7.21.5, allowing a difference of one unit, and leaves out text in rendering mode 3.
How to fix it free
Re-export from the source. Positions and widths are then computed from the same font in the same pass, which is the only way to be sure the text lands where it did before.
Acrobat cannot edit the widths. Adobe’s page on the font browser says you can navigate fonts in it but not edit them, and Adobe documents no fixup that rewrites a width array.
For a simple TrueType font with WinAnsi encoding, the widths can be recomputed from the program with the free Python libraries pikepdf and fontTools:
import io
import pikepdf
from fontTools.ttLib import TTFont
with pikepdf.open("in.pdf") 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())))
cmap, scale = tt.getBestCmap(), 1000 / tt["head"].unitsPerEm
widths = []
for code in range(int(font.FirstChar), int(font.LastChar) + 1):
glyph = cmap.get(ord(bytes([code]).decode("cp1252", "replace")), ".notdef")
widths.append(round(tt["hmtx"][glyph][0] * scale))
font.Widths = pikepdf.Array(widths)
pdf.save("out.pdf")
On the test file it turns [600 400 600] into [600 600 600]. Correcting a width moves every glyph set after it in the same run, so compare the page before and after: if the producer laid text out with the wrong widths, the fixed file will look different, and re-exporting is the better repair.
What Taggart does
Taggart does not detect this condition. It reads the widths in font dictionaries but does not open font programs, so it has nothing to compare them with. On the test file it reports nothing about the font; 31-016 is on the list of conditions Taggart asks a person to confirm before it claims conformance.
It is also exposed to the condition. Taggart measures text the way a viewer positions it, from the dictionary — that is what places reading-order badges, compares structure order with page order, and finds the word breaks in its screen-reader preview. On the per-glyph version of the test file, its preview reads AB C, exactly as pdftotext does. A wrong width misleads Taggart’s geometry as much as anyone’s, and no finding says so. Use the Acrobat font browser or veraPDF for this condition 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
Which width does a viewer actually use?
The dictionary's, to decide where the next glyph starts; the program's outline is what gets drawn. That is why a mismatch shows as letters running into each other or standing apart: the position comes from one source and the shape from the other.
How large a difference counts?
More than one thousandth of an em, which is the unit /Widths is written in, so any difference of a whole unit or more after scaling. Rounding a TrueType font's 2048-unit advances to thousandths stays inside that; a wrong font or a wrong scale does not.
Can it change what a screen reader says?
It can. Text extractors, including the ones screen readers rely on, decide where words end from the gaps between glyphs, measured with the dictionary's widths. If a producer placed each glyph at the program's advance and the dictionary says a glyph is narrower, a gap appears that is not on the page, and a word is read as two.
Can I correct the widths after the fact?
Yes, but it moves text: every glyph set after a corrected one shifts by the difference. Compare the pages before and after, and prefer a re-export, which recomputes positions and widths together.
Where this sits in the standards
| Standard | Reference |
|---|---|
| Matterhorn Protocol 1.1 | Checkpoint 31 (Fonts), index 31-016 |
| PDF/UA-1 (ISO 14289-1) | Clause 7.21.5-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-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-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.
- 09-001 Tags are not in logical reading orderThe order of elements in the structure tree does not match the order a human would read the page. This is the single most common substantive defect in tagged PDFs and the one Acrobat's own tooling handles worst.
- 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-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-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 .